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