soblendwa 0.1.0

SoblendWA: a blazing fast WhatsApp Web multi-device client library written in Rust. Crafted by Soblend Development Studio.
Documentation
use std::borrow::Cow;
use std::fmt;

use crate::jid::Jid;

pub type Str = Cow<'static, str>;

#[derive(Clone, Debug, PartialEq)]
pub enum Value {
    Str(Str),
    Jid(Jid),
}

impl Value {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Value::Str(s) => Some(s),
            Value::Jid(_) => None,
        }
    }

    pub fn to_jid(&self) -> Option<Jid> {
        match self {
            Value::Jid(jid) => Some(jid.clone()),
            Value::Str(s) => s.parse().ok(),
        }
    }
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::Str(s) => f.write_str(s),
            Value::Jid(jid) => jid.fmt(f),
        }
    }
}

impl From<&'static str> for Value {
    fn from(value: &'static str) -> Self {
        Value::Str(Cow::Borrowed(value))
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Value::Str(Cow::Owned(value))
    }
}

impl From<&String> for Value {
    fn from(value: &String) -> Self {
        Value::Str(Cow::Owned(value.clone()))
    }
}

impl From<Str> for Value {
    fn from(value: Str) -> Self {
        Value::Str(value)
    }
}

impl From<&Value> for Value {
    fn from(value: &Value) -> Self {
        value.clone()
    }
}

impl From<Jid> for Value {
    fn from(value: Jid) -> Self {
        Value::Jid(value)
    }
}

impl From<&Jid> for Value {
    fn from(value: &Jid) -> Self {
        Value::Jid(value.clone())
    }
}

macro_rules! numeric_value {
    ($($ty:ty),*) => {
        $(impl From<$ty> for Value {
            fn from(value: $ty) -> Self {
                Value::Str(Cow::Owned(value.to_string()))
            }
        })*
    };
}

numeric_value!(u8, u16, u32, u64, usize, i32, i64);

#[derive(Clone, Debug, Default, PartialEq)]
pub enum Content {
    #[default]
    None,
    Bytes(Vec<u8>),
    Text(Str),
    Nodes(Vec<Node>),
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct Node {
    pub tag: Str,
    pub attrs: Vec<(Str, Value)>,
    pub content: Content,
}

impl Node {
    pub fn new(tag: impl Into<Str>) -> Self {
        Node { tag: tag.into(), attrs: Vec::new(), content: Content::None }
    }

    pub fn attr(mut self, key: impl Into<Str>, value: impl Into<Value>) -> Self {
        self.set_attr(key, value);
        self
    }

    pub fn attr_opt<V: Into<Value>>(self, key: impl Into<Str>, value: Option<V>) -> Self {
        match value {
            Some(value) => self.attr(key, value),
            None => self,
        }
    }

    pub fn set_attr(&mut self, key: impl Into<Str>, value: impl Into<Value>) {
        let key = key.into();
        let value = value.into();
        match self.attrs.iter_mut().find(|(k, _)| *k == key) {
            Some(slot) => slot.1 = value,
            None => self.attrs.push((key, value)),
        }
    }

    pub fn children(mut self, nodes: impl IntoIterator<Item = Node>) -> Self {
        self.content = Content::Nodes(nodes.into_iter().collect());
        self
    }

    pub fn child_node(self, node: Node) -> Self {
        self.children([node])
    }

    pub fn bytes(mut self, data: impl Into<Vec<u8>>) -> Self {
        self.content = Content::Bytes(data.into());
        self
    }

    pub fn text(mut self, text: impl Into<Str>) -> Self {
        self.content = Content::Text(text.into());
        self
    }

    pub fn push_child(&mut self, node: Node) {
        match &mut self.content {
            Content::Nodes(nodes) => nodes.push(node),
            _ => self.content = Content::Nodes(vec![node]),
        }
    }

    pub fn get(&self, key: &str) -> Option<&Value> {
        self.attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
    }

    pub fn has(&self, key: &str) -> bool {
        self.get(key).is_some()
    }

    pub fn attr_str(&self, key: &str) -> Option<&str> {
        self.get(key).and_then(Value::as_str)
    }

    pub fn attr_string(&self, key: &str) -> Option<String> {
        self.get(key).map(ToString::to_string)
    }

    pub fn attr_jid(&self, key: &str) -> Option<Jid> {
        self.get(key).and_then(Value::to_jid)
    }

    pub fn attr_u64(&self, key: &str) -> Option<u64> {
        self.attr_str(key).and_then(|v| v.parse().ok())
    }

    pub fn attr_i64(&self, key: &str) -> Option<i64> {
        self.attr_str(key).and_then(|v| v.parse().ok())
    }

    pub fn attr_is(&self, key: &str, expected: &str) -> bool {
        self.attr_str(key) == Some(expected)
    }

    pub fn nodes(&self) -> &[Node] {
        match &self.content {
            Content::Nodes(nodes) => nodes,
            _ => &[],
        }
    }

    pub fn into_nodes(self) -> Vec<Node> {
        match self.content {
            Content::Nodes(nodes) => nodes,
            _ => Vec::new(),
        }
    }

    pub fn child(&self, tag: &str) -> Option<&Node> {
        self.nodes().iter().find(|n| n.tag == tag)
    }

    pub fn children_by<'a>(&'a self, tag: &'a str) -> impl Iterator<Item = &'a Node> + 'a {
        self.nodes().iter().filter(move |n| n.tag == tag)
    }

    pub fn first_child(&self) -> Option<&Node> {
        self.nodes().first()
    }

    pub fn path(&self, tags: &[&str]) -> Option<&Node> {
        tags.iter().try_fold(self, |node, tag| node.child(tag))
    }

    pub fn content_bytes(&self) -> Option<&[u8]> {
        match &self.content {
            Content::Bytes(data) => Some(data),
            Content::Text(text) => Some(text.as_bytes()),
            _ => None,
        }
    }

    pub fn content_string(&self) -> Option<String> {
        self.content_bytes().map(|b| String::from_utf8_lossy(b).into_owned())
    }

    pub fn child_bytes(&self, tag: &str) -> Option<&[u8]> {
        self.child(tag).and_then(Node::content_bytes)
    }

    pub fn child_uint(&self, tag: &str) -> Option<u32> {
        self.child_bytes(tag).map(|b| b.iter().fold(0u32, |acc, byte| (acc << 8) | u32::from(*byte)))
    }

    pub fn error_code(&self) -> Option<(u16, String)> {
        let error = self.child("error")?;
        let code = error.attr_str("code").and_then(|c| c.parse().ok()).unwrap_or(0);
        let text = error.attr_str("text").unwrap_or_default().to_owned();
        Some((code, text))
    }
}

impl fmt::Display for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write_xml(self, f, 0)
    }
}

fn write_xml(node: &Node, f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result {
    let pad = "  ".repeat(depth);
    write!(f, "{pad}<{}", node.tag)?;
    for (key, value) in &node.attrs {
        write!(f, " {key}=\"{value}\"")?;
    }
    match &node.content {
        Content::None => writeln!(f, "/>"),
        Content::Text(text) => writeln!(f, ">{text}</{}>", node.tag),
        Content::Bytes(data) => match std::str::from_utf8(data) {
            Ok(text) if text.chars().all(|c| !c.is_control()) => writeln!(f, ">{text}</{}>", node.tag),
            _ => writeln!(
                f,
                "><!-- {} bytes: {} --></{}>",
                data.len(),
                hex::encode(&data[..data.len().min(32)]),
                node.tag
            ),
        },
        Content::Nodes(children) => {
            writeln!(f, ">")?;
            for child in children {
                write_xml(child, f, depth + 1)?;
            }
            writeln!(f, "{pad}</{}>", node.tag)
        }
    }
}