Skip to main content

wacore_binary/
attrs.rs

1use std::borrow::Cow;
2use std::str::FromStr;
3
4use crate::error::{BinaryError, Result};
5use crate::jid::Jid;
6use crate::node::{Attrs, Node, NodeRef, NodeValue, ValueRef};
7
8pub struct AttrParser<'a> {
9    pub attrs: &'a Attrs,
10    pub errors: Vec<BinaryError>,
11}
12
13pub struct AttrParserRef<'a> {
14    pub attrs: &'a [(Cow<'a, str>, ValueRef<'a>)],
15    pub errors: Vec<BinaryError>,
16}
17
18impl<'a> AttrParserRef<'a> {
19    pub fn new(node: &'a NodeRef<'a>) -> Self {
20        Self {
21            attrs: node.attrs.as_slice(),
22            errors: Vec::new(),
23        }
24    }
25
26    pub fn ok(&self) -> bool {
27        self.errors.is_empty()
28    }
29
30    pub fn finish(&self) -> Result<()> {
31        if self.ok() {
32            Ok(())
33        } else {
34            Err(BinaryError::AttrList(self.errors.clone()))
35        }
36    }
37
38    fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a ValueRef<'a>> {
39        let val = self
40            .attrs
41            .iter()
42            .find(|(k, _)| k.as_ref() == key)
43            .map(|(_, v)| v);
44
45        if require && val.is_none() {
46            self.errors.push(BinaryError::AttrParse(format!(
47                "Required attribute '{key}' not found"
48            )));
49        }
50
51        val
52    }
53
54    /// Get string from the value.
55    /// For JID values, this returns None - use optional_jid instead.
56    pub fn optional_string(&mut self, key: &str) -> Option<&'a str> {
57        self.get_raw(key, false).and_then(|v| v.as_str())
58    }
59
60    /// Get a required string attribute, returning an error if missing.
61    ///
62    /// Prefer this over `string()` for required attributes as it makes
63    /// the error explicit rather than silently defaulting to empty string.
64    pub fn required_string(&mut self, key: &str) -> Result<&'a str> {
65        self.optional_string(key)
66            .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
67    }
68
69    /// Get string, defaulting to empty string if missing.
70    ///
71    /// # Deprecation
72    ///
73    /// This method silently defaults to an empty string when the attribute is missing.
74    /// Use `optional_string()` with explicit error handling or `required_string()`
75    /// to avoid silent failures.
76    #[deprecated(
77        since = "0.2.0",
78        note = "Use optional_string() with explicit handling or required_string() instead"
79    )]
80    pub fn string(&mut self, key: &str) -> String {
81        self.get_raw(key, true)
82            .map(|v| v.to_string_cow().into_owned())
83            .unwrap_or_default()
84    }
85
86    /// Get JID from the value.
87    /// If the value is a JidRef, returns it directly without parsing (zero allocation).
88    /// If the value is a string, parses it as a JID.
89    pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
90        self.get_raw(key, false).and_then(|v| match v.to_jid() {
91            Some(jid) => Some(jid),
92            None => {
93                // to_jid() only returns None if it's a String that failed to parse
94                if let ValueRef::String(s) = v {
95                    self.errors
96                        .push(BinaryError::AttrParse(format!("Invalid JID: {s}")));
97                }
98                None
99            }
100        })
101    }
102
103    pub fn jid(&mut self, key: &str) -> Jid {
104        self.get_raw(key, true);
105        self.optional_jid(key).unwrap_or_default()
106    }
107
108    pub fn non_ad_jid(&mut self, key: &str) -> Jid {
109        self.jid(key).to_non_ad()
110    }
111
112    fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
113        self.get_raw(key, require).map(|v| v.to_string_cow())
114    }
115
116    fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
117        self.get_string_value(key, require)
118            .and_then(|s| match s.parse::<bool>() {
119                Ok(val) => Some(val),
120                Err(e) => {
121                    self.errors.push(BinaryError::AttrParse(format!(
122                        "Failed to parse bool from '{s}' for key '{key}': {e}"
123                    )));
124                    None
125                }
126            })
127    }
128
129    pub fn optional_bool(&mut self, key: &str) -> bool {
130        self.get_bool(key, false).unwrap_or(false)
131    }
132
133    pub fn bool(&mut self, key: &str) -> bool {
134        self.get_bool(key, true).unwrap_or(false)
135    }
136
137    pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
138        self.get_string_value(key, false)
139            .and_then(|s| match s.parse::<u64>() {
140                Ok(val) => Some(val),
141                Err(e) => {
142                    self.errors.push(BinaryError::AttrParse(format!(
143                        "Failed to parse u64 from '{s}' for key '{key}': {e}"
144                    )));
145                    None
146                }
147            })
148    }
149
150    pub fn unix_time(&mut self, key: &str) -> i64 {
151        self.get_raw(key, true);
152        self.optional_unix_time(key).unwrap_or_default()
153    }
154
155    pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
156        self.get_i64(key, false)
157    }
158
159    pub fn unix_milli(&mut self, key: &str) -> i64 {
160        self.get_raw(key, true);
161        self.optional_unix_milli(key).unwrap_or_default()
162    }
163
164    pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
165        self.get_i64(key, false)
166    }
167
168    fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
169        self.get_string_value(key, require)
170            .and_then(|s| match s.parse::<i64>() {
171                Ok(val) => Some(val),
172                Err(e) => {
173                    self.errors.push(BinaryError::AttrParse(format!(
174                        "Failed to parse i64 from '{s}' for key '{key}': {e}"
175                    )));
176                    None
177                }
178            })
179    }
180}
181
182impl<'a> AttrParser<'a> {
183    pub fn new(node: &'a Node) -> Self {
184        Self {
185            attrs: &node.attrs,
186            errors: Vec::new(),
187        }
188    }
189
190    pub fn ok(&self) -> bool {
191        self.errors.is_empty()
192    }
193
194    pub fn finish(&self) -> Result<()> {
195        if self.ok() {
196            Ok(())
197        } else {
198            Err(BinaryError::AttrList(self.errors.clone()))
199        }
200    }
201
202    fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a NodeValue> {
203        let val = self.attrs.get(key);
204        if require && val.is_none() {
205            self.errors.push(BinaryError::AttrParse(format!(
206                "Required attribute '{key}' not found"
207            )));
208        }
209        val
210    }
211
212    /// Get the string representation of the value (for numeric parsing, etc.)
213    fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
214        self.get_raw(key, require).map(|v| match v {
215            NodeValue::String(s) => Cow::Borrowed(s.as_str()),
216            NodeValue::Jid(j) => Cow::Owned(j.to_string()),
217        })
218    }
219
220    // --- String ---
221    pub fn optional_string(&mut self, key: &str) -> Option<&'a str> {
222        self.get_raw(key, false).and_then(|v| v.as_str())
223    }
224
225    /// Get a required string attribute, returning an error if missing.
226    ///
227    /// Prefer this over `string()` for required attributes as it makes
228    /// the error explicit rather than silently defaulting to empty string.
229    pub fn required_string(&mut self, key: &str) -> Result<&'a str> {
230        self.optional_string(key)
231            .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
232    }
233
234    /// Get string, defaulting to empty string if missing.
235    ///
236    /// # Deprecation
237    ///
238    /// This method silently defaults to an empty string when the attribute is missing.
239    /// Use `optional_string()` with explicit error handling or `required_string()`
240    /// to avoid silent failures.
241    #[deprecated(
242        since = "0.2.0",
243        note = "Use optional_string() with explicit handling or required_string() instead"
244    )]
245    pub fn string(&mut self, key: &str) -> String {
246        self.get_raw(key, true)
247            .map(|v| v.to_string_value())
248            .unwrap_or_default()
249    }
250
251    // --- JID ---
252    /// Get JID from the value.
253    /// If the value is a JID variant, returns it directly without parsing (zero allocation clone).
254    /// If the value is a string, parses it as a JID.
255    pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
256        self.get_raw(key, false).and_then(|v| match v {
257            NodeValue::Jid(j) => Some(j.clone()),
258            NodeValue::String(s) => match Jid::from_str(s) {
259                Ok(jid) => Some(jid),
260                Err(e) => {
261                    self.errors.push(BinaryError::from(e));
262                    None
263                }
264            },
265        })
266    }
267
268    pub fn jid(&mut self, key: &str) -> Jid {
269        self.get_raw(key, true); // Push "not found" error if needed.
270        self.optional_jid(key).unwrap_or_default()
271    }
272
273    pub fn non_ad_jid(&mut self, key: &str) -> Jid {
274        self.jid(key).to_non_ad()
275    }
276
277    // --- Boolean ---
278    fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
279        self.get_string_value(key, require)
280            .and_then(|s| match s.parse::<bool>() {
281                Ok(val) => Some(val),
282                Err(e) => {
283                    self.errors.push(BinaryError::AttrParse(format!(
284                        "Failed to parse bool from '{s}' for key '{key}': {e}"
285                    )));
286                    None
287                }
288            })
289    }
290
291    pub fn optional_bool(&mut self, key: &str) -> bool {
292        self.get_bool(key, false).unwrap_or(false)
293    }
294
295    pub fn bool(&mut self, key: &str) -> bool {
296        self.get_bool(key, true).unwrap_or(false)
297    }
298
299    // --- u64 ---
300    pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
301        self.get_string_value(key, false)
302            .and_then(|s| match s.parse::<u64>() {
303                Ok(val) => Some(val),
304                Err(e) => {
305                    self.errors.push(BinaryError::AttrParse(format!(
306                        "Failed to parse u64 from '{s}' for key '{key}': {e}"
307                    )));
308                    None
309                }
310            })
311    }
312
313    pub fn unix_time(&mut self, key: &str) -> i64 {
314        self.get_raw(key, true);
315        self.optional_unix_time(key).unwrap_or_default()
316    }
317
318    pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
319        self.get_i64(key, false)
320    }
321
322    pub fn unix_milli(&mut self, key: &str) -> i64 {
323        self.get_raw(key, true);
324        self.optional_unix_milli(key).unwrap_or_default()
325    }
326
327    pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
328        self.get_i64(key, false)
329    }
330
331    fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
332        self.get_string_value(key, require)
333            .and_then(|s| match s.parse::<i64>() {
334                Ok(val) => Some(val),
335                Err(e) => {
336                    self.errors.push(BinaryError::AttrParse(format!(
337                        "Failed to parse i64 from '{s}' for key '{key}': {e}"
338                    )));
339                    None
340                }
341            })
342    }
343}