firebase_rs/
lib.rs

1use constants::{Method, Response, AUTH};
2use errors::{RequestResult, UrlParseResult};
3use params::Params;
4use reqwest::StatusCode;
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use serde_json::Value;
8use std::fmt::Debug;
9use url::Url;
10use utils::check_uri;
11
12use crate::sse::ServerEvents;
13
14pub use errors::{RequestError, ServerEventError, UrlParseError};
15
16mod constants;
17mod errors;
18mod params;
19mod sse;
20mod utils;
21
22#[derive(Debug)]
23pub struct Firebase {
24    uri: Url,
25}
26
27impl Firebase {
28    /// ```rust
29    /// use firebase_rs::Firebase;
30    ///
31    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap();
32    /// ```
33    pub fn new(uri: &str) -> UrlParseResult<Self>
34    where
35        Self: Sized,
36    {
37        match check_uri(&uri) {
38            Ok(uri) => Ok(Self { uri }),
39            Err(err) => Err(err),
40        }
41    }
42
43    /// ```rust
44    /// const URI: &str = "...";
45    /// const AUTH_KEY: &str = "...";
46    ///
47    /// use firebase_rs::Firebase;
48    ///
49    /// let firebase = Firebase::auth("https://myfirebase.firebaseio.com", AUTH_KEY).unwrap();
50    /// ```
51    pub fn auth(uri: &str, auth_key: &str) -> UrlParseResult<Self>
52    where
53        Self: Sized,
54    {
55        match check_uri(&uri) {
56            Ok(mut uri) => {
57                uri.set_query(Some(&format!("{}={}", AUTH, auth_key)));
58                Ok(Self { uri })
59            }
60            Err(err) => Err(err),
61        }
62    }
63
64    /// ```rust
65    /// use firebase_rs::Firebase;
66    ///
67    /// # async fn run() {
68    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().with_params().start_at(1).order_by("name").equal_to(5).finish();
69    /// let result = firebase.get::<String>().await;
70    /// # }
71    /// ```
72    pub fn with_params(&self) -> Params {
73        let uri = self.uri.clone();
74        Params::new(uri)
75    }
76
77    /// To use simple interface with synchronous callbacks, pair with `.listen()`:
78    /// ```rust
79    /// use firebase_rs::Firebase;
80    /// # async fn run() {
81    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
82    /// let stream = firebase.with_realtime_events().unwrap();
83    /// stream.listen(|event_type, data| {
84    ///                     println!("{:?} {:?}" ,event_type, data);
85    ///                 }, |err| println!("{:?}" ,err), false).await;
86    /// # }
87    /// ```
88    ///
89    /// To use streaming interface for async code, pair with `.stream()`:
90    /// ```rust
91    /// use firebase_rs::Firebase;
92    /// use futures_util::StreamExt;
93    ///
94    /// # async fn run() {
95    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
96    /// let stream = firebase.with_realtime_events()
97    ///              .unwrap()
98    ///              .stream(true);
99    /// stream.for_each(|event| {
100    ///           match event {
101    ///               Ok((event_type, maybe_data)) => println!("{:?} {:?}" ,event_type, maybe_data),
102    ///               Err(err) => println!("{:?}" ,err),
103    ///           }
104    ///           futures_util::future::ready(())
105    ///        }).await;
106    /// # }
107    /// ```
108    pub fn with_realtime_events(&self) -> Option<ServerEvents> {
109        ServerEvents::new(self.uri.as_str())
110    }
111
112    /// ```rust
113    /// use firebase_rs::Firebase;
114    ///
115    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users").at("USER_ID").at("f69111a8a5258c15286d3d0bd4688c55");
116    /// ```
117    pub fn at(&self, path: &str) -> Self {
118        let uri = self.build_uri(path);
119
120        Firebase { uri }
121    }
122
123    fn build_uri(&self, path: &str) -> Url {
124        let mut new_path = String::new();
125
126        if let Some(segments) = self.uri.path_segments() {
127            for segment in segments {
128                let clean_segment = segment.trim_end_matches(".json");
129                new_path.push_str(clean_segment);
130                new_path.push('/');
131            }
132        }
133
134        new_path.push_str(path);
135
136        let final_path = if new_path.ends_with(".json") {
137            new_path.trim_end_matches(".json").to_string()
138        } else {
139            new_path
140        };
141
142        let mut uri = self.uri.clone();
143        uri.set_path(&format!("{}.json", final_path));
144
145        uri
146    }
147
148    /// ```rust
149    /// use firebase_rs::Firebase;
150    ///
151    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
152    /// let uri = firebase.get_uri();
153    /// ```
154    pub fn get_uri(&self) -> String {
155        self.uri.to_string()
156    }
157
158    async fn request(&self, method: Method, data: Option<Value>) -> RequestResult<Response> {
159        let client = reqwest::Client::new();
160        let request = match method {
161            Method::GET => client.get(self.uri.to_string()).send().await,
162            Method::PUT | Method::PATCH | Method::POST => {
163                if data.is_none() {
164                    return Err(RequestError::SerializeError);
165                }
166                let builder = if method == Method::PUT {
167                    client.put(self.uri.to_string())
168                } else if method == Method::POST {
169                    client.post(self.uri.to_string())
170                } else {
171                    client.patch(self.uri.to_string())
172                };
173                builder.json(&data).send().await
174            }
175            Method::DELETE => client.delete(self.uri.to_string()).send().await,
176        };
177
178        match request {
179            Ok(response) => match response.status() {
180                StatusCode::OK => {
181                    let response_text = response.text().await.unwrap_or_default();
182                    if response_text == "null" {
183                        Err(RequestError::NotFoundOrNullBody)
184                    } else {
185                        Ok(Response { data: response_text })
186                    }
187                }
188                _ => Err(RequestError::NetworkError),
189            },
190            Err(_) => Err(RequestError::NetworkError),
191        }
192    }
193
194    async fn request_generic<T>(&self, method: Method) -> RequestResult<T>
195    where
196        T: Serialize + DeserializeOwned + Debug,
197    {
198        let request = self.request(method, None).await;
199
200        match request {
201            Ok(response) => {
202                let data: T = serde_json::from_str(response.data.as_str()).unwrap();
203
204                Ok(data)
205            }
206            Err(err) => Err(err),
207        }
208    }
209
210    /// ```rust
211    /// use firebase_rs::Firebase;
212    /// use serde::{Serialize, Deserialize};
213    ///
214    /// #[derive(Serialize, Deserialize, Debug)]
215    /// struct User {
216    ///    name: String
217    /// }
218    ///
219    /// # async fn run() {
220    /// let user = User { name: String::default() };
221    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
222    /// let users = firebase.set(&user).await;
223    /// # }
224    /// ```
225    pub async fn set<T>(&self, data: &T) -> RequestResult<Response>
226    where
227        T: Serialize + DeserializeOwned + Debug,
228    {
229        let data = serde_json::to_value(&data).unwrap();
230        self.request(Method::POST, Some(data)).await
231    }
232
233    /// ```rust
234    /// use firebase_rs::Firebase;
235    /// use serde::{Serialize, Deserialize};
236    ///
237    /// #[derive(Serialize, Deserialize, Debug)]
238    /// struct User {
239    ///    name: String
240    /// }
241    ///
242    /// # async fn run() {
243    /// let user = User { name: String::default() };
244    /// let mut firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
245    /// let users = firebase.set_with_key("myKey", &user).await;
246    /// # }
247    /// ```
248    pub async fn set_with_key<T>(&mut self, key: &str, data: &T) -> RequestResult<Response>
249    where
250        T: Serialize + DeserializeOwned + Debug,
251    {
252        self.uri = self.build_uri(key);
253        let data = serde_json::to_value(&data).unwrap();
254
255        self.request(Method::PUT, Some(data)).await
256    }
257
258    /// ```rust
259    /// use std::collections::HashMap;
260    /// use firebase_rs::Firebase;
261    /// use serde::{Serialize, Deserialize};
262    ///
263    /// #[derive(Serialize, Deserialize, Debug)]
264    /// struct User {
265    ///    name: String
266    /// }
267    ///
268    /// # async fn run() {
269    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
270    /// let users = firebase.get::<HashMap<String, User>>().await;
271    /// # }
272    /// ```
273    pub async fn get_as_string(&self) -> RequestResult<Response> {
274        self.request(Method::GET, None).await
275    }
276
277    /// ```rust
278    /// use std::collections::HashMap;
279    /// use firebase_rs::Firebase;
280    /// use serde::{Serialize, Deserialize};
281    ///
282    /// #[derive(Serialize, Deserialize, Debug)]
283    /// struct User {
284    ///     name: String
285    /// }
286    ///
287    /// # async fn run() {
288    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users").at("USER_ID");
289    /// let user = firebase.get::<User>().await;
290    ///
291    ///  // OR
292    ///
293    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users");
294    /// let user = firebase.get::<HashMap<String, User>>().await;
295    /// # }
296    /// ```
297    pub async fn get<T>(&self) -> RequestResult<T>
298    where
299        T: Serialize + DeserializeOwned + Debug,
300    {
301        self.request_generic::<T>(Method::GET).await
302    }
303
304    /// ```rust
305    /// use firebase_rs::Firebase;
306    ///
307    /// # async fn run() {
308    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users").at("USER_ID");
309    /// firebase.delete().await;
310    /// # }
311    /// ```
312    pub async fn delete(&self) -> RequestResult<Response> {
313        self.request(Method::DELETE, None).await
314    }
315
316    /// ```rust
317    /// use firebase_rs::Firebase;
318    /// use serde::{Serialize, Deserialize};
319    ///
320    /// #[derive(Serialize, Deserialize, Debug)]
321    /// struct User {
322    ///     name: String
323    /// }
324    ///
325    /// # async fn run() {
326    /// let user = User { name: String::default() };
327    /// let firebase = Firebase::new("https://myfirebase.firebaseio.com").unwrap().at("users").at("USER_ID");
328    /// let users = firebase.update(&user).await;
329    /// # }
330    /// ```
331    pub async fn update<T>(&self, data: &T) -> RequestResult<Response>
332    where
333        T: DeserializeOwned + Serialize + Debug,
334    {
335        let value = serde_json::to_value(&data).unwrap();
336        self.request(Method::PATCH, Some(value)).await
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use crate::{Firebase, UrlParseError};
343
344    const URI: &str = "https://firebase_id.firebaseio.com";
345    const URI_WITH_SLASH: &str = "https://firebase_id.firebaseio.com/";
346    const URI_NON_HTTPS: &str = "http://firebase_id.firebaseio.com/";
347
348    #[tokio::test]
349    async fn simple() {
350        let firebase = Firebase::new(URI).unwrap();
351        assert_eq!(URI_WITH_SLASH.to_string(), firebase.get_uri());
352    }
353
354    #[tokio::test]
355    async fn non_https() {
356        let firebase = Firebase::new(URI_NON_HTTPS).map_err(|e| e.to_string());
357        assert_eq!(
358            firebase.err(),
359            Some(String::from(UrlParseError::NotHttps.to_string()))
360        );
361    }
362
363    #[tokio::test]
364    async fn with_auth() {
365        let firebase = Firebase::auth(URI, "auth_key").unwrap();
366        assert_eq!(
367            format!("{}/?auth=auth_key", URI.to_string()),
368            firebase.get_uri()
369        );
370    }
371
372    #[tokio::test]
373    async fn with_sse_events() {
374        // TODO: SSE Events Test
375    }
376}