1
0
mirror of https://github.com/pcvolkmer/arsnova-client.git synced 2025-04-19 19:16:51 +00:00

feat: apply typestate pattern to client

This prevents using the client without using guest login and without token.
This commit is contained in:
Paul-Christian Volkmer 2023-12-20 14:36:45 +01:00
parent b116457796
commit 9cdd68abed
3 changed files with 30 additions and 11 deletions

View File

@ -10,7 +10,7 @@ Create a client using and request guest login.
```rust ```rust
let client = Client::new("https://ars.particify.de/api").expect("client created"); let client = Client::new("https://ars.particify.de/api").expect("client created");
client.guest_login().await; let client = client.guest_login().await.expect("logged in");
``` ```
### Request room information ### Request room information

View File

@ -55,14 +55,12 @@ pub struct Cli {
async fn main() -> Result<(), ()> { async fn main() -> Result<(), ()> {
let cli = Cli::parse(); let cli = Cli::parse();
let mut client = match Client::new(&cli.url) { let client = match Client::new(&cli.url) {
Ok(client) => client, Ok(client) => client,
Err(_) => return Err(()), Err(_) => return Err(()),
}; };
if client.guest_login().await.is_err() { let client = client.guest_login().await.map_err(|_| ())?;
return Err(());
}
let (tx, rx) = channel::<Feedback>(10); let (tx, rx) = channel::<Feedback>(10);

View File

@ -19,6 +19,7 @@
use std::error; use std::error;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::marker::PhantomData;
use std::time::Duration; use std::time::Duration;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
@ -199,10 +200,14 @@ impl Display for ClientError {
impl error::Error for ClientError {} impl error::Error for ClientError {}
pub struct Client { pub struct LoggedIn;
pub struct LoggedOut;
pub struct Client<State = LoggedOut> {
api_url: String, api_url: String,
http_client: reqwest::Client, http_client: reqwest::Client,
token: Option<String>, token: Option<String>,
state: PhantomData<State>,
} }
impl Client { impl Client {
@ -216,10 +221,13 @@ impl Client {
api_url: api_url.to_string(), api_url: api_url.to_string(),
http_client: client, http_client: client,
token: None, token: None,
state: PhantomData::<LoggedOut>,
}) })
} }
}
pub async fn guest_login(&mut self) -> Result<(), ClientError> { impl Client<LoggedOut> {
pub async fn guest_login(self) -> Result<Client<LoggedIn>, ClientError> {
match self match self
.http_client .http_client
.post(format!("{}/auth/login/guest", self.api_url)) .post(format!("{}/auth/login/guest", self.api_url))
@ -227,15 +235,28 @@ impl Client {
.await .await
{ {
Ok(res) => match res.json::<LoginResponse>().await { Ok(res) => match res.json::<LoginResponse>().await {
Ok(res) => { Ok(res) => Ok(Client {
self.token = Some(res.token); api_url: self.api_url,
Ok(()) http_client: self.http_client,
} token: Some(res.token),
state: PhantomData::<LoggedIn>,
}),
Err(_) => Err(LoginError), Err(_) => Err(LoginError),
}, },
Err(_) => Err(ConnectionError), Err(_) => Err(ConnectionError),
} }
} }
}
impl Client<LoggedIn> {
pub fn logout(self) -> Client<LoggedOut> {
Client {
api_url: self.api_url,
http_client: self.http_client,
token: None,
state: PhantomData::<LoggedOut>,
}
}
pub async fn get_room_info(&self, short_id: &str) -> Result<RoomInfo, ClientError> { pub async fn get_room_info(&self, short_id: &str) -> Result<RoomInfo, ClientError> {
let token = self.token.as_ref().unwrap(); let token = self.token.as_ref().unwrap();