rust_freely/client/
api.rs

1/// Provides convenience functions for HTTP requests & serialization
2pub mod api_wrapper {
3    use std::fmt::Debug;
4
5    use reqwest::{header, Client as ReqwestClient, Error, Method, RequestBuilder, Response, Url};
6    use serde::{de::DeserializeOwned, Serialize};
7
8    use crate::{
9        api_client::{ApiError, Client, RequestError},
10        api_models::responses::ResponseModel,
11    };
12
13    #[derive(Clone, Debug)]
14    /// Wrapper struct for API, implements all API methods. Generally not useful for clients.
15    pub struct Api {
16        client: Client,
17    }
18
19    impl Api {
20        /// Creates a new Api instance with a passed [Client]
21        pub fn new(client: Client) -> Self {
22            Api { client }
23        }
24
25        /// Fetches the base URL from the [Client] instance
26        pub fn base(&self) -> String {
27            self.client.url()
28        }
29
30        /// Fetches the API token from the [Client] instance
31        pub fn token(&self) -> Option<String> {
32            self.client.token()
33        }
34
35        /// Determines if the current session is authenticated
36        pub fn is_authenticated(&self) -> bool {
37            self.client.is_authenticated()
38        }
39
40        /// Assembles an API url from the base url and an endpoint.
41        pub fn url(&self, endpoint: &str) -> Result<Url, ApiError> {
42            if let Ok(result) = Url::parse(self.base().as_str()) {
43                if let Ok(result) = result.join(vec!["/api", endpoint].join("").as_str()) {
44                    Ok(result)
45                } else {
46                    Err(ApiError::UrlError {})
47                }
48            } else {
49                Err(ApiError::UrlError {})
50            }
51        }
52
53        fn http(&self) -> Result<ReqwestClient, Error> {
54            let mut headers = header::HeaderMap::new();
55            headers.insert(
56                "Accept",
57                header::HeaderValue::from_static("application/json"),
58            );
59            headers.insert(
60                "Content-Type",
61                header::HeaderValue::from_static("application/json"),
62            );
63
64            ReqwestClient::builder().default_headers(headers).build()
65        }
66
67        /// Assembles a request builder with default settings
68        pub fn request(&self, endpoint: &str, method: Method) -> Result<RequestBuilder, ApiError> {
69            if let Ok(http) = self.http() {
70                if let Ok(url) = self.url(endpoint) {
71                    let mut request = http.request(method, url.clone());
72                    println!("{:?}", url);
73                    if let Some(token) = self.token() {
74                        request = request.header(header::AUTHORIZATION, format!("Token {token}"));
75                    }
76                    Ok(request)
77                } else {
78                    Err(ApiError::UrlError {})
79                }
80            } else {
81                Err(ApiError::UnknownError {})
82            }
83        }
84
85        /// Extracts a reponse with serde
86        pub async fn extract_response<T: DeserializeOwned + Debug>(
87            &self,
88            response: Response,
89        ) -> Result<T, ApiError> {
90            match response.error_for_status() {
91                Ok(resp) => {
92                    let text = resp.text().await.unwrap();
93                    serde_json::from_str::<ResponseModel>(text.clone().as_str())
94                        .or(Err(ApiError::ParseError {
95                            text: text.clone(),
96                        }))
97                        .and_then(|v| {
98                            serde_json::from_value::<T>(v.data).or(Err(ApiError::ParseError {
99                                text: text.clone(),
100                            }))
101                        })
102                }
103                Err(resp) => Err(ApiError::Request {
104                    error: RequestError {
105                        code: resp.status().map_or(0, |s| s.as_u16()),
106                        reason: Some(resp.to_string()),
107                    },
108                }),
109            }
110        }
111
112        /// Executes a GET request.
113        pub async fn get<T: DeserializeOwned + Debug>(
114            &self,
115            endpoint: &str,
116        ) -> Result<T, ApiError> {
117            if let Ok(response) = self.request(endpoint, Method::GET)?.send().await {
118                self.extract_response::<T>(response).await
119            } else {
120                Err(ApiError::ConnectionError {})
121            }
122        }
123
124        /// Executes a DELETE request
125        pub async fn delete(
126            &self,
127            endpoint: &str,
128        ) -> Result<(), ApiError> {
129            if let Ok(response) = self.request(endpoint, Method::DELETE)?.send().await {
130                match response.error_for_status() {
131                    Ok(_) => Ok(()),
132                    Err(resp) => Err(ApiError::Request {
133                        error: RequestError {
134                            code: resp.status().map_or(0, |s| s.as_u16()),
135                            reason: Some(resp.to_string()),
136                        },
137                    })
138                }
139                
140            } else {
141                Err(ApiError::ConnectionError {})
142            }
143        }
144
145        /// Executes a POST request
146        pub async fn post<T: DeserializeOwned + Debug, D: Serialize>(
147            &self,
148            endpoint: &str,
149            data: Option<D>,
150        ) -> Result<T, ApiError> {
151            if let Ok(response) = self
152                .request(endpoint, Method::POST)?
153                .json(&data)
154                .send()
155                .await
156            {
157                self.extract_response::<T>(response).await
158            } else {
159                Err(ApiError::ConnectionError {})
160            }
161        }
162    }
163}