Skip to main content

wa_rs_binary/
node.rs

1use crate::attrs::{AttrParser, AttrParserRef};
2use crate::jid::{Jid, JidRef};
3use std::borrow::Cow;
4
5/// An owned attribute value that can be either a string or a structured JID.
6/// This avoids string allocation for JID attributes by storing the JID directly,
7/// eliminating format/parse overhead when routing logic needs the JID.
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Debug, Clone, PartialEq)]
10pub enum NodeValue {
11    String(String),
12    Jid(Jid),
13}
14
15impl Default for NodeValue {
16    fn default() -> Self {
17        NodeValue::String(String::new())
18    }
19}
20
21impl NodeValue {
22    /// Get the value as a string slice, if it's a string variant.
23    #[inline]
24    pub fn as_str(&self) -> Option<&str> {
25        match self {
26            NodeValue::String(s) => Some(s.as_ref()),
27            NodeValue::Jid(_) => None,
28        }
29    }
30
31    /// Get the value as a Jid reference, if it's a JID variant.
32    #[inline]
33    pub fn as_jid(&self) -> Option<&Jid> {
34        match self {
35            NodeValue::Jid(j) => Some(j),
36            NodeValue::String(_) => None,
37        }
38    }
39
40    /// Convert to an owned Jid, parsing from string if necessary.
41    #[inline]
42    pub fn to_jid(&self) -> Option<Jid> {
43        match self {
44            NodeValue::Jid(j) => Some(j.clone()),
45            NodeValue::String(s) => s.parse().ok(),
46        }
47    }
48
49    /// Convert to a string, formatting the JID if necessary.
50    #[inline]
51    pub fn to_string_value(&self) -> String {
52        match self {
53            NodeValue::String(s) => s.clone(),
54            NodeValue::Jid(j) => j.to_string(),
55        }
56    }
57}
58
59use std::fmt;
60
61impl fmt::Display for NodeValue {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            NodeValue::String(s) => write!(f, "{}", s),
65            NodeValue::Jid(j) => write!(f, "{}", j),
66        }
67    }
68}
69
70impl PartialEq<str> for NodeValue {
71    fn eq(&self, other: &str) -> bool {
72        match self {
73            NodeValue::String(s) => s == other,
74            // For JID, format and compare. This is rare since JIDs are typically
75            // accessed via optional_jid() or to_jid(), not string comparison.
76            NodeValue::Jid(j) => j.to_string() == other,
77        }
78    }
79}
80
81impl PartialEq<&str> for NodeValue {
82    fn eq(&self, other: &&str) -> bool {
83        self == *other
84    }
85}
86
87impl PartialEq<String> for NodeValue {
88    fn eq(&self, other: &String) -> bool {
89        self == other.as_str()
90    }
91}
92
93impl From<String> for NodeValue {
94    #[inline]
95    fn from(s: String) -> Self {
96        NodeValue::String(s)
97    }
98}
99
100impl From<&str> for NodeValue {
101    #[inline]
102    fn from(s: &str) -> Self {
103        NodeValue::String(s.to_string())
104    }
105}
106
107impl From<Jid> for NodeValue {
108    #[inline]
109    fn from(jid: Jid) -> Self {
110        NodeValue::Jid(jid)
111    }
112}
113
114/// A collection of node attributes stored as key-value pairs.
115/// Uses a Vec internally for better cache locality with small attribute counts (typically 3-6).
116/// Values can be either strings or JIDs, avoiding stringification overhead for JID attributes.
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118#[derive(Debug, Clone, PartialEq, Default)]
119pub struct Attrs(pub Vec<(String, NodeValue)>);
120
121impl Attrs {
122    #[inline]
123    pub fn new() -> Self {
124        Self(Vec::new())
125    }
126
127    #[inline]
128    pub fn with_capacity(capacity: usize) -> Self {
129        Self(Vec::with_capacity(capacity))
130    }
131
132    /// Get a reference to the NodeValue for a key, or None if not found.
133    /// Uses linear search which is efficient for small attribute counts.
134    #[inline]
135    pub fn get(&self, key: &str) -> Option<&NodeValue> {
136        self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
137    }
138
139    /// Check if a key exists.
140    #[inline]
141    pub fn contains_key(&self, key: &str) -> bool {
142        self.0.iter().any(|(k, _)| k == key)
143    }
144
145    /// Insert a key-value pair. If the key already exists, update the value.
146    #[inline]
147    pub fn insert(&mut self, key: String, value: impl Into<NodeValue>) {
148        let value = value.into();
149        if let Some(pos) = self.0.iter().position(|(k, _)| k == &key) {
150            self.0[pos].1 = value;
151        } else {
152            self.0.push((key, value));
153        }
154    }
155
156    #[inline]
157    pub fn len(&self) -> usize {
158        self.0.len()
159    }
160
161    #[inline]
162    pub fn is_empty(&self) -> bool {
163        self.0.is_empty()
164    }
165
166    /// Iterate over key-value pairs.
167    #[inline]
168    pub fn iter(&self) -> impl Iterator<Item = (&String, &NodeValue)> {
169        self.0.iter().map(|(k, v)| (k, v))
170    }
171
172    /// Push a key-value pair without checking for duplicates.
173    /// Use this when building from a known-unique source (e.g., decoding).
174    #[inline]
175    pub fn push(&mut self, key: String, value: impl Into<NodeValue>) {
176        self.0.push((key, value.into()));
177    }
178
179    /// Push a NodeValue directly without conversion.
180    /// Slightly more efficient when you already have a NodeValue.
181    #[inline]
182    pub fn push_value(&mut self, key: String, value: NodeValue) {
183        self.0.push((key, value));
184    }
185
186    /// Iterate over keys only.
187    #[inline]
188    pub fn keys(&self) -> impl Iterator<Item = &String> {
189        self.0.iter().map(|(k, _)| k)
190    }
191}
192
193/// Owned iterator implementation (consuming).
194impl IntoIterator for Attrs {
195    type Item = (String, NodeValue);
196    type IntoIter = std::vec::IntoIter<(String, NodeValue)>;
197
198    fn into_iter(self) -> Self::IntoIter {
199        self.0.into_iter()
200    }
201}
202
203/// Borrowed iterator implementation.
204impl<'a> IntoIterator for &'a Attrs {
205    type Item = (&'a String, &'a NodeValue);
206    type IntoIter = std::iter::Map<
207        std::slice::Iter<'a, (String, NodeValue)>,
208        fn(&'a (String, NodeValue)) -> (&'a String, &'a NodeValue),
209    >;
210
211    fn into_iter(self) -> Self::IntoIter {
212        self.0.iter().map(|(k, v)| (k, v))
213    }
214}
215
216impl FromIterator<(String, NodeValue)> for Attrs {
217    fn from_iter<I: IntoIterator<Item = (String, NodeValue)>>(iter: I) -> Self {
218        Self(iter.into_iter().collect())
219    }
220}
221pub type AttrsRef<'a> = Vec<(Cow<'a, str>, ValueRef<'a>)>;
222
223/// A decoded attribute value that can be either a string or a structured JID.
224/// This avoids string allocation when decoding JID tokens - the JidRef is returned
225/// directly and only converted to a string when actually needed.
226#[derive(Debug, Clone, PartialEq)]
227pub enum ValueRef<'a> {
228    String(Cow<'a, str>),
229    Jid(JidRef<'a>),
230}
231
232impl<'a> ValueRef<'a> {
233    /// Get the value as a string slice, if it's a string variant.
234    pub fn as_str(&self) -> Option<&str> {
235        match self {
236            ValueRef::String(s) => Some(s.as_ref()),
237            ValueRef::Jid(_) => None,
238        }
239    }
240
241    /// Get the value as a JidRef, if it's a JID variant.
242    pub fn as_jid(&self) -> Option<&JidRef<'a>> {
243        match self {
244            ValueRef::Jid(j) => Some(j),
245            ValueRef::String(_) => None,
246        }
247    }
248
249    /// Convert to an owned Jid, parsing from string if necessary.
250    pub fn to_jid(&self) -> Option<Jid> {
251        match self {
252            ValueRef::Jid(j) => Some(j.to_owned()),
253            ValueRef::String(s) => Jid::from_str(s.as_ref()).ok(),
254        }
255    }
256
257    /// Convert to a string, formatting the JID if necessary.
258    /// Returns a Cow to avoid allocation when the value is already a string.
259    pub fn to_string_cow(&self) -> Cow<'a, str> {
260        match self {
261            ValueRef::String(s) => s.clone(),
262            ValueRef::Jid(j) => Cow::Owned(j.to_string()),
263        }
264    }
265}
266
267use std::str::FromStr;
268
269impl<'a> fmt::Display for ValueRef<'a> {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            ValueRef::String(s) => write!(f, "{}", s),
273            ValueRef::Jid(j) => write!(f, "{}", j),
274        }
275    }
276}
277
278pub type NodeVec<'a> = Vec<NodeRef<'a>>;
279
280#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
281#[derive(Debug, Clone, PartialEq)]
282pub enum NodeContent {
283    Bytes(Vec<u8>),
284    String(String),
285    Nodes(Vec<Node>),
286}
287
288#[derive(Debug, Clone, PartialEq)]
289pub enum NodeContentRef<'a> {
290    Bytes(Cow<'a, [u8]>),
291    String(Cow<'a, str>),
292    Nodes(Box<NodeVec<'a>>),
293}
294
295impl NodeContent {
296    /// Convert an owned NodeContent to a borrowed NodeContentRef.
297    pub fn as_content_ref(&self) -> NodeContentRef<'_> {
298        match self {
299            NodeContent::Bytes(b) => NodeContentRef::Bytes(Cow::Borrowed(b)),
300            NodeContent::String(s) => NodeContentRef::String(Cow::Borrowed(s)),
301            NodeContent::Nodes(nodes) => {
302                NodeContentRef::Nodes(Box::new(nodes.iter().map(|n| n.as_node_ref()).collect()))
303            }
304        }
305    }
306}
307
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309#[derive(Debug, Clone, PartialEq, Default)]
310pub struct Node {
311    pub tag: String,
312    pub attrs: Attrs,
313    pub content: Option<NodeContent>,
314}
315
316#[derive(Debug, Clone, PartialEq)]
317pub struct NodeRef<'a> {
318    pub tag: Cow<'a, str>,
319    pub attrs: AttrsRef<'a>,
320    pub content: Option<Box<NodeContentRef<'a>>>,
321}
322
323impl Node {
324    pub fn new(tag: &str, attrs: Attrs, content: Option<NodeContent>) -> Self {
325        Self {
326            tag: tag.to_string(),
327            attrs,
328            content,
329        }
330    }
331
332    /// Convert an owned Node to a borrowed NodeRef.
333    /// The returned NodeRef borrows from self.
334    pub fn as_node_ref(&self) -> NodeRef<'_> {
335        NodeRef {
336            tag: Cow::Borrowed(&self.tag),
337            attrs: self
338                .attrs
339                .iter()
340                .map(|(k, v)| {
341                    let value_ref = match v {
342                        NodeValue::String(s) => ValueRef::String(Cow::Borrowed(s.as_str())),
343                        NodeValue::Jid(j) => ValueRef::Jid(JidRef {
344                            user: Cow::Borrowed(&j.user),
345                            server: Cow::Borrowed(&j.server),
346                            agent: j.agent,
347                            device: j.device,
348                            integrator: j.integrator,
349                        }),
350                    };
351                    (Cow::Borrowed(k.as_str()), value_ref)
352                })
353                .collect(),
354            content: self.content.as_ref().map(|c| Box::new(c.as_content_ref())),
355        }
356    }
357
358    pub fn children(&self) -> Option<&[Node]> {
359        match &self.content {
360            Some(NodeContent::Nodes(nodes)) => Some(nodes),
361            _ => None,
362        }
363    }
364
365    pub fn attrs(&self) -> AttrParser<'_> {
366        AttrParser::new(self)
367    }
368
369    pub fn get_optional_child_by_tag<'a>(&'a self, tags: &[&str]) -> Option<&'a Node> {
370        let mut current_node = self;
371        for &tag in tags {
372            if let Some(children) = current_node.children() {
373                if let Some(found) = children.iter().find(|c| c.tag == tag) {
374                    current_node = found;
375                } else {
376                    return None;
377                }
378            } else {
379                return None;
380            }
381        }
382        Some(current_node)
383    }
384
385    pub fn get_children_by_tag<'a>(&'a self, tag: &'a str) -> impl Iterator<Item = &'a Node> {
386        self.children()
387            .into_iter()
388            .flatten()
389            .filter(move |c| c.tag == tag)
390    }
391
392    pub fn get_optional_child(&self, tag: &str) -> Option<&Node> {
393        self.children()
394            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
395    }
396}
397
398impl<'a> NodeRef<'a> {
399    pub fn new(
400        tag: Cow<'a, str>,
401        attrs: AttrsRef<'a>,
402        content: Option<NodeContentRef<'a>>,
403    ) -> Self {
404        Self {
405            tag,
406            attrs,
407            content: content.map(Box::new),
408        }
409    }
410
411    pub fn attr_parser(&'a self) -> AttrParserRef<'a> {
412        AttrParserRef::new(self)
413    }
414
415    pub fn children(&self) -> Option<&[NodeRef<'a>]> {
416        match self.content.as_deref() {
417            Some(NodeContentRef::Nodes(nodes)) => Some(nodes.as_slice()),
418            _ => None,
419        }
420    }
421
422    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'a>> {
423        self.attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
424    }
425
426    pub fn attrs_iter(&self) -> impl Iterator<Item = (&Cow<'a, str>, &ValueRef<'a>)> {
427        self.attrs.iter().map(|(k, v)| (k, v))
428    }
429
430    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'a>> {
431        let mut current_node = self;
432        for &tag in tags {
433            if let Some(children) = current_node.children() {
434                if let Some(found) = children.iter().find(|c| c.tag == tag) {
435                    current_node = found;
436                } else {
437                    return None;
438                }
439            } else {
440                return None;
441            }
442        }
443        Some(current_node)
444    }
445
446    pub fn get_children_by_tag<'b>(&'b self, tag: &'b str) -> impl Iterator<Item = &'b NodeRef<'a>>
447    where
448        'a: 'b,
449    {
450        self.children()
451            .into_iter()
452            .flatten()
453            .filter(move |c| c.tag == tag)
454    }
455
456    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'a>> {
457        self.children()
458            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
459    }
460
461    pub fn to_owned(&self) -> Node {
462        Node {
463            tag: self.tag.to_string(),
464            attrs: self
465                .attrs
466                .iter()
467                .map(|(k, v)| {
468                    let value = match v {
469                        ValueRef::String(s) => NodeValue::String(s.to_string()),
470                        ValueRef::Jid(j) => NodeValue::Jid(j.to_owned()),
471                    };
472                    (k.to_string(), value)
473                })
474                .collect::<Attrs>(),
475            content: self.content.as_deref().map(|c| match c {
476                NodeContentRef::Bytes(b) => NodeContent::Bytes(b.to_vec()),
477                NodeContentRef::String(s) => NodeContent::String(s.to_string()),
478                NodeContentRef::Nodes(nodes) => {
479                    NodeContent::Nodes(nodes.iter().map(|n| n.to_owned()).collect())
480                }
481            }),
482        }
483    }
484}