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 LessThan,
98 LessThanEq,
100 GreaterThan,
102 GreaterThanEq,
104 Eq,
106 NotEq,
108 ArrayContains,
110 In,
112 ArrayContainsAny,
114 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}