Skip to main content

wacore_binary/
node.rs

1use crate::attrs::{AttrParser, AttrParserRef};
2use crate::jid::{Jid, JidRef};
3use crate::token;
4use bytes::Bytes;
5use compact_str::CompactString;
6use stable_deref_trait::StableDeref;
7use std::borrow::Cow;
8
9/// Borrowed-or-inline string for decoded nodes. Short owned values (≤24 bytes)
10/// are stored inline via `CompactString`, avoiding heap allocation.
11#[derive(Clone, yoke::Yokeable)]
12pub enum NodeStr<'a> {
13    Borrowed(&'a str),
14    Owned(CompactString),
15}
16
17impl NodeStr<'_> {
18    /// Clone-preserving conversion. Avoids re-parsing the inner CompactString
19    /// when converting owned NodeStr values in `to_owned()` paths.
20    #[inline]
21    pub fn to_compact_string(&self) -> CompactString {
22        match self {
23            NodeStr::Borrowed(s) => CompactString::from(*s),
24            NodeStr::Owned(cs) => cs.clone(),
25        }
26    }
27}
28
29impl Default for NodeStr<'_> {
30    #[inline]
31    fn default() -> Self {
32        NodeStr::Borrowed("")
33    }
34}
35
36impl std::ops::Deref for NodeStr<'_> {
37    type Target = str;
38    #[inline(always)]
39    fn deref(&self) -> &str {
40        match self {
41            NodeStr::Borrowed(s) => s,
42            NodeStr::Owned(cs) => cs.as_str(),
43        }
44    }
45}
46
47impl AsRef<str> for NodeStr<'_> {
48    #[inline(always)]
49    fn as_ref(&self) -> &str {
50        self
51    }
52}
53
54impl fmt::Debug for NodeStr<'_> {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        fmt::Debug::fmt(&**self, f)
57    }
58}
59
60impl fmt::Display for NodeStr<'_> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.write_str(self)
63    }
64}
65
66#[cfg(feature = "serde")]
67impl serde::Serialize for NodeStr<'_> {
68    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
69        serializer.serialize_str(self)
70    }
71}
72
73impl PartialEq for NodeStr<'_> {
74    #[inline]
75    fn eq(&self, other: &Self) -> bool {
76        **self == **other
77    }
78}
79
80impl Eq for NodeStr<'_> {}
81
82impl std::hash::Hash for NodeStr<'_> {
83    #[inline]
84    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
85        (**self).hash(state)
86    }
87}
88
89impl PartialEq<str> for NodeStr<'_> {
90    #[inline]
91    fn eq(&self, other: &str) -> bool {
92        &**self == other
93    }
94}
95
96impl PartialEq<&str> for NodeStr<'_> {
97    #[inline]
98    fn eq(&self, other: &&str) -> bool {
99        &**self == *other
100    }
101}
102
103impl<'a> From<&'a str> for NodeStr<'a> {
104    #[inline]
105    fn from(s: &'a str) -> Self {
106        NodeStr::Borrowed(s)
107    }
108}
109
110impl From<CompactString> for NodeStr<'_> {
111    #[inline]
112    fn from(s: CompactString) -> Self {
113        NodeStr::Owned(s)
114    }
115}
116
117/// Intern a string as a `Cow::Borrowed(&'static str)` if it matches a known token,
118/// otherwise allocate a `Cow::Owned(String)`. This avoids heap allocations for the
119/// vast majority of tag names and attribute keys which are protocol tokens.
120#[inline]
121fn intern_cow(s: &str) -> Cow<'static, str> {
122    if let Some(kind) = token::index_of_token(s) {
123        let interned = match kind {
124            token::TokenKind::Single(idx) => token::get_single_token(idx),
125            token::TokenKind::Double(dict, idx) => token::get_double_token(dict, idx),
126        };
127        if let Some(token) = interned {
128            return Cow::Borrowed(token);
129        }
130    }
131    Cow::Owned(s.to_string())
132}
133
134/// An owned attribute value that can be either a string or a structured JID.
135/// This avoids string allocation for JID attributes by storing the JID directly,
136/// eliminating format/parse overhead when routing logic needs the JID.
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138#[derive(Debug, Clone, PartialEq)]
139pub enum NodeValue {
140    String(CompactString),
141    Jid(Jid),
142}
143
144impl Default for NodeValue {
145    fn default() -> Self {
146        NodeValue::String(CompactString::default())
147    }
148}
149
150impl NodeValue {
151    /// String view of the value. Works for both variants.
152    /// - String variant: Cow::Borrowed(&str) — zero copy
153    /// - Jid variant: Cow::Owned(formatted) — allocates only when needed
154    #[inline]
155    pub fn as_str(&self) -> Cow<'_, str> {
156        match self {
157            NodeValue::String(s) => Cow::Borrowed(s.as_str()),
158            NodeValue::Jid(j) => Cow::Owned(j.to_string()),
159        }
160    }
161
162    /// Convert to an owned Jid, parsing from string if necessary.
163    #[inline]
164    pub fn to_jid(&self) -> Option<Jid> {
165        match self {
166            NodeValue::Jid(j) => Some(j.clone()),
167            NodeValue::String(s) => s.parse().ok(),
168        }
169    }
170}
171
172use std::fmt;
173
174impl fmt::Display for NodeValue {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            NodeValue::String(s) => write!(f, "{}", s),
178            NodeValue::Jid(j) => write!(f, "{}", j),
179        }
180    }
181}
182
183impl PartialEq<str> for NodeValue {
184    fn eq(&self, other: &str) -> bool {
185        match self {
186            NodeValue::String(s) => s == other,
187            NodeValue::Jid(j) => j.display_eq(other),
188        }
189    }
190}
191
192impl PartialEq<&str> for NodeValue {
193    fn eq(&self, other: &&str) -> bool {
194        self == *other
195    }
196}
197
198impl PartialEq<String> for NodeValue {
199    fn eq(&self, other: &String) -> bool {
200        self == other.as_str()
201    }
202}
203
204impl From<String> for NodeValue {
205    #[inline]
206    fn from(s: String) -> Self {
207        NodeValue::String(CompactString::from(s))
208    }
209}
210
211impl From<&str> for NodeValue {
212    #[inline]
213    fn from(s: &str) -> Self {
214        NodeValue::String(CompactString::from(s))
215    }
216}
217
218impl From<&String> for NodeValue {
219    #[inline]
220    fn from(s: &String) -> Self {
221        NodeValue::String(CompactString::from(s.as_str()))
222    }
223}
224
225impl From<CompactString> for NodeValue {
226    #[inline]
227    fn from(s: CompactString) -> Self {
228        NodeValue::String(s)
229    }
230}
231
232impl From<Jid> for NodeValue {
233    #[inline]
234    fn from(jid: Jid) -> Self {
235        NodeValue::Jid(jid)
236    }
237}
238
239impl From<&Jid> for NodeValue {
240    #[inline]
241    fn from(jid: &Jid) -> Self {
242        NodeValue::Jid(jid.clone())
243    }
244}
245
246macro_rules! impl_from_integer_for_nodevalue {
247    ($($t:ty),* $(,)?) => {
248        $(
249            impl From<$t> for NodeValue {
250                #[inline]
251                fn from(n: $t) -> Self {
252                    let mut buf = itoa::Buffer::new();
253                    NodeValue::String(CompactString::from(buf.format(n)))
254                }
255            }
256        )*
257    };
258}
259
260impl_from_integer_for_nodevalue!(
261    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
262);
263
264impl From<bool> for NodeValue {
265    #[inline]
266    fn from(b: bool) -> Self {
267        NodeValue::String(CompactString::from(if b { "true" } else { "false" }))
268    }
269}
270
271/// Inline backing store for [`Attrs`]. A plain `Vec` paid one heap allocation
272/// per node on the encode hot path just for the backing buffer. Capacity 2 is
273/// the measured sweet spot: the per-recipient fanout nodes (`to`, `enc`) carry
274/// 1-2 attributes and stay inline, while stanza roots with 3+ attrs spill once
275/// per stanza. A larger inline array (4) grows `Node` enough that moving it
276/// through children Vecs costs more than the spared spills save.
277pub type AttrsVec = smallvec::SmallVec<[(Cow<'static, str>, NodeValue); 2]>;
278
279/// A collection of node attributes stored as key-value pairs.
280/// Stored inline for small attribute counts (typically 3-6) for cache locality
281/// and to avoid a per-node heap allocation; see [`AttrsVec`].
282/// Values can be either strings or JIDs, avoiding stringification overhead for JID attributes.
283/// Keys use `Cow<'static, str>` to avoid heap allocation for compile-time-known strings
284/// (e.g., "type", "id", "to") which are the vast majority of attribute keys.
285#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
286#[derive(Debug, Clone, PartialEq, Default)]
287pub struct Attrs(pub AttrsVec);
288
289impl Attrs {
290    #[inline]
291    pub fn new() -> Self {
292        Self(AttrsVec::new())
293    }
294
295    #[inline]
296    pub fn with_capacity(capacity: usize) -> Self {
297        Self(AttrsVec::with_capacity(capacity))
298    }
299
300    /// Get a reference to the NodeValue for a key, or None if not found.
301    /// Uses linear search which is efficient for small attribute counts.
302    #[inline]
303    pub fn get(&self, key: &str) -> Option<&NodeValue> {
304        self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
305    }
306
307    /// Check if a key exists.
308    #[inline]
309    pub fn contains_key(&self, key: &str) -> bool {
310        self.0.iter().any(|(k, _)| k == key)
311    }
312
313    /// Insert a key-value pair. If the key already exists, update the value.
314    #[inline]
315    pub fn insert(&mut self, key: impl Into<Cow<'static, str>>, value: impl Into<NodeValue>) {
316        let key = key.into();
317        let value = value.into();
318        if let Some(pos) = self.0.iter().position(|(k, _)| k == &key) {
319            self.0[pos].1 = value;
320        } else {
321            self.0.push((key, value));
322        }
323    }
324
325    #[inline]
326    pub fn len(&self) -> usize {
327        self.0.len()
328    }
329
330    #[inline]
331    pub fn is_empty(&self) -> bool {
332        self.0.is_empty()
333    }
334
335    /// Iterate over key-value pairs.
336    #[inline]
337    pub fn iter(&self) -> impl Iterator<Item = (&Cow<'static, str>, &NodeValue)> {
338        self.0.iter().map(|(k, v)| (k, v))
339    }
340
341    /// Push a key-value pair without checking for duplicates.
342    /// Use this when building from a known-unique source (e.g., decoding).
343    #[inline]
344    pub fn push(&mut self, key: impl Into<Cow<'static, str>>, value: impl Into<NodeValue>) {
345        self.0.push((key.into(), value.into()));
346    }
347
348    /// Push a NodeValue directly without conversion.
349    /// Slightly more efficient when you already have a NodeValue.
350    #[inline]
351    pub fn push_value(&mut self, key: impl Into<Cow<'static, str>>, value: NodeValue) {
352        self.0.push((key.into(), value));
353    }
354
355    /// Iterate over keys only.
356    #[inline]
357    pub fn keys(&self) -> impl Iterator<Item = &Cow<'static, str>> {
358        self.0.iter().map(|(k, _)| k)
359    }
360}
361
362/// Owned iterator implementation (consuming).
363impl IntoIterator for Attrs {
364    type Item = (Cow<'static, str>, NodeValue);
365    type IntoIter = smallvec::IntoIter<[(Cow<'static, str>, NodeValue); 2]>;
366
367    fn into_iter(self) -> Self::IntoIter {
368        self.0.into_iter()
369    }
370}
371
372/// Borrowed iterator implementation.
373impl<'a> IntoIterator for &'a Attrs {
374    type Item = (&'a Cow<'static, str>, &'a NodeValue);
375    type IntoIter = std::iter::Map<
376        std::slice::Iter<'a, (Cow<'static, str>, NodeValue)>,
377        fn(&'a (Cow<'static, str>, NodeValue)) -> (&'a Cow<'static, str>, &'a NodeValue),
378    >;
379
380    fn into_iter(self) -> Self::IntoIter {
381        self.0.iter().map(|(k, v)| (k, v))
382    }
383}
384
385impl FromIterator<(Cow<'static, str>, NodeValue)> for Attrs {
386    fn from_iter<I: IntoIterator<Item = (Cow<'static, str>, NodeValue)>>(iter: I) -> Self {
387        Self(iter.into_iter().collect())
388    }
389}
390/// Covariant attribute container for decoded nodes.
391///
392/// Uses `Box<[T]>` (16 bytes: ptr + len) instead of `Vec<T>` (24 bytes: ptr + len + cap)
393/// or inline storage (which inflated NodeRef size). Zero-attr nodes skip allocation
394/// entirely. The boxed slice is allocated once with exact size from the decoder.
395///
396/// Covariant in `'a` (both Box and slices are covariant), compatible with yoke::Yokeable.
397#[derive(Debug, Clone)]
398pub enum AttrsRef<'a> {
399    Empty,
400    Slice(Box<[(NodeStr<'a>, ValueRef<'a>)]>),
401}
402
403impl PartialEq for AttrsRef<'_> {
404    fn eq(&self, other: &Self) -> bool {
405        self.as_slice() == other.as_slice()
406    }
407}
408
409impl<'a> AttrsRef<'a> {
410    /// Build from a pre-filled Vec. Preferred path from the decoder which
411    /// knows the exact attr count upfront.
412    pub fn from_vec(v: Vec<(NodeStr<'a>, ValueRef<'a>)>) -> Self {
413        if v.is_empty() {
414            Self::Empty
415        } else {
416            Self::Slice(v.into_boxed_slice())
417        }
418    }
419
420    #[inline]
421    pub fn len(&self) -> usize {
422        match self {
423            Self::Empty => 0,
424            Self::Slice(s) => s.len(),
425        }
426    }
427
428    #[inline]
429    pub fn is_empty(&self) -> bool {
430        self.as_slice().is_empty()
431    }
432
433    #[inline]
434    pub fn as_slice(&self) -> &[(NodeStr<'a>, ValueRef<'a>)] {
435        match self {
436            Self::Empty => &[],
437            Self::Slice(s) => s,
438        }
439    }
440
441    #[inline]
442    pub fn iter(&self) -> impl Iterator<Item = &(NodeStr<'a>, ValueRef<'a>)> {
443        self.as_slice().iter()
444    }
445}
446
447impl<'a> FromIterator<(NodeStr<'a>, ValueRef<'a>)> for AttrsRef<'a> {
448    fn from_iter<I: IntoIterator<Item = (NodeStr<'a>, ValueRef<'a>)>>(iter: I) -> Self {
449        Self::from_vec(iter.into_iter().collect())
450    }
451}
452
453// Compile-time covariance check: if AttrsRef ever becomes invariant
454// (e.g. by adding a Cell or &mut), this function will fail to compile.
455fn _assert_attrs_ref_covariant<'short, 'long: 'short>(x: AttrsRef<'long>) -> AttrsRef<'short> {
456    x
457}
458
459// Safety: AttrsRef<'a> is covariant in 'a because:
460// - Empty carries no lifetime
461// - Slice(Box<[(NodeStr<'a>, ValueRef<'a>)]>): Box<[T]> is covariant in T,
462//   and (NodeStr<'a>, ValueRef<'a>) is covariant in 'a
463// The _assert_attrs_ref_covariant function above enforces this at compile time.
464unsafe impl<'a> yoke::Yokeable<'a> for AttrsRef<'static> {
465    type Output = AttrsRef<'a>;
466
467    fn transform(&'a self) -> &'a Self::Output {
468        self
469    }
470
471    fn transform_owned(self) -> Self::Output {
472        self
473    }
474
475    unsafe fn make(from: Self::Output) -> Self {
476        unsafe { std::mem::transmute(from) }
477    }
478
479    fn transform_mut<F>(&'a mut self, f: F)
480    where
481        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
482    {
483        unsafe { f(std::mem::transmute::<&mut Self, &mut Self::Output>(self)) }
484    }
485}
486
487/// A decoded attribute value that can be either a string or a structured JID.
488/// This avoids string allocation when decoding JID tokens - the JidRef is returned
489/// directly and only converted to a string when actually needed.
490#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
491pub enum ValueRef<'a> {
492    String(NodeStr<'a>),
493    Jid(JidRef<'a>),
494}
495
496#[cfg(feature = "serde")]
497impl serde::Serialize for ValueRef<'_> {
498    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
499        match self {
500            ValueRef::String(s) => {
501                serializer.serialize_newtype_variant("NodeValue", 0, "String", &**s)
502            }
503            ValueRef::Jid(j) => serializer.serialize_newtype_variant("NodeValue", 1, "Jid", j),
504        }
505    }
506}
507
508impl<'a> ValueRef<'a> {
509    /// Encode this value directly to the binary encoder.
510    pub fn encode_value<W: crate::encoder::ByteWriter>(
511        &self,
512        encoder: &mut crate::encoder::Encoder<'_, W>,
513    ) -> crate::error::Result<()> {
514        match self {
515            ValueRef::String(s) => encoder.write_string(s),
516            ValueRef::Jid(jid) => encoder.write_jid_ref(jid),
517        }
518    }
519
520    /// String view of the value. Borrows from `self`.
521    /// - String variant: borrows the inner str — zero copy
522    /// - Jid variant: Cow::Owned — allocates only when needed
523    pub fn as_str(&self) -> Cow<'_, str> {
524        match self {
525            ValueRef::String(s) => Cow::Borrowed(s),
526            ValueRef::Jid(j) => Cow::Owned(j.to_string()),
527        }
528    }
529
530    /// Get the value as a JidRef, if it's a JID variant.
531    pub fn as_jid(&self) -> Option<&JidRef<'a>> {
532        match self {
533            ValueRef::Jid(j) => Some(j),
534            ValueRef::String(_) => None,
535        }
536    }
537
538    /// Convert to an owned Jid, parsing from string if necessary.
539    pub fn to_jid(&self) -> Option<Jid> {
540        match self {
541            ValueRef::Jid(j) => Some(j.to_owned()),
542            ValueRef::String(s) => Jid::from_str(s.as_ref()).ok(),
543        }
544    }
545
546    /// Convert to an owned NodeValue, preserving the variant (JID stays JID).
547    pub fn to_node_value(&self) -> NodeValue {
548        match self {
549            ValueRef::String(s) => NodeValue::String(s.to_compact_string()),
550            ValueRef::Jid(j) => NodeValue::Jid(j.to_owned()),
551        }
552    }
553}
554
555impl PartialEq<str> for ValueRef<'_> {
556    #[inline]
557    fn eq(&self, other: &str) -> bool {
558        match self {
559            ValueRef::String(value) => value == other,
560            ValueRef::Jid(value) => value.display_eq(other),
561        }
562    }
563}
564
565impl PartialEq<&str> for ValueRef<'_> {
566    #[inline]
567    fn eq(&self, other: &&str) -> bool {
568        self == *other
569    }
570}
571
572impl PartialEq<String> for ValueRef<'_> {
573    #[inline]
574    fn eq(&self, other: &String) -> bool {
575        self == other.as_str()
576    }
577}
578
579use std::str::FromStr;
580
581impl<'a> fmt::Display for ValueRef<'a> {
582    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583        match self {
584            ValueRef::String(s) => write!(f, "{}", s),
585            ValueRef::Jid(j) => write!(f, "{}", j),
586        }
587    }
588}
589
590pub type NodeVec<'a> = Vec<NodeRef<'a>>;
591
592#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
593#[derive(Debug, Clone, PartialEq)]
594pub enum NodeContent {
595    Bytes(Vec<u8>),
596    String(CompactString),
597    Nodes(Vec<Node>),
598}
599
600#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
601pub enum NodeContentRef<'a> {
602    Bytes(Cow<'a, [u8]>),
603    String(NodeStr<'a>),
604    Nodes(Box<[NodeRef<'a>]>),
605}
606
607#[cfg(feature = "serde")]
608impl serde::Serialize for NodeContentRef<'_> {
609    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
610        match self {
611            NodeContentRef::Bytes(b) => {
612                serializer.serialize_newtype_variant("NodeContent", 0, "Bytes", b.as_ref())
613            }
614            NodeContentRef::String(s) => {
615                serializer.serialize_newtype_variant("NodeContent", 1, "String", &**s)
616            }
617            NodeContentRef::Nodes(nodes) => {
618                serializer.serialize_newtype_variant("NodeContent", 2, "Nodes", &**nodes)
619            }
620        }
621    }
622}
623
624impl NodeContent {
625    /// Convert an owned NodeContent to a borrowed NodeContentRef.
626    pub fn as_content_ref(&self) -> NodeContentRef<'_> {
627        match self {
628            NodeContent::Bytes(b) => NodeContentRef::Bytes(Cow::Borrowed(b)),
629            NodeContent::String(s) => NodeContentRef::String(NodeStr::Borrowed(s.as_str())),
630            NodeContent::Nodes(nodes) => {
631                let v: Vec<_> = nodes.iter().map(|n| n.as_node_ref()).collect();
632                NodeContentRef::Nodes(v.into_boxed_slice())
633            }
634        }
635    }
636}
637
638#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
639#[derive(Debug, Clone, PartialEq, Default)]
640pub struct Node {
641    pub tag: Cow<'static, str>,
642    pub attrs: Attrs,
643    pub content: Option<NodeContent>,
644}
645
646#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
647pub struct NodeRef<'a> {
648    pub tag: NodeStr<'a>,
649    pub attrs: AttrsRef<'a>,
650    pub content: Option<NodeContentRef<'a>>,
651}
652
653impl Node {
654    pub fn new(
655        tag: impl Into<Cow<'static, str>>,
656        attrs: Attrs,
657        content: Option<NodeContent>,
658    ) -> Self {
659        Self {
660            tag: tag.into(),
661            attrs,
662            content,
663        }
664    }
665
666    /// Convert an owned Node to a borrowed NodeRef.
667    /// The returned NodeRef borrows from self.
668    pub fn as_node_ref(&self) -> NodeRef<'_> {
669        NodeRef {
670            tag: NodeStr::Borrowed(self.tag.as_ref()),
671            attrs: self
672                .attrs
673                .iter()
674                .map(|(k, v)| {
675                    let value_ref = match v {
676                        NodeValue::String(s) => ValueRef::String(NodeStr::Borrowed(s.as_str())),
677                        NodeValue::Jid(j) => ValueRef::Jid(JidRef {
678                            user: NodeStr::Borrowed(&j.user),
679                            server: j.server,
680                            agent: j.agent,
681                            device: j.device,
682                            integrator: j.integrator,
683                        }),
684                    };
685                    (NodeStr::Borrowed(k.as_ref()), value_ref)
686                })
687                .collect(),
688            content: self.content.as_ref().map(|c| c.as_content_ref()),
689        }
690    }
691
692    pub fn children(&self) -> Option<&[Node]> {
693        match &self.content {
694            Some(NodeContent::Nodes(nodes)) => Some(nodes),
695            _ => None,
696        }
697    }
698
699    pub fn attrs(&self) -> AttrParser<'_> {
700        AttrParser::new(self)
701    }
702
703    pub fn get_optional_child_by_tag<'a>(&'a self, tags: &[&str]) -> Option<&'a Node> {
704        let mut current_node = self;
705        for &tag in tags {
706            let children = current_node.children()?;
707            current_node = children.iter().find(|c| c.tag == tag)?;
708        }
709        Some(current_node)
710    }
711
712    pub fn get_children_by_tag<'a>(&'a self, tag: &'a str) -> impl Iterator<Item = &'a Node> {
713        // `unwrap_or_default` and not `into_iter().flatten()`: the absent case is
714        // already an empty slice, so flattening only buys a nested iterator whose
715        // state machine LLVM does not fold away on this hot traversal.
716        self.children()
717            .unwrap_or_default()
718            .iter()
719            .filter(move |c| c.tag == tag)
720    }
721
722    pub fn get_optional_child(&self, tag: &str) -> Option<&Node> {
723        self.children()
724            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
725    }
726
727    /// Extract text content, handling both String and Bytes (lossy UTF-8).
728    pub fn content_as_string(&self) -> Option<CompactString> {
729        match &self.content {
730            Some(NodeContent::String(s)) => Some(s.clone()),
731            Some(NodeContent::Bytes(b)) => {
732                Some(CompactString::from(String::from_utf8_lossy(b).as_ref()))
733            }
734            _ => None,
735        }
736    }
737}
738
739/// Wrapper that serializes `AttrsRef` with the same newtype-struct framing
740/// that serde's derive produces for `Attrs(Vec<...>)`. Without this, binary
741/// formats (bincode, postcard, etc.) would see a bare sequence instead of a
742/// newtype struct wrapper.
743#[cfg(feature = "serde")]
744struct AttrsRefWrapper<'a, 'b>(&'b AttrsRef<'a>);
745
746#[cfg(feature = "serde")]
747impl serde::Serialize for AttrsRefWrapper<'_, '_> {
748    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
749        serializer.serialize_newtype_struct("Attrs", self.0.as_slice())
750    }
751}
752
753#[cfg(feature = "serde")]
754impl serde::Serialize for NodeRef<'_> {
755    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
756        use serde::ser::SerializeStruct;
757        let mut s = serializer.serialize_struct("Node", 3)?;
758        s.serialize_field("tag", &*self.tag)?;
759        s.serialize_field("attrs", &AttrsRefWrapper(&self.attrs))?;
760        s.serialize_field("content", &self.content)?;
761        s.end()
762    }
763}
764
765impl<'a> NodeRef<'a> {
766    pub fn new(tag: NodeStr<'a>, attrs: AttrsRef<'a>, content: Option<NodeContentRef<'a>>) -> Self {
767        Self {
768            tag,
769            attrs,
770            content,
771        }
772    }
773
774    pub fn attrs(&self) -> AttrParserRef<'_> {
775        AttrParserRef::new(self)
776    }
777
778    pub fn children(&self) -> Option<&[NodeRef<'a>]> {
779        match self.content.as_ref() {
780            Some(NodeContentRef::Nodes(nodes)) => Some(nodes),
781            _ => None,
782        }
783    }
784
785    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'a>> {
786        self.attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
787    }
788
789    pub fn attrs_iter(&self) -> impl Iterator<Item = (&NodeStr<'a>, &ValueRef<'a>)> {
790        self.attrs.iter().map(|(k, v)| (k, v))
791    }
792
793    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'a>> {
794        let mut current_node = self;
795        for &tag in tags {
796            let children = current_node.children()?;
797            current_node = children.iter().find(|c| c.tag == tag)?;
798        }
799        Some(current_node)
800    }
801
802    pub fn get_children_by_tag<'b>(&'b self, tag: &'b str) -> impl Iterator<Item = &'b NodeRef<'a>>
803    where
804        'a: 'b,
805    {
806        self.children()
807            .unwrap_or_default()
808            .iter()
809            .filter(move |c| c.tag == tag)
810    }
811
812    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'a>> {
813        self.children()
814            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
815    }
816
817    /// Extract text content, handling both String and Bytes (lossy UTF-8).
818    pub fn content_as_string(&self) -> Option<CompactString> {
819        match self.content.as_ref() {
820            Some(NodeContentRef::String(s)) => Some(s.to_compact_string()),
821            Some(NodeContentRef::Bytes(b)) => Some(CompactString::from(
822                String::from_utf8_lossy(b.as_ref()).as_ref(),
823            )),
824            _ => None,
825        }
826    }
827
828    /// Zero-copy byte content, if this node has Bytes content.
829    pub fn content_bytes(&self) -> Option<&[u8]> {
830        match self.content.as_ref() {
831            Some(NodeContentRef::Bytes(b)) => Some(b.as_ref()),
832            _ => None,
833        }
834    }
835
836    /// Zero-copy string content, if this node has String content.
837    pub fn content_str(&self) -> Option<&str> {
838        match self.content.as_ref() {
839            Some(NodeContentRef::String(s)) => Some(s.as_ref()),
840            _ => None,
841        }
842    }
843
844    /// Child nodes from content, if this node has Nodes content.
845    /// Alias for `children()`.
846    #[inline]
847    pub fn content_nodes(&self) -> Option<&[NodeRef<'a>]> {
848        self.children()
849    }
850
851    pub fn to_owned(&self) -> Node {
852        Node {
853            tag: intern_cow(&self.tag),
854            attrs: self
855                .attrs
856                .iter()
857                .map(|(k, v)| {
858                    let value = match v {
859                        ValueRef::String(s) => NodeValue::String(s.to_compact_string()),
860                        ValueRef::Jid(j) => NodeValue::Jid(j.to_owned()),
861                    };
862                    (intern_cow(k), value)
863                })
864                .collect::<Attrs>(),
865            content: self.content.as_ref().map(|c| match c {
866                NodeContentRef::Bytes(b) => NodeContent::Bytes(b.to_vec()),
867                NodeContentRef::String(s) => NodeContent::String(s.to_compact_string()),
868                NodeContentRef::Nodes(nodes) => {
869                    NodeContent::Nodes(nodes.iter().map(|n| n.to_owned()).collect())
870                }
871            }),
872        }
873    }
874}
875
876// ---------------------------------------------------------------------------
877// OwnedNodeRef — self-referential zero-copy node via yoke
878// ---------------------------------------------------------------------------
879
880use yoke::Yoke;
881
882#[derive(Clone)]
883struct BytesCart(Bytes);
884
885impl std::ops::Deref for BytesCart {
886    type Target = [u8];
887
888    fn deref(&self) -> &Self::Target {
889        self.0.as_ref()
890    }
891}
892
893// Safety: `Bytes` points to immutable backing storage whose deref target
894// remains stable for the lifetime of the value, even when the wrapper moves.
895unsafe impl StableDeref for BytesCart {}
896
897/// A decoded node that owns its decompressed buffer. The inner `NodeRef`
898/// borrows string/byte payloads directly from the buffer, avoiding copies.
899/// Container allocations (attribute Vec, child Vec) still occur during decode.
900///
901/// Wrap in `Arc<OwnedNodeRef>` for cheap sharing across handlers.
902pub struct OwnedNodeRef {
903    inner: Yoke<NodeRef<'static>, BytesCart>,
904}
905
906impl OwnedNodeRef {
907    /// Decode a node from an owned buffer. The buffer should be the raw
908    /// binary-protocol bytes (after decompression, without the leading
909    /// format byte which `unpack` already strips).
910    pub fn new(buffer: impl Into<Bytes>) -> crate::error::Result<Self> {
911        let inner = Yoke::try_attach_to_cart(BytesCart(buffer.into()), |buf| {
912            crate::marshal::unmarshal_ref(buf)
913        })?;
914        Ok(Self { inner })
915    }
916
917    /// Access the borrowed node.
918    #[inline]
919    pub fn get(&self) -> &NodeRef<'_> {
920        self.inner.get()
921    }
922
923    /// Convert to an owned `Node`, cloning all data out of the buffer.
924    /// Use sparingly — this is the allocation path that yoke is designed to avoid.
925    pub fn to_owned_node(&self) -> Node {
926        self.inner.get().to_owned()
927    }
928
929    /// Return a zero-copy `Bytes` sub-view for a slice that borrows from this
930    /// node's backing buffer. Panics if `slice` does not point within the buffer.
931    pub fn slice_bytes(&self, slice: &[u8]) -> Bytes {
932        let cart = &self.inner.backing_cart().0;
933        let base = cart.as_ptr() as usize;
934        let end = base + cart.len();
935        let ptr = slice.as_ptr() as usize;
936        assert!(
937            ptr >= base && ptr + slice.len() <= end,
938            "slice is not within the backing buffer"
939        );
940        let offset = ptr - base;
941        cart.slice(offset..offset + slice.len())
942    }
943
944    /// The tag name of this node.
945    #[inline]
946    pub fn tag(&self) -> &str {
947        &self.get().tag
948    }
949
950    /// Get an attribute parser for this node.
951    #[inline]
952    pub fn attrs(&self) -> AttrParserRef<'_> {
953        self.get().attrs()
954    }
955
956    /// Look up a single attribute by key.
957    #[inline]
958    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'_>> {
959        self.get().get_attr(key)
960    }
961
962    /// Get child nodes, if content is a node list.
963    #[inline]
964    pub fn children(&self) -> Option<&[NodeRef<'_>]> {
965        self.get().children()
966    }
967
968    /// Find a child node by tag.
969    #[inline]
970    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'_>> {
971        self.get().get_optional_child(tag)
972    }
973
974    /// Find a child by traversing a path of tags.
975    #[inline]
976    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'_>> {
977        self.get().get_optional_child_by_tag(tags)
978    }
979
980    /// Get children matching a tag.
981    #[inline]
982    pub fn get_children_by_tag<'b>(
983        &'b self,
984        tag: &'b str,
985    ) -> impl Iterator<Item = &'b NodeRef<'b>> {
986        self.get().get_children_by_tag(tag)
987    }
988
989    /// Zero-copy byte content, if this node has Bytes content.
990    #[inline]
991    pub fn content_bytes(&self) -> Option<&[u8]> {
992        self.get().content_bytes()
993    }
994
995    /// Zero-copy string content, if this node has String content.
996    #[inline]
997    pub fn content_str(&self) -> Option<&str> {
998        self.get().content_str()
999    }
1000
1001    /// Child nodes from content, if this node has Nodes content.
1002    #[inline]
1003    pub fn content_nodes(&self) -> Option<&[NodeRef<'_>]> {
1004        self.get().content_nodes()
1005    }
1006
1007    /// Extract text content, handling both String and Bytes (lossy UTF-8).
1008    #[inline]
1009    pub fn content_as_string(&self) -> Option<CompactString> {
1010        self.get().content_as_string()
1011    }
1012}
1013
1014#[cfg(test)]
1015mod value_ref_compare_tests {
1016    use super::*;
1017    use crate::jid::Server;
1018    use std::str::FromStr;
1019
1020    /// Callers compare a `ValueRef` against a literal to decide whether a
1021    /// stanza came from the server, so `v == needle` has to answer exactly what
1022    /// `v.as_str() == needle` answered — including for the server-only shape
1023    /// (`s.whatsapp.net`, no user), which is the one those checks actually use.
1024    #[test]
1025    fn comparing_a_jid_value_matches_comparing_its_rendered_form() {
1026        let jids = [
1027            "s.whatsapp.net",
1028            "5511999998888@s.whatsapp.net",
1029            "5511999998888:7@s.whatsapp.net",
1030            "5511999998888.2@s.whatsapp.net",
1031            "120363012345678901@g.us",
1032            "123456789012345@lid",
1033            "123456789012345:9@lid",
1034            "123456789.4:17@interop",
1035            "status@broadcast",
1036            "12345.6@hosted.lid",
1037        ];
1038        let needles = [
1039            "s.whatsapp.net",
1040            "5511999998888@s.whatsapp.net",
1041            "5511999998888:7@s.whatsapp.net",
1042            "123456789012345@lid",
1043            "",
1044            "not-a-jid",
1045        ];
1046
1047        for raw in jids {
1048            let owned = Jid::from_str(raw).unwrap_or_else(|e| panic!("{raw}: {e}"));
1049            let borrowed = ValueRef::Jid(JidRef {
1050                user: NodeStr::Borrowed(&owned.user),
1051                server: owned.server,
1052                agent: owned.agent,
1053                device: owned.device,
1054                integrator: owned.integrator,
1055            });
1056            let as_string = ValueRef::String(NodeStr::Borrowed(raw));
1057
1058            for needle in needles {
1059                assert_eq!(
1060                    borrowed.as_str() == needle,
1061                    borrowed == needle,
1062                    "jid value {raw:?} vs {needle:?}"
1063                );
1064                assert_eq!(
1065                    as_string.as_str() == needle,
1066                    as_string == needle,
1067                    "string value {raw:?} vs {needle:?}"
1068                );
1069            }
1070        }
1071
1072        // The server-only shape is the one the server checks compare against.
1073        let server_only = ValueRef::Jid(JidRef {
1074            user: NodeStr::Borrowed(""),
1075            server: Server::Pn,
1076            agent: 0,
1077            device: 0,
1078            integrator: 0,
1079        });
1080        assert!(server_only == "s.whatsapp.net");
1081        assert!(server_only != "5511999998888@s.whatsapp.net");
1082    }
1083}
1084
1085#[cfg(test)]
1086mod owned_node_ref_tests {
1087    use super::*;
1088
1089    /// Raw binary-protocol bytes, as `OwnedNodeRef::new` wants them: `marshal`
1090    /// writes a leading format byte that `unmarshal_ref` does not expect.
1091    fn encoded(node: &Node) -> Bytes {
1092        let bytes = crate::marshal::marshal(node).unwrap();
1093        Bytes::from(bytes[1..].to_vec())
1094    }
1095
1096    fn sample() -> Node {
1097        Node::new(
1098            "iq",
1099            Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("abc".into()))].into()),
1100            Some(NodeContent::Bytes(b"payload".to_vec())),
1101        )
1102    }
1103
1104    #[test]
1105    fn borrowed_payloads_survive_moving_the_cart() {
1106        let node = sample();
1107        let owned = OwnedNodeRef::new(encoded(&node)).unwrap();
1108
1109        // Move the value twice — through a Box and into a Vec — before reading
1110        // anything back. That is the whole `StableDeref` claim: the yoked
1111        // `NodeRef` keeps pointing at live bytes even though the wrapper it
1112        // borrows from has moved. Nothing but an interpreter notices when it
1113        // stops being true, which is why this test exists separately from the
1114        // serde one it used to be a side effect of.
1115        let mut moved = vec![*Box::new(owned)];
1116        let owned = moved.pop().unwrap();
1117
1118        assert_eq!(owned.tag(), "iq");
1119        assert!(owned.get_attr("id").unwrap() == "abc");
1120        assert_eq!(owned.content_bytes(), Some(&b"payload"[..]));
1121        assert_eq!(owned.to_owned_node(), node);
1122    }
1123
1124    #[test]
1125    fn yoked_attrs_ref_survives_make_transform_and_mutation() {
1126        // `NodeRef`'s derived `Yokeable` transmutes the whole struct in one go,
1127        // so yoking a node never calls `AttrsRef`'s hand-written impl — that one
1128        // is there to satisfy the derive's bound on the field. Reaching its
1129        // three methods takes a yoke of `AttrsRef` itself, and it is the only
1130        // way to put our own transmutes, rather than yoke's generated ones, in
1131        // front of the interpreter.
1132        let cart = BytesCart(Bytes::from_static(b"idabc"));
1133        let mut yoke: Yoke<AttrsRef<'static>, BytesCart> = Yoke::attach_to_cart(cart, |buf| {
1134            // Borrowed from the cart, which is what makes the transmutes load-bearing.
1135            let (key, value) = buf.split_at(2);
1136            AttrsRef::from_vec(vec![(
1137                NodeStr::Borrowed(std::str::from_utf8(key).expect("ascii")),
1138                ValueRef::String(NodeStr::Borrowed(
1139                    std::str::from_utf8(value).expect("ascii"),
1140                )),
1141            )])
1142        });
1143
1144        // `make` ran on attach; `transform` runs here.
1145        let (key, value) = &yoke.get().as_slice()[0];
1146        assert!(*key == "id");
1147        assert!(*value == "abc");
1148
1149        // `transform_mut`, which nothing in the workspace calls.
1150        yoke.with_mut(|attrs| {
1151            *attrs = AttrsRef::from_vec(vec![(
1152                NodeStr::Owned("k".into()),
1153                ValueRef::String(NodeStr::Owned("v".into())),
1154            )]);
1155        });
1156        assert!(yoke.get().as_slice()[0].1 == "v");
1157    }
1158
1159    #[test]
1160    fn slice_bytes_views_the_backing_buffer_without_copying() {
1161        let owned = OwnedNodeRef::new(encoded(&sample())).unwrap();
1162        let content = owned.content_bytes().unwrap();
1163
1164        let view = owned.slice_bytes(content);
1165
1166        assert_eq!(view.as_ref(), b"payload");
1167        assert_eq!(view.as_ptr(), content.as_ptr(), "slice_bytes copied");
1168    }
1169}
1170
1171#[cfg(feature = "serde")]
1172impl serde::Serialize for OwnedNodeRef {
1173    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1174        self.get().serialize(serializer)
1175    }
1176}
1177
1178impl fmt::Debug for OwnedNodeRef {
1179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1180        self.inner.get().fmt(f)
1181    }
1182}
1183
1184#[cfg(test)]
1185mod value_ref_tests {
1186    use super::*;
1187    use crate::jid::Server;
1188
1189    #[test]
1190    fn value_ref_compares_string_and_jid_display_without_conversion() {
1191        let string = ValueRef::String(NodeStr::Borrowed("encrypt"));
1192        assert!(string == "encrypt");
1193        assert!(string != "other");
1194
1195        let jid = ValueRef::Jid(JidRef {
1196            user: NodeStr::Borrowed("12025550111"),
1197            server: Server::Pn,
1198            agent: 0,
1199            device: 7,
1200            integrator: 0,
1201        });
1202        assert!(jid == "12025550111:7@s.whatsapp.net");
1203        assert!(jid != "12025550111@s.whatsapp.net");
1204    }
1205}
1206
1207#[cfg(test)]
1208#[cfg(feature = "serde")]
1209mod serde_tests {
1210    use super::*;
1211    use crate::jid::{Jid, Server};
1212
1213    #[test]
1214    fn node_ref_serializes_same_as_node() {
1215        let node = Node::new(
1216            Cow::Borrowed("message"),
1217            Attrs(
1218                vec![
1219                    (Cow::Borrowed("type"), NodeValue::String("text".into())),
1220                    (Cow::Borrowed("from"), NodeValue::Jid(Jid::pn("5550199999"))),
1221                ]
1222                .into(),
1223            ),
1224            Some(NodeContent::String("hello".into())),
1225        );
1226        let node_ref = node.as_node_ref();
1227
1228        let owned_json = serde_json::to_value(&node).unwrap();
1229        let ref_json = serde_json::to_value(&node_ref).unwrap();
1230        assert_eq!(owned_json, ref_json);
1231    }
1232
1233    #[test]
1234    fn nested_nodes_serialize_same() {
1235        let child = Node::new(Cow::Borrowed("item"), Attrs::new(), None);
1236        let parent = Node::new(
1237            Cow::Borrowed("list"),
1238            Attrs::new(),
1239            Some(NodeContent::Nodes(vec![child])),
1240        );
1241        let parent_ref = parent.as_node_ref();
1242
1243        assert_eq!(
1244            serde_json::to_value(&parent).unwrap(),
1245            serde_json::to_value(&parent_ref).unwrap(),
1246        );
1247    }
1248
1249    #[test]
1250    fn bytes_content_serializes_same() {
1251        let node = Node::new(
1252            Cow::Borrowed("iq"),
1253            Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("1".into()))].into()),
1254            Some(NodeContent::Bytes(vec![0xDE, 0xAD])),
1255        );
1256        let node_ref = node.as_node_ref();
1257
1258        let owned_json = serde_json::to_value(&node).unwrap();
1259        let ref_json = serde_json::to_value(&node_ref).unwrap();
1260        assert_eq!(owned_json, ref_json);
1261    }
1262
1263    #[test]
1264    fn value_ref_matches_node_value() {
1265        let string_val = NodeValue::String("hello".into());
1266        let string_ref = ValueRef::String(NodeStr::Borrowed("hello"));
1267        assert_eq!(
1268            serde_json::to_value(&string_val).unwrap(),
1269            serde_json::to_value(&string_ref).unwrap(),
1270        );
1271
1272        let jid = Jid {
1273            user: "5550199999".into(),
1274            server: Server::Group,
1275            agent: 1,
1276            device: 2,
1277            integrator: 3,
1278        };
1279        let jid_val = NodeValue::Jid(jid.clone());
1280        let jid_ref_val = ValueRef::Jid(JidRef {
1281            user: NodeStr::Borrowed("5550199999"),
1282            server: Server::Group,
1283            agent: 1,
1284            device: 2,
1285            integrator: 3,
1286        });
1287        assert_eq!(
1288            serde_json::to_value(&jid_val).unwrap(),
1289            serde_json::to_value(&jid_ref_val).unwrap(),
1290        );
1291    }
1292
1293    #[test]
1294    fn owned_node_ref_serializes_same_as_owned() {
1295        let node = Node::new(
1296            Cow::Borrowed("iq"),
1297            Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("abc".into()))].into()),
1298            Some(NodeContent::String("payload".into())),
1299        );
1300
1301        let bytes = crate::marshal::marshal(&node).unwrap();
1302        // marshal writes a leading format byte that unmarshal_ref doesn't expect
1303        let owned_ref = OwnedNodeRef::new(Bytes::from(bytes[1..].to_vec())).unwrap();
1304
1305        let from_ref = serde_json::to_value(&owned_ref).unwrap();
1306        let from_owned = serde_json::to_value(owned_ref.to_owned_node()).unwrap();
1307        assert_eq!(from_ref, from_owned);
1308    }
1309}