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}