Skip to main content

wacore_binary/
node.rs

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