realm_web_rs/
lib.rs

1use std::fmt::Display;
2
3pub use ::bson;
4use bson::{Document, oid::ObjectId};
5use builder_pattern::Builder;
6use reqwest::{StatusCode, header::{HeaderMap, HeaderName, HeaderValue}};
7use serde::{Serialize, Deserialize};
8
9#[derive(Builder, Debug, Clone)]
10/// Implements all the api calls, but doesn't hold information about the selected collection or database
11pub struct Client {
12    #[into]
13    /// The application id, which is inserted into the query url
14    pub application_id: String,
15    #[into]
16    /// authentication using the `apiKey` header
17    pub api_token: String,
18
19    #[default(ApiVersion::v1)]
20    pub api_version: ApiVersion,
21    #[into]
22    #[default(None)]
23    /// should be none, if deployed globally
24    /// or <Region>.<Cloud>
25    pub deployment_region: Option<String>,
26}
27#[derive(Debug, Clone)]
28pub enum ApiVersion {
29    #[allow(non_camel_case_types)]
30    v1,
31}
32#[allow(unused)]
33impl Client {
34    /// gets base url https://data.mongodb-api.com/app/<App ID>/endpoint/data/<API Version>
35    fn get_url(&self) -> String {
36        format!(
37            "https://{}data.mongodb-api.com/app/{}/endpoint/data/{}",
38            match &self.deployment_region {
39                Some(x) => format!("{}.", x),
40                None => "".into()
41            },
42            self.application_id,
43            match &self.api_version {
44                ApiVersion::v1 => "v1",
45            }
46        )
47    }
48    /// gets the base headers
49    fn get_auth_headers(&self) -> HeaderMap {
50        let mut header_map = HeaderMap::new();
51        header_map.append(HeaderName::from_static("apikey"), HeaderValue::from_str(&self.api_token).unwrap());
52        header_map.append(HeaderName::from_static("content-type"), HeaderValue::from_static("application/json"));
53        header_map.append(HeaderName::from_static("accept"), HeaderValue::from_static("application/json"));
54        header_map
55    }
56
57    /// # Find a Single Document
58    /// 
59    /// ### filter
60    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The findOne action returns the first document in the collection that matches this filter.
61    /// If you do not specify a filter, the action matches all document in the collection.
62    /// 
63    /// ### projection
64    /// A [MongoDB Query Projection](https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/).
65    /// Depending on the projection, the returned document will either omit specific fields or include only specified fields or values
66    pub async fn find_one(
67        &self,
68        collection: Collection,
69        filter: Option<Document>,
70        projection: Option<Document>,
71        http_client: &reqwest::Client
72    ) -> Result<FindResponse, Error> {
73        let req = FindRequest {
74            collection,
75            filter,
76            projection,
77            sort: None,
78            limit: None,
79            skip: None
80        };
81
82        let res = http_client.post(format!("{}/action/findOne", self.get_url()))
83            .headers(self.get_auth_headers())
84            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
85            .send()
86            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
87
88        if !res.status().is_success(){
89            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
90        }
91
92        res.json::<FindResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
93    }
94    /// # Find Multiple Documents
95    /// ### filter
96    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The find action returns documents in the collection that match this filter.
97    /// If you do not specify a filter, the action matches all document in the collection. If the filter matches more documents than the specified limit, the action only returns a subset of them. You can use skip in subsequent queries to return later documents in the result set.
98    /// 
99    /// ### projection
100    /// A [MongoDB Query Projection](https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/).
101    /// Depending on the projection, the returned document will either omit specific fields or include only specified fields or values
102    /// 
103    /// ### sort
104    /// A [MongoDB Sort Expression](https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/).
105    /// Matched documents are returned in ascending or descending order of the fields specified in the expression.
106    /// 
107    /// ### limit
108    /// The maximum number of matched documents to include in the returned result set. Each request may return up to 50,000 documents.
109    ///
110    /// ### skip
111    /// The number of matched documents to skip before adding matched documents to the result set.
112    pub async fn find(
113        &self,
114        collection: Collection,
115        filter: Option<Document>,
116        projection: Option<Document>,
117        sort: Option<Document>,
118        limit: Option<i32>,
119        skip: Option<i32>,
120        http_client: &reqwest::Client
121    ) -> Result<FindResponse, Error> {
122        let req = FindRequest {
123            collection,
124            filter,
125            projection,
126            sort,
127            limit,
128            skip
129        };
130
131        let res = http_client.post(format!("{}/action/find", self.get_url()))
132            .headers(self.get_auth_headers())
133            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
134            .send()
135            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
136
137        if !res.status().is_success(){
138            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
139        }
140
141        res.json::<FindResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
142    }
143    /// # Insert a Single Document
144    /// 
145    /// ### document
146    /// An [EJSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) document to insert into the collection.
147    pub async fn insert_one(
148        &self,
149        collection: Collection,
150        document: Document,
151        http_client: &reqwest::Client
152    ) -> Result<InsertResponse, Error> {
153        let req = InsertRequest {
154            collection,
155            document: Some(document),
156            documents: None
157        };
158        let res = http_client.post(format!("{}/action/insertOne", self.get_url()))
159            .headers(self.get_auth_headers())
160            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
161            .send()
162            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
163
164        if !res.status().is_success(){
165            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
166        }
167
168        res.json::<InsertResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
169    } 
170    /// # Insert Multiple Documents
171    /// 
172    /// ### documents
173    /// An array of one or more [EJSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) documents to insert into the collection.
174    pub async fn insert(
175        &self,
176        collection: Collection,
177        documents: Vec<Document>,
178        http_client: &reqwest::Client
179    ) -> Result<InsertResponse, Error> {
180        let req = InsertRequest {
181            collection,
182            document: None,
183            documents: Some(documents)
184        };
185        let res = http_client.post(format!("{}/action/insertMany", self.get_url()))
186            .headers(self.get_auth_headers())
187            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
188            .send()
189            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
190
191        if !res.status().is_success(){
192            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
193        }
194
195        res.json::<InsertResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
196    }
197    /// # Update a Single Document
198    /// ### filter
199    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The updateOne action modifies the first document in the collection that matches this filter.
200    /// ### update
201    /// A [MongoDB Update Expression](https://www.mongodb.com/docs/manual/tutorial/update-documents/) that specifies how to modify the matched document.
202    /// ### upsert
203    /// The upsert flag only applies if no documents match the specified filter. If true, the updateOne action inserts a new document that matches the filter with the specified update applied to it.
204    pub async fn update_one(
205        &self,
206        collection: Collection,
207        filter: Document,
208        update: Document,
209        upsert: Option<bool>,
210        http_client: &reqwest::Client
211    ) -> Result<UpdateResponse, Error> {
212        let req = UpdateRequest {
213            collection,
214            filter,
215            update,
216            upsert
217        };
218        let res = http_client.post(format!("{}/action/updateOne", self.get_url()))
219            .headers(self.get_auth_headers())
220            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
221            .send()
222            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
223
224        if !res.status().is_success(){
225            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
226        }
227
228        res.json::<UpdateResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
229    }
230    /// # Update Multiple Documents
231    /// 
232    /// ### filter
233    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The updateMany action modifies the first document in the collection that matches this filter.
234    /// ### update
235    /// A [MongoDB Update Expression](https://www.mongodb.com/docs/manual/tutorial/update-documents/) that specifies how to modify the matched document.
236    /// ### upsert
237    /// The upsert flag only applies if no documents match the specified filter. If true, the updateMany action inserts a new document that matches the filter with the specified update applied to it.
238    pub async fn update(
239        &self,
240        collection: Collection,
241        filter: Document,
242        update: Document,
243        upsert: Option<bool>,
244        http_client: &reqwest::Client
245    ) -> Result<UpdateResponse, Error> {
246        let req = UpdateRequest {
247            collection,
248            filter,
249            update,
250            upsert
251        };
252        let res = http_client.post(format!("{}/action/updateMany", self.get_url()))
253            .headers(self.get_auth_headers())
254            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
255            .send()
256            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
257
258        if !res.status().is_success(){
259            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
260        }
261
262        res.json::<UpdateResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
263    }
264
265    /// # Replace a Single Document
266    /// ### filter
267    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The replaceOne action overwrites the first document in the collection that matches this filter.
268    /// ### replacement
269    /// An [EJSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) document that overwrites the matched document.
270    /// ### upsert
271    /// The upsert flag only applies if no documents match the specified filter. If true, the replaceOne action inserts the replacement document.
272    pub async fn replace_one(
273        &self,
274        collection: Collection,
275        filter: Document,
276        replacement: Document,
277        upsert: Option<bool>,
278        http_client: &reqwest::Client
279    ) -> Result<ReplaceResponse, Error> {
280        let req = ReplaceRequest {
281            collection,
282            filter,
283            replacement,
284            upsert
285        };
286        let res = http_client.post(format!("{}/action/replaceOne", self.get_url()))
287            .headers(self.get_auth_headers())
288            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
289            .send()
290            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
291
292        if !res.status().is_success(){
293            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
294        }
295
296        res.json::<ReplaceResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
297    }
298    /// # Delete a Single Document
299    /// 
300    /// ### filter
301    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The deleteOne action deletes the first document in the collection that matches this filter.
302    pub async fn delete_one(
303        &self,
304        collection: Collection,
305        filter: Document,
306        http_client: &reqwest::Client
307    ) -> Result<DeleteResponse, Error> {
308        let req = DeleteRequest {
309            collection,
310            filter,
311        };
312        let res = http_client.post(format!("{}/action/deleteOne", self.get_url()))
313            .headers(self.get_auth_headers())
314            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
315            .send()
316            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
317
318        if !res.status().is_success(){
319            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
320        }
321
322        res.json::<DeleteResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
323    }
324    /// # Delete Multiple Documents
325    /// 
326    /// ### filter
327    /// A [MongoDB Query Filter](https://www.mongodb.com/docs/manual/tutorial/query-documents/). The deleteMany action deletes all documents in the collection that match this filter.
328    pub async fn delete(
329        &self,
330        collection: Collection,
331        filter: Document,
332        http_client: &reqwest::Client
333    ) -> Result<DeleteResponse, Error> {
334        let req = DeleteRequest {
335            collection,
336            filter,
337        };
338        let res = http_client.post(format!("{}/action/deleteMany", self.get_url()))
339            .headers(self.get_auth_headers())
340            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
341            .send()
342            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
343
344        if !res.status().is_success(){
345            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
346        }
347
348        res.json::<DeleteResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
349    }
350    /// # Run an Aggregation Pipeline
351    /// 
352    /// ### pipeline
353    /// A [MongoDB Aggregation Pipeline](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/).
354    pub async fn aggregate(
355        &self,
356        collection: Collection,
357        pipeline: Vec<Document>,
358        http_client: &reqwest::Client
359    ) -> Result<AggregationResponse, Error> {
360        let req = AggregationRequest {
361            collection,
362            pipeline,
363        };
364        let res = http_client.post(format!("{}/action/aggregate", self.get_url()))
365            .headers(self.get_auth_headers())
366            .body(serde_json::to_string(&req).map_err(|x| Error {status_code: None, error: format!("Format error: {:?}", x)})?)
367            .send()
368            .await.map_err(|x| Error {status_code: None, error: format!("Failed to send request: {:?}", x)})?;
369
370        if !res.status().is_success(){
371            return Err(Error { status_code: Some(res.status()), error: format!("; content: {}", res.text().await.unwrap_or_default()) })
372        }
373
374        res.json::<AggregationResponse>().await.map_err(|x| Error {status_code: None, error: format!("Failed to deserialize response: {:?}", x)})
375    }
376}
377
378#[allow(unused)]
379#[derive(Debug, Clone, Deserialize)]
380pub struct FindResponse {
381    pub document: Option<Document>,
382    pub documents: Option<Vec<Document>>
383}
384#[allow(unused)]
385#[derive(Debug, Clone, Serialize)]
386#[serde(rename_all = "camelCase")]
387struct FindRequest {
388    #[serde(flatten)]
389    collection: Collection,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    filter: Option<Document>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    projection: Option<Document>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    sort: Option<Document>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    limit: Option<i32>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    skip: Option<i32>
400}
401
402#[allow(unused)]
403#[derive(Debug, Clone, Serialize)]
404#[serde(rename_all = "camelCase")]
405struct InsertRequest {
406    #[serde(flatten)]
407    collection: Collection,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    document: Option<Document>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    documents: Option<Vec<Document>>
412}
413
414#[allow(unused)]
415#[derive(Debug, Clone, Deserialize)]
416pub struct InsertResponse {
417    pub inserted_id: Option<ObjectId>,
418    pub inserted_ids: Option<Vec<ObjectId>>
419}
420
421#[allow(unused)]
422#[derive(Debug, Clone, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub struct UpdateResponse {
425    pub matched_count: i32,
426    pub modified_count: i32,
427    pub upserted_id: Option<ObjectId>
428}
429
430#[allow(unused)]
431#[derive(Debug, Clone, Deserialize)]
432#[serde(rename_all = "camelCase")]
433pub struct ReplaceResponse {
434    pub matched_count: i32,
435    pub modified_count: i32,
436    pub upserted_id: Option<ObjectId>
437}
438
439#[allow(unused)]
440#[derive(Debug, Clone, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct DeleteResponse {
443    pub deleted_count: i32,
444}
445
446#[allow(unused)]
447#[derive(Debug, Clone, Serialize)]
448#[serde(rename_all = "camelCase")]
449struct UpdateRequest {
450    #[serde(flatten)]
451    collection: Collection,
452    filter: Document,
453    update: Document,
454    #[serde(skip_serializing_if = "Option::is_none")]
455    upsert: Option<bool>,
456}
457
458#[allow(unused)]
459#[derive(Debug, Clone, Serialize)]
460#[serde(rename_all = "camelCase")]
461struct ReplaceRequest {
462    #[serde(flatten)]
463    collection: Collection,
464    filter: Document,
465    replacement: Document,
466    #[serde(skip_serializing_if = "Option::is_none")]
467    upsert: Option<bool>,
468}
469
470#[allow(unused)]
471#[derive(Debug, Clone, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct AggregationResponse {
474    pub documents: Vec<Document>
475}
476
477#[allow(unused)]
478#[derive(Debug, Clone, Serialize)]
479#[serde(rename_all = "camelCase")]
480struct DeleteRequest {
481    #[serde(flatten)]
482    collection: Collection,
483    filter: Document,
484}
485
486#[allow(unused)]
487#[derive(Debug, Clone, Serialize)]
488#[serde(rename_all = "camelCase")]
489struct AggregationRequest {
490    #[serde(flatten)]
491    collection: Collection,
492    pipeline: Vec<Document>,
493}
494
495#[allow(unused)]
496#[derive(Debug, Clone)]
497pub struct Error {
498    /// A statuscode, only available if the request gets denied
499    status_code: Option<StatusCode>,
500    error: String,
501}
502impl Display for Error {
503    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504        write!(f, "StatusCode: {:?}; {}", self.status_code, self.error)
505    }
506}
507
508#[derive(Debug, Clone, Serialize)]
509#[serde(rename_all = "camelCase")]
510/// holds information which collection to select
511pub struct Collection {
512    /// Atlas data source
513    pub data_source: String,
514    /// database name
515    pub database: String,
516    /// collection name
517    pub collection: String,
518}