firebase_rs_sdk/util/
errors.rs

1use 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            // No closing brace, append the rest and break.
90            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}