1use quarb::{AstAdapter, NodeId, Value};
57use serde_json::Value as Json;
58use std::cell::RefCell;
59use std::collections::HashMap;
60
61#[derive(Debug, thiserror::Error)]
63pub enum FirebaseError {
64 #[error("firebase: {0}")]
65 Http(#[from] Box<ureq::Error>),
66 #[error("firebase: {0}")]
67 Api(String),
68 #[error("firebase target: {0} (expected firebase://HOST/BASE/PATH[?QUERY])")]
69 Target(String),
70}
71
72enum Kind {
74 Scalar(Value),
76 Container(Vec<NodeId>),
80 Opaque,
87}
88
89struct Node {
90 path: String,
92 name: Option<String>,
93 parent: Option<NodeId>,
94 kind: RefCell<Option<Kind>>,
96}
97
98enum GetError {
103 Status(u16),
105 Other(String),
109}
110
111impl std::fmt::Display for GetError {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 GetError::Status(code) => write!(f, "status code {code}"),
115 GetError::Other(msg) => f.write_str(msg),
116 }
117 }
118}
119
120pub type Refs = std::collections::HashMap<String, String>;
123
124pub fn parse_refs(text: &str) -> Result<Refs, FirebaseError> {
126 let json: Json = serde_json::from_str(text)
127 .map_err(|e| FirebaseError::Api(format!("refs document: {e}")))?;
128 let map = json
129 .get("refs")
130 .and_then(|v| v.as_object())
131 .ok_or_else(|| {
132 FirebaseError::Api(
133 "refs document: expected {\"refs\": {\"field\": \"container\"}}".into(),
134 )
135 })?;
136 map.iter()
137 .map(|(k, v)| {
138 v.as_str()
139 .map(|t| (k.clone(), t.to_string()))
140 .ok_or_else(|| {
141 FirebaseError::Api(format!("refs document: '{k}' target must be a string"))
142 })
143 })
144 .collect()
145}
146
147pub struct FirebaseAdapter {
149 base: String,
151 query: String,
153 nodes: RefCell<Vec<Node>>,
154 by_path: RefCell<HashMap<String, NodeId>>,
156 refs: Refs,
159}
160
161impl FirebaseAdapter {
162 pub fn connect(target: &str) -> Result<Self, FirebaseError> {
165 Self::connect_with_refs(target, Refs::new())
166 }
167
168 pub fn connect_with_refs(target: &str, refs: Refs) -> Result<Self, FirebaseError> {
172 let rest = target
173 .strip_prefix("firebase://")
174 .ok_or_else(|| FirebaseError::Target(target.to_string()))?;
175 let (path, query) = match rest.split_once('?') {
176 Some((p, q)) => (p, q.to_string()),
177 None => (rest, String::new()),
178 };
179 if path.is_empty() {
180 return Err(FirebaseError::Target(target.to_string()));
181 }
182 let adapter = FirebaseAdapter {
183 base: format!("https://{}", path.trim_end_matches('/')),
184 query,
185 nodes: RefCell::new(vec![Node {
186 path: String::new(),
187 name: None,
188 parent: None,
189 kind: RefCell::new(None),
190 }]),
191 by_path: RefCell::new(HashMap::new()),
192 refs,
193 };
194 adapter
199 .fetch(NodeId(0))
200 .map_err(|e| FirebaseError::Api(format!("probing the database root: {e}")))?;
201 Ok(adapter)
202 }
203
204 pub fn locator(&self, node: NodeId) -> String {
206 let path = &self.nodes.borrow()[node.0 as usize].path;
207 if path.is_empty() {
208 "/".to_string()
209 } else {
210 format!("/{path}")
211 }
212 }
213
214 fn url(&self, path: &str, shallow: bool) -> String {
215 let mut url = if path.is_empty() {
216 format!("{}.json", self.base)
217 } else {
218 format!("{}/{path}.json", self.base)
219 };
220 let mut params = Vec::new();
221 if shallow {
222 params.push("shallow=true".to_string());
223 }
224 if !self.query.is_empty() {
225 params.push(self.query.clone());
226 }
227 if !params.is_empty() {
228 url.push('?');
229 url.push_str(¶ms.join("&"));
230 }
231 url
232 }
233
234 fn get(&self, url: &str) -> Result<Json, GetError> {
235 let resp = match ureq::get(url).call() {
239 Ok(resp) => resp,
240 Err(ureq::Error::Status(code, _)) => return Err(GetError::Status(code)),
241 Err(ureq::Error::Transport(t)) => {
242 let mut msg = t.kind().to_string();
243 if let Some(detail) = t.message() {
244 msg.push_str(": ");
245 msg.push_str(detail);
246 }
247 return Err(GetError::Other(msg));
248 }
249 };
250 resp.into_json()
251 .map_err(|e| GetError::Other(format!("decoding response: {e}")))
252 }
253
254 fn intern(&self, parent: NodeId, key: &str) -> NodeId {
256 let path = {
257 let nodes = self.nodes.borrow();
258 let ppath = &nodes[parent.0 as usize].path;
259 if ppath.is_empty() {
260 key.to_string()
261 } else {
262 format!("{ppath}/{key}")
263 }
264 };
265 if let Some(&id) = self.by_path.borrow().get(&path) {
266 return id;
267 }
268 let mut nodes = self.nodes.borrow_mut();
269 let id = NodeId(nodes.len() as u64);
270 nodes.push(Node {
271 path: path.clone(),
272 name: Some(key.to_string()),
273 parent: Some(parent),
274 kind: RefCell::new(None),
275 });
276 self.by_path.borrow_mut().insert(path, id);
277 id
278 }
279
280 fn fetch(&self, node: NodeId) -> Result<(), String> {
284 let (path, fetched) = {
285 let nodes = self.nodes.borrow();
286 let n = &nodes[node.0 as usize];
287 (n.path.clone(), n.kind.borrow().is_some())
288 };
289 if fetched {
290 return Ok(());
291 }
292 let json = match self.get(&self.url(&path, true)) {
293 Ok(j) => j,
294 Err(GetError::Status(401)) => {
300 *self.nodes.borrow()[node.0 as usize].kind.borrow_mut() = Some(Kind::Opaque);
301 return Ok(());
302 }
303 Err(e) => return Err(e.to_string()),
304 };
305 let kind = match &json {
306 Json::Object(map) => {
307 let mut keys: Vec<&String> = map.keys().collect();
311 keys.sort_by(|a, b| match (a.parse::<i64>(), b.parse::<i64>()) {
312 (Ok(x), Ok(y)) => x.cmp(&y),
313 (Ok(_), Err(_)) => std::cmp::Ordering::Less,
314 (Err(_), Ok(_)) => std::cmp::Ordering::Greater,
315 (Err(_), Err(_)) => a.cmp(b),
316 });
317 let children = keys.iter().map(|k| self.intern(node, k)).collect();
318 Kind::Container(children)
319 }
320 Json::Array(items) => {
321 let children = (0..items.len())
322 .map(|i| self.intern(node, &i.to_string()))
323 .collect();
324 Kind::Container(children)
325 }
326 other => Kind::Scalar(scalar_of(other)),
327 };
328 *self.nodes.borrow()[node.0 as usize].kind.borrow_mut() = Some(kind);
329 Ok(())
330 }
331
332 fn touched(&self, node: NodeId) {
333 if let Err(e) = self.fetch(node) {
334 let path = self.locator(node);
335 eprintln!("quarb-firebase: fetching {path}: {e}");
336 *self.nodes.borrow()[node.0 as usize].kind.borrow_mut() =
337 Some(Kind::Container(Vec::new()));
338 }
339 }
340
341 fn field(&self, node: NodeId, name: &str) -> Option<Value> {
344 let child = self.intern(node, name);
345 self.touched(child);
346 let nodes = self.nodes.borrow();
347 match &*nodes[child.0 as usize].kind.borrow() {
348 Some(Kind::Scalar(v)) => match v {
349 Value::Null => None,
350 other => Some(other.clone()),
351 },
352 _ => None,
353 }
354 }
355}
356
357fn scalar_of(value: &Json) -> Value {
359 match value {
360 Json::Bool(b) => Value::Bool(*b),
361 Json::Number(n) => n
362 .as_i64()
363 .map(Value::Int)
364 .or_else(|| n.as_f64().map(Value::Float))
365 .unwrap_or(Value::Null),
366 Json::String(s) => Value::Str(s.clone()),
367 _ => Value::Null,
368 }
369}
370
371impl AstAdapter for FirebaseAdapter {
372 fn root(&self) -> NodeId {
373 NodeId(0)
374 }
375
376 fn children(&self, node: NodeId) -> Vec<NodeId> {
377 self.touched(node);
378 let nodes = self.nodes.borrow();
379 match &*nodes[node.0 as usize].kind.borrow() {
380 Some(Kind::Container(c)) => c.clone(),
381 _ => Vec::new(),
382 }
383 }
384
385 fn name(&self, node: NodeId) -> Option<String> {
386 self.nodes.borrow()[node.0 as usize].name.clone()
387 }
388
389 fn parent(&self, node: NodeId) -> Option<NodeId> {
390 self.nodes.borrow()[node.0 as usize].parent
391 }
392
393 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
397 self.touched(node);
398 {
399 let nodes = self.nodes.borrow();
400 match &*nodes[node.0 as usize].kind.borrow() {
401 Some(Kind::Scalar(_)) => return Vec::new(),
402 Some(Kind::Container(c)) => {
403 return c
404 .iter()
405 .copied()
406 .filter(|&c| nodes[c.0 as usize].name.as_deref() == Some(name))
407 .collect();
408 }
409 _ => {}
410 }
411 }
412 let child = self.intern(node, name);
414 self.touched(child);
415 let nodes = self.nodes.borrow();
416 match &*nodes[child.0 as usize].kind.borrow() {
417 Some(Kind::Scalar(Value::Null)) | None => Vec::new(),
418 _ => vec![child],
419 }
420 }
421
422 fn traits(&self, node: NodeId) -> Vec<String> {
425 self.touched(node);
426 let nodes = self.nodes.borrow();
427 let t = match &*nodes[node.0 as usize].kind.borrow() {
428 Some(Kind::Container(_) | Kind::Opaque) => "object",
429 Some(Kind::Scalar(Value::Str(_))) => "string",
430 Some(Kind::Scalar(Value::Int(_) | Value::Float(_))) => "number",
431 Some(Kind::Scalar(Value::Bool(_))) => "boolean",
432 _ => "null",
433 };
434 vec![t.to_string()]
435 }
436
437 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
439 self.field(node, name)
440 }
441
442 fn default_value(&self, node: NodeId) -> Option<Value> {
445 self.touched(node);
446 let nodes = self.nodes.borrow();
447 match &*nodes[node.0 as usize].kind.borrow() {
448 Some(Kind::Scalar(v)) => Some(v.clone()),
449 _ => None,
450 }
451 }
452
453 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
457 match key {
458 "path" => Some(Value::Str(self.locator(node))),
459 "type" => Some(Value::Str(self.traits(node).remove(0))),
460 "length" => {
461 self.touched(node);
462 let nodes = self.nodes.borrow();
463 match &*nodes[node.0 as usize].kind.borrow() {
464 Some(Kind::Container(c)) => Some(Value::Int(c.len() as i64)),
465 Some(Kind::Scalar(Value::Str(s))) => Some(Value::Int(s.chars().count() as i64)),
466 _ => None,
467 }
468 }
469 _ => None,
470 }
471 }
472
473 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
478 let container = hint.or_else(|| self.refs.get(property).map(String::as_str))?;
479 let value = self.field(node, property)?;
480 let root_child = self.intern(NodeId(0), container);
481 let target = self.intern(root_child, &value.to_string());
482 self.touched(target);
483 let nodes = self.nodes.borrow();
484 match &*nodes[target.0 as usize].kind.borrow() {
485 Some(Kind::Scalar(Value::Null)) | None => None,
486 _ => Some(target),
487 }
488 }
489
490 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
495 let mut declared: Vec<(&String, &String)> = self.refs.iter().collect();
496 declared.sort();
497 let mut out = Vec::new();
498 for (field, target) in declared {
499 let root_child = self.intern(NodeId(0), target);
500 if let Some(elem_field) = field.strip_suffix("/*") {
501 let container = self.intern(node, elem_field);
502 for elem in self.children(container) {
503 let Some(v) = self.default_value(elem) else {
504 continue;
505 };
506 let t = self.intern(root_child, &v.to_string());
507 self.touched(t);
508 let nodes = self.nodes.borrow();
509 if !matches!(
510 &*nodes[t.0 as usize].kind.borrow(),
511 Some(Kind::Scalar(Value::Null)) | None
512 ) {
513 out.push((elem_field.to_string(), t));
514 }
515 }
516 } else if let Some(v) = self.field(node, field) {
517 let t = self.intern(root_child, &v.to_string());
518 self.touched(t);
519 let nodes = self.nodes.borrow();
520 if !matches!(
521 &*nodes[t.0 as usize].kind.borrow(),
522 Some(Kind::Scalar(Value::Null)) | None
523 ) {
524 out.push((field.clone(), t));
525 }
526 }
527 }
528 out
529 }
530}