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
8fn coerce_protocol_bool(s: &str) -> Option<bool> {
13 match s {
14 "1" | "true" | "True" | "t" | "T" | "TRUE" => Some(true),
15 "0" | "false" | "False" | "f" | "F" | "FALSE" => Some(false),
16 _ => None,
17 }
18}
19
20pub struct AttrParser<'a> {
21 pub attrs: &'a Attrs,
22 pub errors: Vec<BinaryError>,
23}
24
25pub struct AttrParserRef<'a> {
26 pub(crate) attrs: &'a [(NodeStr<'a>, ValueRef<'a>)],
27 pub errors: Vec<BinaryError>,
28}
29
30impl<'a> AttrParserRef<'a> {
31 pub fn new(node: &'a NodeRef<'a>) -> Self {
32 Self {
33 attrs: node.attrs.as_slice(),
34 errors: Vec::new(),
35 }
36 }
37
38 pub fn ok(&self) -> bool {
39 self.errors.is_empty()
40 }
41
42 pub fn finish(&self) -> Result<()> {
43 if self.ok() {
44 Ok(())
45 } else {
46 Err(BinaryError::AttrList(self.errors.clone()))
47 }
48 }
49
50 fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a ValueRef<'a>> {
51 let val = self.attrs.iter().find(|(k, _)| **k == *key).map(|(_, v)| v);
52
53 if require && val.is_none() {
54 self.errors.push(BinaryError::AttrParse(format!(
55 "Required attribute '{key}' not found"
56 )));
57 }
58
59 val
60 }
61
62 pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
66 self.get_raw(key, false).map(|v| v.as_str())
67 }
68
69 pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
74 self.optional_string(key)
75 .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
76 }
77
78 pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
82 self.get_jid(key, false)
83 }
84
85 fn get_jid(&mut self, key: &str, require: bool) -> Option<Jid> {
90 self.get_raw(key, require).and_then(|v| match v.to_jid() {
91 Some(jid) => Some(jid),
92 None => {
93 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 optional_jid_result(&mut self, key: &str) -> Result<Option<Jid>> {
105 match self.get_raw(key, false) {
106 None => Ok(None),
107 Some(ValueRef::Jid(jid)) => Ok(Some(jid.to_owned())),
108 Some(ValueRef::String(value)) => {
109 Jid::from_str(value).map(Some).map_err(BinaryError::from)
110 }
111 }
112 }
113
114 pub fn required_jid(&mut self, key: &str) -> Result<Jid> {
119 self.optional_jid_result(key)?
120 .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
121 }
122
123 pub fn jid(&mut self, key: &str) -> Jid {
124 self.get_jid(key, true).unwrap_or_default()
125 }
126
127 pub fn non_ad_jid(&mut self, key: &str) -> Jid {
128 self.jid(key).to_non_ad()
129 }
130
131 fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
132 self.get_raw(key, require).map(|v| v.as_str())
133 }
134
135 fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
136 self.get_string_value(key, require)
137 .and_then(|s| match coerce_protocol_bool(&s) {
138 Some(val) => Some(val),
139 None => {
140 self.errors.push(BinaryError::AttrParse(format!(
141 "Failed to parse bool from '{s}' for key '{key}'"
142 )));
143 None
144 }
145 })
146 }
147
148 pub fn optional_bool_value(&mut self, key: &str) -> Option<bool> {
150 self.get_bool(key, false)
151 }
152
153 pub fn optional_bool(&mut self, key: &str) -> bool {
154 self.optional_bool_value(key).unwrap_or(false)
155 }
156
157 pub fn bool(&mut self, key: &str) -> bool {
158 self.get_bool(key, true).unwrap_or(false)
159 }
160
161 pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
162 self.get_string_value(key, false)
163 .and_then(|s| match s.parse::<u64>() {
164 Ok(val) => Some(val),
165 Err(e) => {
166 self.errors.push(BinaryError::AttrParse(format!(
167 "Failed to parse u64 from '{s}' for key '{key}': {e}"
168 )));
169 None
170 }
171 })
172 }
173
174 pub fn unix_time(&mut self, key: &str) -> i64 {
175 self.get_i64(key, true).unwrap_or_default()
176 }
177
178 pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
179 self.get_i64(key, false)
180 }
181
182 pub fn unix_milli(&mut self, key: &str) -> i64 {
183 self.get_i64(key, true).unwrap_or_default()
184 }
185
186 pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
187 self.get_i64(key, false)
188 }
189
190 fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
191 self.get_string_value(key, require)
192 .and_then(|s| match s.parse::<i64>() {
193 Ok(val) => Some(val),
194 Err(e) => {
195 self.errors.push(BinaryError::AttrParse(format!(
196 "Failed to parse i64 from '{s}' for key '{key}': {e}"
197 )));
198 None
199 }
200 })
201 }
202}
203
204impl<'a> AttrParser<'a> {
205 pub fn new(node: &'a Node) -> Self {
206 Self {
207 attrs: &node.attrs,
208 errors: Vec::new(),
209 }
210 }
211
212 pub fn ok(&self) -> bool {
213 self.errors.is_empty()
214 }
215
216 pub fn finish(&self) -> Result<()> {
217 if self.ok() {
218 Ok(())
219 } else {
220 Err(BinaryError::AttrList(self.errors.clone()))
221 }
222 }
223
224 fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a NodeValue> {
225 let val = self.attrs.get(key);
226 if require && val.is_none() {
227 self.errors.push(BinaryError::AttrParse(format!(
228 "Required attribute '{key}' not found"
229 )));
230 }
231 val
232 }
233
234 fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
236 self.get_raw(key, require).map(|v| match v {
237 NodeValue::String(s) => Cow::Borrowed(s.as_str()),
238 NodeValue::Jid(j) => Cow::Owned(j.to_string()),
239 })
240 }
241
242 pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
247 self.get_raw(key, false).map(|v| v.as_str())
248 }
249
250 pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
255 self.optional_string(key)
256 .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
257 }
258
259 pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
264 self.get_jid(key, false)
265 }
266
267 fn get_jid(&mut self, key: &str, require: bool) -> Option<Jid> {
272 self.get_raw(key, require).and_then(|v| match v {
273 NodeValue::Jid(j) => Some(j.clone()),
274 NodeValue::String(s) => match Jid::from_str(s) {
275 Ok(jid) => Some(jid),
276 Err(e) => {
277 self.errors.push(BinaryError::from(e));
278 None
279 }
280 },
281 })
282 }
283
284 pub fn required_jid(&mut self, key: &str) -> Result<Jid> {
286 match self
287 .get_raw(key, false)
288 .ok_or_else(|| BinaryError::MissingAttr(key.to_string()))?
289 {
290 NodeValue::Jid(jid) => Ok(jid.clone()),
291 NodeValue::String(value) => Jid::from_str(value).map_err(BinaryError::from),
292 }
293 }
294
295 pub fn jid(&mut self, key: &str) -> Jid {
296 self.get_jid(key, true).unwrap_or_default()
297 }
298
299 pub fn non_ad_jid(&mut self, key: &str) -> Jid {
300 self.jid(key).to_non_ad()
301 }
302
303 fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
305 self.get_string_value(key, require)
306 .and_then(|s| match coerce_protocol_bool(&s) {
307 Some(val) => Some(val),
308 None => {
309 self.errors.push(BinaryError::AttrParse(format!(
310 "Failed to parse bool from '{s}' for key '{key}'"
311 )));
312 None
313 }
314 })
315 }
316
317 pub fn optional_bool_value(&mut self, key: &str) -> Option<bool> {
319 self.get_bool(key, false)
320 }
321
322 pub fn optional_bool(&mut self, key: &str) -> bool {
323 self.optional_bool_value(key).unwrap_or(false)
324 }
325
326 pub fn bool(&mut self, key: &str) -> bool {
327 self.get_bool(key, true).unwrap_or(false)
328 }
329
330 pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
332 self.get_string_value(key, false)
333 .and_then(|s| match s.parse::<u64>() {
334 Ok(val) => Some(val),
335 Err(e) => {
336 self.errors.push(BinaryError::AttrParse(format!(
337 "Failed to parse u64 from '{s}' for key '{key}': {e}"
338 )));
339 None
340 }
341 })
342 }
343
344 pub fn unix_time(&mut self, key: &str) -> i64 {
345 self.get_i64(key, true).unwrap_or_default()
346 }
347
348 pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
349 self.get_i64(key, false)
350 }
351
352 pub fn unix_milli(&mut self, key: &str) -> i64 {
353 self.get_i64(key, true).unwrap_or_default()
354 }
355
356 pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
357 self.get_i64(key, false)
358 }
359
360 fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
361 self.get_string_value(key, require)
362 .and_then(|s| match s.parse::<i64>() {
363 Ok(val) => Some(val),
364 Err(e) => {
365 self.errors.push(BinaryError::AttrParse(format!(
366 "Failed to parse i64 from '{s}' for key '{key}': {e}"
367 )));
368 None
369 }
370 })
371 }
372}
373
374#[cfg(test)]
375mod required_getter_tests {
376 use super::*;
377 use crate::builder::NodeBuilder;
378
379 #[test]
385 fn required_getters_keep_reporting_missing_and_invalid_separately() {
386 let node = NodeBuilder::new("message")
387 .attr("from", "5511999998888@s.whatsapp.net")
388 .attr("bad_jid", "@@@")
389 .attr("t", "1700000000")
390 .attr("bad_t", "not-a-number")
391 .build();
392
393 let mut p = AttrParser::new(&node);
395 assert_eq!(p.jid("from").user, "5511999998888");
396 assert_eq!(p.unix_time("t"), 1700000000);
397 assert!(p.ok(), "clean parse should not record errors");
398
399 let mut p = AttrParser::new(&node);
401 assert_eq!(p.jid("nope"), Jid::default());
402 assert!(format!("{:?}", p.errors).contains("not found"));
403
404 let mut p = AttrParser::new(&node);
405 assert_eq!(p.unix_time("nope"), 0);
406 assert!(format!("{:?}", p.errors).contains("not found"));
407
408 let mut p = AttrParser::new(&node);
410 assert_eq!(p.jid("bad_jid"), Jid::default());
411 assert!(!p.ok());
412 assert!(!format!("{:?}", p.errors).contains("not found"));
413
414 let mut p = AttrParser::new(&node);
415 assert_eq!(p.unix_time("bad_t"), 0);
416 assert!(!p.ok());
417 assert!(!format!("{:?}", p.errors).contains("not found"));
418 }
419}
420
421#[cfg(test)]
422mod bool_coercion_tests {
423 use super::coerce_protocol_bool;
424
425 #[test]
426 fn coerce_protocol_bool_accepts_wire_forms() {
427 for t in ["1", "true", "True", "t", "T", "TRUE"] {
429 assert_eq!(coerce_protocol_bool(t), Some(true), "{t}");
430 }
431 for f in ["0", "false", "False", "f", "F", "FALSE"] {
432 assert_eq!(coerce_protocol_bool(f), Some(false), "{f}");
433 }
434 for bad in ["", "yes", "no", "2", "on", "off"] {
436 assert_eq!(coerce_protocol_bool(bad), None, "{bad}");
437 }
438 }
439}