use std::borrow::Cow;
use std::str::FromStr;
use crate::error::{BinaryError, Result};
use crate::jid::Jid;
use crate::node::{Attrs, Node, NodeRef, NodeStr, NodeValue, ValueRef};
fn coerce_protocol_bool(s: &str) -> Option<bool> {
match s {
"1" | "true" | "True" | "t" | "T" | "TRUE" => Some(true),
"0" | "false" | "False" | "f" | "F" | "FALSE" => Some(false),
_ => None,
}
}
pub struct AttrParser<'a> {
pub attrs: &'a Attrs,
pub errors: Vec<BinaryError>,
}
pub struct AttrParserRef<'a> {
pub(crate) attrs: &'a [(NodeStr<'a>, ValueRef<'a>)],
pub errors: Vec<BinaryError>,
}
impl<'a> AttrParserRef<'a> {
pub fn new(node: &'a NodeRef<'a>) -> Self {
Self {
attrs: node.attrs.as_slice(),
errors: Vec::new(),
}
}
pub fn ok(&self) -> bool {
self.errors.is_empty()
}
pub fn finish(&self) -> Result<()> {
if self.ok() {
Ok(())
} else {
Err(BinaryError::AttrList(self.errors.clone()))
}
}
fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a ValueRef<'a>> {
let val = self.attrs.iter().find(|(k, _)| **k == *key).map(|(_, v)| v);
if require && val.is_none() {
self.errors.push(BinaryError::AttrParse(format!(
"Required attribute '{key}' not found"
)));
}
val
}
pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
self.get_raw(key, false).map(|v| v.as_str())
}
pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
self.optional_string(key)
.ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
}
pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
self.get_jid(key, false)
}
fn get_jid(&mut self, key: &str, require: bool) -> Option<Jid> {
self.get_raw(key, require).and_then(|v| match v.to_jid() {
Some(jid) => Some(jid),
None => {
if let ValueRef::String(s) = v {
self.errors
.push(BinaryError::AttrParse(format!("Invalid JID: {s}")));
}
None
}
})
}
pub fn optional_jid_result(&mut self, key: &str) -> Result<Option<Jid>> {
match self.get_raw(key, false) {
None => Ok(None),
Some(ValueRef::Jid(jid)) => Ok(Some(jid.to_owned())),
Some(ValueRef::String(value)) => {
Jid::from_str(value).map(Some).map_err(BinaryError::from)
}
}
}
pub fn required_jid(&mut self, key: &str) -> Result<Jid> {
self.optional_jid_result(key)?
.ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
}
pub fn jid(&mut self, key: &str) -> Jid {
self.get_jid(key, true).unwrap_or_default()
}
pub fn non_ad_jid(&mut self, key: &str) -> Jid {
self.jid(key).to_non_ad()
}
fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
self.get_raw(key, require).map(|v| v.as_str())
}
fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
self.get_string_value(key, require)
.and_then(|s| match coerce_protocol_bool(&s) {
Some(val) => Some(val),
None => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse bool from '{s}' for key '{key}'"
)));
None
}
})
}
pub fn optional_bool_value(&mut self, key: &str) -> Option<bool> {
self.get_bool(key, false)
}
pub fn optional_bool(&mut self, key: &str) -> bool {
self.optional_bool_value(key).unwrap_or(false)
}
pub fn bool(&mut self, key: &str) -> bool {
self.get_bool(key, true).unwrap_or(false)
}
pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
self.get_string_value(key, false)
.and_then(|s| match s.parse::<u64>() {
Ok(val) => Some(val),
Err(e) => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse u64 from '{s}' for key '{key}': {e}"
)));
None
}
})
}
pub fn unix_time(&mut self, key: &str) -> i64 {
self.get_i64(key, true).unwrap_or_default()
}
pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
self.get_i64(key, false)
}
pub fn unix_milli(&mut self, key: &str) -> i64 {
self.get_i64(key, true).unwrap_or_default()
}
pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
self.get_i64(key, false)
}
fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
self.get_string_value(key, require)
.and_then(|s| match s.parse::<i64>() {
Ok(val) => Some(val),
Err(e) => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse i64 from '{s}' for key '{key}': {e}"
)));
None
}
})
}
}
impl<'a> AttrParser<'a> {
pub fn new(node: &'a Node) -> Self {
Self {
attrs: &node.attrs,
errors: Vec::new(),
}
}
pub fn ok(&self) -> bool {
self.errors.is_empty()
}
pub fn finish(&self) -> Result<()> {
if self.ok() {
Ok(())
} else {
Err(BinaryError::AttrList(self.errors.clone()))
}
}
fn get_raw(&mut self, key: &str, require: bool) -> Option<&'a NodeValue> {
let val = self.attrs.get(key);
if require && val.is_none() {
self.errors.push(BinaryError::AttrParse(format!(
"Required attribute '{key}' not found"
)));
}
val
}
fn get_string_value(&mut self, key: &str, require: bool) -> Option<Cow<'a, str>> {
self.get_raw(key, require).map(|v| match v {
NodeValue::String(s) => Cow::Borrowed(s.as_str()),
NodeValue::Jid(j) => Cow::Owned(j.to_string()),
})
}
pub fn optional_string(&mut self, key: &str) -> Option<Cow<'a, str>> {
self.get_raw(key, false).map(|v| v.as_str())
}
pub fn required_string(&mut self, key: &str) -> Result<Cow<'a, str>> {
self.optional_string(key)
.ok_or_else(|| BinaryError::MissingAttr(key.to_string()))
}
pub fn optional_jid(&mut self, key: &str) -> Option<Jid> {
self.get_jid(key, false)
}
fn get_jid(&mut self, key: &str, require: bool) -> Option<Jid> {
self.get_raw(key, require).and_then(|v| match v {
NodeValue::Jid(j) => Some(j.clone()),
NodeValue::String(s) => match Jid::from_str(s) {
Ok(jid) => Some(jid),
Err(e) => {
self.errors.push(BinaryError::from(e));
None
}
},
})
}
pub fn required_jid(&mut self, key: &str) -> Result<Jid> {
match self
.get_raw(key, false)
.ok_or_else(|| BinaryError::MissingAttr(key.to_string()))?
{
NodeValue::Jid(jid) => Ok(jid.clone()),
NodeValue::String(value) => Jid::from_str(value).map_err(BinaryError::from),
}
}
pub fn jid(&mut self, key: &str) -> Jid {
self.get_jid(key, true).unwrap_or_default()
}
pub fn non_ad_jid(&mut self, key: &str) -> Jid {
self.jid(key).to_non_ad()
}
fn get_bool(&mut self, key: &str, require: bool) -> Option<bool> {
self.get_string_value(key, require)
.and_then(|s| match coerce_protocol_bool(&s) {
Some(val) => Some(val),
None => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse bool from '{s}' for key '{key}'"
)));
None
}
})
}
pub fn optional_bool_value(&mut self, key: &str) -> Option<bool> {
self.get_bool(key, false)
}
pub fn optional_bool(&mut self, key: &str) -> bool {
self.optional_bool_value(key).unwrap_or(false)
}
pub fn bool(&mut self, key: &str) -> bool {
self.get_bool(key, true).unwrap_or(false)
}
pub fn optional_u64(&mut self, key: &str) -> Option<u64> {
self.get_string_value(key, false)
.and_then(|s| match s.parse::<u64>() {
Ok(val) => Some(val),
Err(e) => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse u64 from '{s}' for key '{key}': {e}"
)));
None
}
})
}
pub fn unix_time(&mut self, key: &str) -> i64 {
self.get_i64(key, true).unwrap_or_default()
}
pub fn optional_unix_time(&mut self, key: &str) -> Option<i64> {
self.get_i64(key, false)
}
pub fn unix_milli(&mut self, key: &str) -> i64 {
self.get_i64(key, true).unwrap_or_default()
}
pub fn optional_unix_milli(&mut self, key: &str) -> Option<i64> {
self.get_i64(key, false)
}
fn get_i64(&mut self, key: &str, require: bool) -> Option<i64> {
self.get_string_value(key, require)
.and_then(|s| match s.parse::<i64>() {
Ok(val) => Some(val),
Err(e) => {
self.errors.push(BinaryError::AttrParse(format!(
"Failed to parse i64 from '{s}' for key '{key}': {e}"
)));
None
}
})
}
}
#[cfg(test)]
mod required_getter_tests {
use super::*;
use crate::builder::NodeBuilder;
#[test]
fn required_getters_keep_reporting_missing_and_invalid_separately() {
let node = NodeBuilder::new("message")
.attr("from", "5511999998888@s.whatsapp.net")
.attr("bad_jid", "@@@")
.attr("t", "1700000000")
.attr("bad_t", "not-a-number")
.build();
let mut p = AttrParser::new(&node);
assert_eq!(p.jid("from").user, "5511999998888");
assert_eq!(p.unix_time("t"), 1700000000);
assert!(p.ok(), "clean parse should not record errors");
let mut p = AttrParser::new(&node);
assert_eq!(p.jid("nope"), Jid::default());
assert!(format!("{:?}", p.errors).contains("not found"));
let mut p = AttrParser::new(&node);
assert_eq!(p.unix_time("nope"), 0);
assert!(format!("{:?}", p.errors).contains("not found"));
let mut p = AttrParser::new(&node);
assert_eq!(p.jid("bad_jid"), Jid::default());
assert!(!p.ok());
assert!(!format!("{:?}", p.errors).contains("not found"));
let mut p = AttrParser::new(&node);
assert_eq!(p.unix_time("bad_t"), 0);
assert!(!p.ok());
assert!(!format!("{:?}", p.errors).contains("not found"));
}
}
#[cfg(test)]
mod bool_coercion_tests {
use super::coerce_protocol_bool;
#[test]
fn coerce_protocol_bool_accepts_wire_forms() {
for t in ["1", "true", "True", "t", "T", "TRUE"] {
assert_eq!(coerce_protocol_bool(t), Some(true), "{t}");
}
for f in ["0", "false", "False", "f", "F", "FALSE"] {
assert_eq!(coerce_protocol_bool(f), Some(false), "{f}");
}
for bad in ["", "yes", "no", "2", "on", "off"] {
assert_eq!(coerce_protocol_bool(bad), None, "{bad}");
}
}
}