firebase_rs_sdk/util/
errors.rs1use std::collections::{BTreeMap, HashMap};
2use std::fmt;
3
4pub type ErrorData = BTreeMap<String, String>;
5pub type ErrorMap = &'static [(&'static str, &'static str)];
6
7#[derive(Debug, Clone)]
8pub struct FirebaseError {
9 pub code: String,
10 pub message: String,
11 pub service: String,
12 pub service_name: String,
13 pub custom_data: ErrorData,
14}
15
16impl fmt::Display for FirebaseError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 write!(f, "{}", self.message)
19 }
20}
21
22impl std::error::Error for FirebaseError {}
23
24pub struct ErrorFactory {
25 service: String,
26 service_name: String,
27 errors: HashMap<&'static str, &'static str>,
28}
29
30impl ErrorFactory {
31 pub fn new(service: &'static str, service_name: &'static str, errors: ErrorMap) -> Self {
32 let errors_map = errors.iter().copied().collect();
33 Self {
34 service: service.to_string(),
35 service_name: service_name.to_string(),
36 errors: errors_map,
37 }
38 }
39
40 pub fn create(&self, code: &str) -> FirebaseError {
41 self.build_error(code, ErrorData::new())
42 }
43
44 pub fn create_with_data<I, K, V>(&self, code: &str, data: I) -> FirebaseError
45 where
46 I: IntoIterator<Item = (K, V)>,
47 K: Into<String>,
48 V: Into<String>,
49 {
50 let mut custom_data = ErrorData::new();
51 for (key, value) in data {
52 custom_data.insert(key.into(), value.into());
53 }
54 self.build_error(code, custom_data)
55 }
56
57 fn build_error(&self, code: &str, custom_data: ErrorData) -> FirebaseError {
58 let template = self.errors.get(code).copied().unwrap_or("Error");
59 let message = replace_template(template, &custom_data);
60 let full_code = format!("{}/{}", self.service, code);
61 let full_message = format!("{}: {} ({}).", self.service_name, message, full_code);
62
63 FirebaseError {
64 code: full_code,
65 message: full_message,
66 service: self.service.clone(),
67 service_name: self.service_name.clone(),
68 custom_data,
69 }
70 }
71}
72
73fn replace_template(template: &str, data: &ErrorData) -> String {
74 let mut result = String::with_capacity(template.len());
75 let mut remainder = template;
76
77 while let Some(start) = remainder.find("{$") {
78 let (head, tail) = remainder.split_at(start);
79 result.push_str(head);
80 if let Some(end) = tail.find('}') {
81 let key = &tail[2..end];
82 let value = data
83 .get(key)
84 .cloned()
85 .unwrap_or_else(|| format!("<{key}?>"));
86 result.push_str(&value);
87 remainder = &tail[end + 1..];
88 } else {
89 result.push_str(tail);
91 remainder = "";
92 break;
93 }
94 }
95
96 result.push_str(remainder);
97 result
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 const ERRORS: ErrorMap = &[
105 ("missing", "Missing field: {$field}"),
106 ("unknown", "Unknown error"),
107 ];
108
109 #[test]
110 fn create_without_data_uses_template() {
111 let factory = ErrorFactory::new("service", "Service", ERRORS);
112 let error = factory.create("unknown");
113 assert_eq!(error.code, "service/unknown");
114 assert!(error.message.contains("Service"));
115 }
116
117 #[test]
118 fn create_with_data_replaces_placeholders() {
119 let factory = ErrorFactory::new("service", "Service", ERRORS);
120 let error = factory.create_with_data("missing", [("field", "name")]);
121 assert!(error.message.contains("name"));
122 assert_eq!(error.custom_data.get("field"), Some(&"name".to_string()));
123 }
124
125 #[test]
126 fn missing_placeholder_is_flagged() {
127 let factory = ErrorFactory::new("service", "Service", ERRORS);
128 let error = factory.create("missing");
129 assert!(error.message.contains("<field?>"));
130 }
131}