firebase_wasm/
firestore.rs

1mod bindings;
2
3use crate::FirebaseError;
4use bindings as b;
5pub use bindings::{
6    delete_doc, doc, get_firestore, on_snapshot_doc, on_snapshot_query, query, set_doc,
7    CollectionReference, DocumentReference, DocumentSnapshot, Firestore, Query, QueryConstraint,
8    QuerySnapshot, SetDocOptions, Transaction,
9};
10use futures::Future;
11use std::{cell::RefCell, error::Error, fmt, rc::Rc};
12use wasm_bindgen::prelude::*;
13use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
14
15#[derive(Clone, Debug, derive_more::Deref)]
16#[wasm_bindgen(getter_with_clone)]
17pub struct FirestoreError {
18    #[wasm_bindgen(skip)]
19    pub kind: FirestoreErrorKind,
20    #[deref]
21    #[wasm_bindgen(readonly)]
22    pub source: FirebaseError,
23}
24
25impl fmt::Display for FirestoreError {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        self.source.fmt(f)
28    }
29}
30
31impl Error for FirestoreError {
32    fn source(&self) -> Option<&(dyn Error + 'static)> {
33        Some(&self.source)
34    }
35}
36
37impl From<FirebaseError> for FirestoreError {
38    fn from(err: FirebaseError) -> Self {
39        let kind = err.code().parse().unwrap();
40
41        Self { kind, source: err }
42    }
43}
44
45#[derive(Clone, Debug, strum_macros::EnumString)]
46#[non_exhaustive]
47pub enum FirestoreErrorKind {
48    #[strum(serialize = "cancelled")]
49    Cancelled,
50    #[strum(serialize = "unknown")]
51    Unknown,
52    #[strum(serialize = "invalid-argument")]
53    InvalidArgument,
54    #[strum(serialize = "deadline-exceeded")]
55    DeadlineExceeded,
56    #[strum(serialize = "not-found")]
57    NotFound,
58    #[strum(serialize = "already-exists")]
59    AlreadyExists,
60    #[strum(serialize = "permission-denied")]
61    PermissionDenied,
62    #[strum(serialize = "resource-exhausted")]
63    ResourceExhausted,
64    #[strum(serialize = "failed-precondition")]
65    FailedPrecondition,
66    #[strum(serialize = "aborted")]
67    Aborted,
68    #[strum(serialize = "out-of-range")]
69    OutOfRange,
70    #[strum(serialize = "unimplemented")]
71    Unimplemented,
72    #[strum(serialize = "internal")]
73    Internal,
74    #[strum(serialize = "unavailable")]
75    Unavailable,
76    #[strum(serialize = "data-loss")]
77    DataLoss,
78    #[strum(serialize = "unauthenticated")]
79    Unauthenticated,
80    #[strum(default)]
81    Other(String),
82}
83
84pub fn where_<V: Into<JsValue>>(
85    field_path: &str,
86    op: QueryConstraintOp,
87    value: V,
88) -> QueryConstraint {
89    let value = value.into();
90
91    b::where_(field_path, &op.to_string(), value)
92}
93
94pub enum QueryConstraintOp {
95    /// `<`o
96    ///
97    LessThan,
98    /// `<=`
99    LessThanEq,
100    /// `>`
101    GreaterThan,
102    /// `>=`
103    GreaterThanEq,
104    /// `==`
105    Eq,
106    /// `!=`
107    NotEq,
108    /// `array-contains`
109    ArrayContains,
110    /// `in`
111    In,
112    /// `array-contains-any`
113    ArrayContainsAny,
114    /// `not-in`
115    NotIn,
116}
117
118impl fmt::Display for QueryConstraintOp {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        let str = match self {
121            Self::LessThan => "<",
122            Self::LessThanEq => "<=",
123            Self::GreaterThan => ">",
124            Self::GreaterThanEq => ">=",
125            Self::Eq => "==",
126            Self::NotEq => "!=",
127            Self::ArrayContains => "array-contains",
128            Self::In => "in",
129            Self::ArrayContainsAny => "array-contains-any",
130            Self::NotIn => "not-in",
131        };
132
133        f.write_str(str)
134    }
135}
136
137pub async fn get_doc(doc: DocumentReference) -> Result<DocumentSnapshot, FirestoreError> {
138    b::get_doc(doc)
139        .await
140        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
141        .map(|snapshot| snapshot.unchecked_into())
142}
143
144pub async fn get_docs(query: Query) -> Result<QuerySnapshot, FirestoreError> {
145    b::get_docs(query)
146        .await
147        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
148        .map(|snapshot| snapshot.unchecked_into())
149}
150
151pub async fn set_doc_with_options<D: Into<JsValue>>(
152    doc: DocumentReference,
153    data: D,
154    options: SetDocOptions,
155) -> Result<(), FirestoreError> {
156    b::set_doc_with_options(doc, data.into(), options)
157        .await
158        .map_err(|err| err.unchecked_into::<FirebaseError>().into())
159}
160
161pub fn collection(firestore: Firestore, path: &str) -> Result<CollectionReference, FirestoreError> {
162    b::collection(firestore, path).map_err(|err| err.into())
163}
164
165impl Transaction {
166    pub async fn get(&self, doc: DocumentReference) -> Result<DocumentSnapshot, FirestoreError> {
167        self.get_js(doc)
168            .await
169            .map_err(|err| err.unchecked_into::<FirebaseError>().into())
170            .map(|snapshot| snapshot.unchecked_into())
171    }
172
173    pub fn set(&self, doc: DocumentReference, data: JsValue) -> Result<Self, FirestoreError> {
174        self.set_js(doc, data).map_err(Into::into)
175    }
176
177    pub fn update(&self, doc: DocumentReference, data: JsValue) -> Result<Self, FirestoreError> {
178        self.update_js(doc, data).map_err(Into::into)
179    }
180
181    pub fn delete(&self, doc: DocumentReference) -> Result<Self, FirestoreError> {
182        self.delete_js(doc).map_err(Into::into)
183    }
184}
185
186#[derive(Clone, Debug, thiserror::Error)]
187pub enum TransactionError {
188    #[error("firestore error: {0}")]
189    Firestore(
190        #[from]
191        #[source]
192        FirestoreError,
193    ),
194    #[error("user-thrown error: {0:#?}")]
195    Custom(JsValue),
196}
197
198pub async fn run_transaction<F, Fut, T, Err>(
199    firestore: Firestore,
200    update_fn: F,
201) -> Result<(), TransactionError>
202where
203    F: FnMut(Transaction) -> Fut + 'static,
204    Fut: Future<Output = Result<T, Err>>,
205    T: Into<JsValue>,
206    Err: Into<JsValue>,
207{
208    let update_fn = Rc::new(RefCell::new(update_fn));
209
210    let update_fn = Closure::new(move |t| {
211        wasm_bindgen_futures::future_to_promise(clone!([update_fn], async move {
212            let mut update_fn_borrow = update_fn.borrow_mut();
213
214            update_fn_borrow(t)
215                .await
216                .map(|v| v.into())
217                .map_err(|err| err.into())
218        }))
219    });
220
221    b::run_transaction(firestore, &update_fn)
222        .await
223        .map_err(|err| {
224            if let Ok(err) = err.clone().dyn_into::<js_sys::Object>() {
225                let name = err.constructor().name();
226
227                if name == "FirebaseError" {
228                    let err = err.unchecked_into::<FirebaseError>().into();
229                    TransactionError::Firestore(err)
230                } else {
231                    TransactionError::Custom(err.unchecked_into())
232                }
233            } else {
234                TransactionError::Custom(err)
235            }
236        })
237}