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. Works for both String and JID variants.
55    /// - String variant: Cow::Borrowed — zero copy
56    /// - JID variant: Cow::Owned — allocates only when needed
57    pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
58        self.get_raw(key, false).map(|v| v.to_string_cow())
59    }
60
61    /// Get a required string attribute, returning an error if missing.
62    ///
63    /// Prefer this over `string()` for required attributes as it makes
64    /// the error explicit rather than silently defaulting to empty string.
65    pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
66        self.optional_string(key)
67            .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
68    }
69
70    /// Get JID from the value.
71    /// If the value is a JidRef, returns it directly without parsing (zero allocation).
72    /// If the value is a string, parses it as a JID.
73    pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
74        self.get_raw(key, false).and_then(|v| match v.to_jid() {
75            Some(jid) => Some(jid),
76            None => {
77                // to_jid() only returns None if it's a String that failed to parse
78                if let ValueRef::String(s) = v {
79                    self.errors
80                        .push(BinaryError::AttrParse(format!("Invalid JID: {s}")));
81                }
82                None
83            }
84        })
85    }
86
87    pub fn jid(&mut self, key: &str) -> Jid {
88        self.get_raw(key, true);
89        self.optional_jid(key).unwrap_or_default()
90    }
91
92    pub fn non_ad_jid(&mut self, key: &str) -> Jid {
93        self.jid(key).to_non_ad()
94    }
95
96    fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
97        self.get_raw(key, require).map(|v| v.to_string_cow())
98    }
99
100    fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
101        self.get_string_value(key, require)
102            .and_then(|s| match s.parse::<bool>() {
103                Ok(val) => Some(val),
104                Err(e) => {
105                    self.errors.push(BinaryError::AttrParse(format!(
106                        "Failed to parse bool from '{s}' for key '{key}': {e}"
107                    )));
108                    None
109                }
110            })
111    }
112
113    pub fn optional_bool(&mut self, key: &str) -> bool {
114        self.get_bool(key, false).unwrap_or(false)
115    }
116
117    pub fn bool(&mut self, key: &str) -> bool {
118        self.get_bool(key, true).unwrap_or(false)
119    }
120
121    pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
122        self.get_string_value(key, false)
123            .and_then(|s| match s.parse::<u64>() {
124                Ok(val) => Some(val),
125                Err(e) => {
126                    self.errors.push(BinaryError::AttrParse(format!(
127                        "Failed to parse u64 from '{s}' for key '{key}': {e}"
128                    )));
129                    None
130                }
131            })
132    }
133
134    pub fn unix_time(&mut self, key: &str) -> i64 {
135        self.get_raw(key, true);
136        self.optional_unix_time(key).unwrap_or_default()
137    }
138
139    pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
140        self.get_i64(key, false)
141    }
142
143    pub fn unix_milli(&mut self, key: &str) -> i64 {
144        self.get_raw(key, true);
145        self.optional_unix_milli(key).unwrap_or_default()
146    }
147
148    pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
149        self.get_i64(key, false)
150    }
151
152    fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
153        self.get_string_value(key, require)
154            .and_then(|s| match s.parse::<i64>() {
155                Ok(val) => Some(val),
156                Err(e) => {
157                    self.errors.push(BinaryError::AttrParse(format!(
158                        "Failed to parse i64 from '{s}' for key '{key}': {e}"
159                    )));
160                    None
161                }
162            })
163    }
164}
165
166impl<'a> AttrParser<'a> {
167    pub fn new(node: &'a Node) -> Self {
168        Self {
169            attrs: &node.attrs,
170            errors: Vec::new(),
171        }
172    }
173
174    pub fn ok(&self) -> bool {
175        self.errors.is_empty()
176    }
177
178    pub fn finish(&self) -> Result<()> {
179        if self.ok() {
180            Ok(())
181        } else {
182            Err(BinaryError::AttrList(self.errors.clone()))
183        }
184    }
185
186    fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a NodeValue> {
187        let val = self.attrs.get(key);
188        if require && val.is_none() {
189            self.errors.push(BinaryError::AttrParse(format!(
190                "Required attribute '{key}' not found"
191            )));
192        }
193        val
194    }
195
196    /// Get the string representation of the value (for numeric parsing, etc.)
197    fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
198        self.get_raw(key, require).map(|v| match v {
199            NodeValue::String(s) => Cow::Borrowed(s.as_str()),
200            NodeValue::Jid(j) => Cow::Owned(j.to_string()),
201        })
202    }
203
204    // --- String ---
205    /// Get string from the value. Works for both String and JID variants.
206    /// - String variant: Cow::Borrowed — zero copy
207    /// - JID variant: Cow::Owned — allocates only when needed
208    pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
209        self.get_raw(key, false).map(|v| v.as_str())
210    }
211
212    /// Get a required string attribute, returning an error if missing.
213    ///
214    /// Prefer this over `string()` for required attributes as it makes
215    /// the error explicit rather than silently defaulting to empty string.
216    pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
217        self.optional_string(key)
218            .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
219    }
220
221    // --- JID ---
222    /// Get JID from the value.
223    /// If the value is a JID variant, returns it directly without parsing (zero allocation clone).
224    /// If the value is a string, parses it as a JID.
225    pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
226        self.get_raw(key, false).and_then(|v| match v {
227            NodeValue::Jid(j) => Some(j.clone()),
228            NodeValue::String(s) => match Jid::from_str(s) {
229                Ok(jid) => Some(jid),
230                Err(e) => {
231                    self.errors.push(BinaryError::from(e));
232                    None
233                }
234            },
235        })
236    }
237
238    pub fn jid(&mut self, key: &str) -> Jid {
239        self.get_raw(key, true); // Push "not found" error if needed.
240        self.optional_jid(key).unwrap_or_default()
241    }
242
243    pub fn non_ad_jid(&mut self, key: &str) -> Jid {
244        self.jid(key).to_non_ad()
245    }
246
247    // --- Boolean ---
248    fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
249        self.get_string_value(key, require)
250            .and_then(|s| match s.parse::<bool>() {
251                Ok(val) => Some(val),
252                Err(e) => {
253                    self.errors.push(BinaryError::AttrParse(format!(
254                        "Failed to parse bool from '{s}' for key '{key}': {e}"
255                    )));
256                    None
257                }
258            })
259    }
260
261    pub fn optional_bool(&mut self, key: &str) -> bool {
262        self.get_bool(key, false).unwrap_or(false)
263    }
264
265    pub fn bool(&mut self, key: &str) -> bool {
266        self.get_bool(key, true).unwrap_or(false)
267    }
268
269    // --- u64 ---
270    pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
271        self.get_string_value(key, false)
272            .and_then(|s| match s.parse::<u64>() {
273                Ok(val) => Some(val),
274                Err(e) => {
275                    self.errors.push(BinaryError::AttrParse(format!(
276                        "Failed to parse u64 from '{s}' for key '{key}': {e}"
277                    )));
278                    None
279                }
280            })
281    }
282
283    pub fn unix_time(&mut self, key: &str) -> i64 {
284        self.get_raw(key, true);
285        self.optional_unix_time(key).unwrap_or_default()
286    }
287
288    pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
289        self.get_i64(key, false)
290    }
291
292    pub fn unix_milli(&mut self, key: &str) -> i64 {
293        self.get_raw(key, true);
294        self.optional_unix_milli(key).unwrap_or_default()
295    }
296
297    pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
298        self.get_i64(key, false)
299    }
300
301    fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
302        self.get_string_value(key, require)
303            .and_then(|s| match s.parse::<i64>() {
304                Ok(val) => Some(val),
305                Err(e) => {
306                    self.errors.push(BinaryError::AttrParse(format!(
307                        "Failed to parse i64 from '{s}' for key '{key}': {e}"
308                    )));
309                    None
310                }
311            })
312    }
313}