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 std::fmt::Debug for NodeStr<'_> {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        std::fmt::Debug::fmt(&**self, f)
57    }
58}
59
60impl std::fmt::Display for NodeStr<'_> {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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            // Compare JID to string without heap allocation by streaming the
188            // Display output through a writer that checks byte-by-byte.
189            NodeValue::Jid(j) => {
190                use std::fmt::Write;
191                struct EqCheck<'a> {
192                    target: &'a [u8],
193                    pos: usize,
194                    matches: bool,
195                }
196                impl fmt::Write for EqCheck<'_> {
197                    fn write_str(&mut self, s: &str) -> fmt::Result {
198                        if !self.matches {
199                            return Ok(());
200                        }
201                        let bytes = s.as_bytes();
202                        let end = self.pos + bytes.len();
203                        if end > self.target.len() || self.target[self.pos..end] != *bytes {
204                            self.matches = false;
205                        }
206                        self.pos = end;
207                        Ok(())
208                    }
209                }
210                let mut check = EqCheck {
211                    target: other.as_bytes(),
212                    pos: 0,
213                    matches: true,
214                };
215                let _ = write!(check, "{}", j);
216                check.matches && check.pos == other.len()
217            }
218        }
219    }
220}
221
222impl PartialEq<&str> for NodeValue {
223    fn eq(&self, other: &&str) -> bool {
224        self == *other
225    }
226}
227
228impl PartialEq<String> for NodeValue {
229    fn eq(&self, other: &String) -> bool {
230        self == other.as_str()
231    }
232}
233
234impl From<String> for NodeValue {
235    #[inline]
236    fn from(s: String) -> Self {
237        NodeValue::String(CompactString::from(s))
238    }
239}
240
241impl From<&str> for NodeValue {
242    #[inline]
243    fn from(s: &str) -> Self {
244        NodeValue::String(CompactString::from(s))
245    }
246}
247
248impl From<&String> for NodeValue {
249    #[inline]
250    fn from(s: &String) -> Self {
251        NodeValue::String(CompactString::from(s.as_str()))
252    }
253}
254
255impl From<CompactString> for NodeValue {
256    #[inline]
257    fn from(s: CompactString) -> Self {
258        NodeValue::String(s)
259    }
260}
261
262impl From<Jid> for NodeValue {
263    #[inline]
264    fn from(jid: Jid) -> Self {
265        NodeValue::Jid(jid)
266    }
267}
268
269impl From<&Jid> for NodeValue {
270    #[inline]
271    fn from(jid: &Jid) -> Self {
272        NodeValue::Jid(jid.clone())
273    }
274}
275
276macro_rules! impl_from_integer_for_nodevalue {
277    ($($t:ty),* $(,)?) => {
278        $(
279            impl From<$t> for NodeValue {
280                #[inline]
281                fn from(n: $t) -> Self {
282                    let mut buf = itoa::Buffer::new();
283                    NodeValue::String(CompactString::from(buf.format(n)))
284                }
285            }
286        )*
287    };
288}
289
290impl_from_integer_for_nodevalue!(
291    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
292);
293
294impl From<bool> for NodeValue {
295    #[inline]
296    fn from(b: bool) -> Self {
297        NodeValue::String(CompactString::from(if b { "true" } else { "false" }))
298    }
299}
300
301/// A collection of node attributes stored as key-value pairs.
302/// Uses a Vec internally for better cache locality with small attribute counts (typically 3-6).
303/// Values can be either strings or JIDs, avoiding stringification overhead for JID attributes.
304/// Keys use `Cow<'static, str>` to avoid heap allocation for compile-time-known strings
305/// (e.g., "type", "id", "to") which are the vast majority of attribute keys.
306#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
307#[derive(Debug, Clone, PartialEq, Default)]
308pub struct Attrs(pub Vec<(Cow<'static, str>, NodeValue)>);
309
310impl Attrs {
311    #[inline]
312    pub fn new() -> Self {
313        Self(Vec::new())
314    }
315
316    #[inline]
317    pub fn with_capacity(capacity: usize) -> Self {
318        Self(Vec::with_capacity(capacity))
319    }
320
321    /// Get a reference to the NodeValue for a key, or None if not found.
322    /// Uses linear search which is efficient for small attribute counts.
323    #[inline]
324    pub fn get(&self, key: &str) -> Option<&NodeValue> {
325        self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
326    }
327
328    /// Check if a key exists.
329    #[inline]
330    pub fn contains_key(&self, key: &str) -> bool {
331        self.0.iter().any(|(k, _)| k == key)
332    }
333
334    /// Insert a key-value pair. If the key already exists, update the value.
335    #[inline]
336    pub fn insert(&mut self, key: impl Into<Cow<'static, str>>, value: impl Into<NodeValue>) {
337        let key = key.into();
338        let value = value.into();
339        if let Some(pos) = self.0.iter().position(|(k, _)| k == &key) {
340            self.0[pos].1 = value;
341        } else {
342            self.0.push((key, value));
343        }
344    }
345
346    #[inline]
347    pub fn len(&self) -> usize {
348        self.0.len()
349    }
350
351    #[inline]
352    pub fn is_empty(&self) -> bool {
353        self.0.is_empty()
354    }
355
356    /// Iterate over key-value pairs.
357    #[inline]
358    pub fn iter(&self) -> impl Iterator<Item = (&Cow<'static, str>, &NodeValue)> {
359        self.0.iter().map(|(k, v)| (k, v))
360    }
361
362    /// Push a key-value pair without checking for duplicates.
363    /// Use this when building from a known-unique source (e.g., decoding).
364    #[inline]
365    pub fn push(&mut self, key: impl Into<Cow<'static, str>>, value: impl Into<NodeValue>) {
366        self.0.push((key.into(), value.into()));
367    }
368
369    /// Push a NodeValue directly without conversion.
370    /// Slightly more efficient when you already have a NodeValue.
371    #[inline]
372    pub fn push_value(&mut self, key: impl Into<Cow<'static, str>>, value: NodeValue) {
373        self.0.push((key.into(), value));
374    }
375
376    /// Iterate over keys only.
377    #[inline]
378    pub fn keys(&self) -> impl Iterator<Item = &Cow<'static, str>> {
379        self.0.iter().map(|(k, _)| k)
380    }
381}
382
383/// Owned iterator implementation (consuming).
384impl IntoIterator for Attrs {
385    type Item = (Cow<'static, str>, NodeValue);
386    type IntoIter = std::vec::IntoIter<(Cow<'static, str>, NodeValue)>;
387
388    fn into_iter(self) -> Self::IntoIter {
389        self.0.into_iter()
390    }
391}
392
393/// Borrowed iterator implementation.
394impl<'a> IntoIterator for &'a Attrs {
395    type Item = (&'a Cow<'static, str>, &'a NodeValue);
396    type IntoIter = std::iter::Map<
397        std::slice::Iter<'a, (Cow<'static, str>, NodeValue)>,
398        fn(&'a (Cow<'static, str>, NodeValue)) -> (&'a Cow<'static, str>, &'a NodeValue),
399    >;
400
401    fn into_iter(self) -> Self::IntoIter {
402        self.0.iter().map(|(k, v)| (k, v))
403    }
404}
405
406impl FromIterator<(Cow<'static, str>, NodeValue)> for Attrs {
407    fn from_iter<I: IntoIterator<Item = (Cow<'static, str>, NodeValue)>>(iter: I) -> Self {
408        Self(iter.into_iter().collect())
409    }
410}
411/// Covariant attribute container for decoded nodes.
412///
413/// Uses `Box<[T]>` (16 bytes: ptr + len) instead of `Vec<T>` (24 bytes: ptr + len + cap)
414/// or inline storage (which inflated NodeRef size). Zero-attr nodes skip allocation
415/// entirely. The boxed slice is allocated once with exact size from the decoder.
416///
417/// Covariant in `'a` (both Box and slices are covariant), compatible with yoke::Yokeable.
418#[derive(Debug, Clone)]
419pub enum AttrsRef<'a> {
420    Empty,
421    Slice(Box<[(NodeStr<'a>, ValueRef<'a>)]>),
422}
423
424impl PartialEq for AttrsRef<'_> {
425    fn eq(&self, other: &Self) -> bool {
426        self.as_slice() == other.as_slice()
427    }
428}
429
430impl<'a> AttrsRef<'a> {
431    /// Build from a pre-filled Vec. Preferred path from the decoder which
432    /// knows the exact attr count upfront.
433    pub fn from_vec(v: Vec<(NodeStr<'a>, ValueRef<'a>)>) -> Self {
434        if v.is_empty() {
435            Self::Empty
436        } else {
437            Self::Slice(v.into_boxed_slice())
438        }
439    }
440
441    #[inline]
442    pub fn len(&self) -> usize {
443        match self {
444            Self::Empty => 0,
445            Self::Slice(s) => s.len(),
446        }
447    }
448
449    #[inline]
450    pub fn is_empty(&self) -> bool {
451        self.as_slice().is_empty()
452    }
453
454    #[inline]
455    pub fn as_slice(&self) -> &[(NodeStr<'a>, ValueRef<'a>)] {
456        match self {
457            Self::Empty => &[],
458            Self::Slice(s) => s,
459        }
460    }
461
462    #[inline]
463    pub fn iter(&self) -> impl Iterator<Item = &(NodeStr<'a>, ValueRef<'a>)> {
464        self.as_slice().iter()
465    }
466}
467
468impl<'a> FromIterator<(NodeStr<'a>, ValueRef<'a>)> for AttrsRef<'a> {
469    fn from_iter<I: IntoIterator<Item = (NodeStr<'a>, ValueRef<'a>)>>(iter: I) -> Self {
470        Self::from_vec(iter.into_iter().collect())
471    }
472}
473
474// Compile-time covariance check: if AttrsRef ever becomes invariant
475// (e.g. by adding a Cell or &mut), this function will fail to compile.
476fn _assert_attrs_ref_covariant<'short, 'long: 'short>(x: AttrsRef<'long>) -> AttrsRef<'short> {
477    x
478}
479
480// Safety: AttrsRef<'a> is covariant in 'a because:
481// - Empty carries no lifetime
482// - Slice(Box<[(NodeStr<'a>, ValueRef<'a>)]>): Box<[T]> is covariant in T,
483//   and (NodeStr<'a>, ValueRef<'a>) is covariant in 'a
484// The _assert_attrs_ref_covariant function above enforces this at compile time.
485unsafe impl<'a> yoke::Yokeable<'a> for AttrsRef<'static> {
486    type Output = AttrsRef<'a>;
487
488    fn transform(&'a self) -> &'a Self::Output {
489        self
490    }
491
492    fn transform_owned(self) -> Self::Output {
493        self
494    }
495
496    unsafe fn make(from: Self::Output) -> Self {
497        unsafe { std::mem::transmute(from) }
498    }
499
500    fn transform_mut<F>(&'a mut self, f: F)
501    where
502        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
503    {
504        unsafe { f(std::mem::transmute::<&mut Self, &mut Self::Output>(self)) }
505    }
506}
507
508/// A decoded attribute value that can be either a string or a structured JID.
509/// This avoids string allocation when decoding JID tokens - the JidRef is returned
510/// directly and only converted to a string when actually needed.
511#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
512pub enum ValueRef<'a> {
513    String(NodeStr<'a>),
514    Jid(JidRef<'a>),
515}
516
517#[cfg(feature = "serde")]
518impl serde::Serialize for ValueRef<'_> {
519    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
520        match self {
521            ValueRef::String(s) => {
522                serializer.serialize_newtype_variant("NodeValue", 0, "String", &**s)
523            }
524            ValueRef::Jid(j) => serializer.serialize_newtype_variant("NodeValue", 1, "Jid", j),
525        }
526    }
527}
528
529impl<'a> ValueRef<'a> {
530    /// Encode this value directly to the binary encoder.
531    pub fn encode_value<W: crate::encoder::ByteWriter>(
532        &self,
533        encoder: &mut crate::encoder::Encoder<'_, W>,
534    ) -> crate::error::Result<()> {
535        match self {
536            ValueRef::String(s) => encoder.write_string(s),
537            ValueRef::Jid(jid) => encoder.write_jid_ref(jid),
538        }
539    }
540
541    /// String view of the value. Borrows from `self`.
542    /// - String variant: borrows the inner str — zero copy
543    /// - Jid variant: Cow::Owned — allocates only when needed
544    pub fn as_str(&self) -> Cow<'_, str> {
545        match self {
546            ValueRef::String(s) => Cow::Borrowed(s),
547            ValueRef::Jid(j) => Cow::Owned(j.to_string()),
548        }
549    }
550
551    /// Get the value as a JidRef, if it's a JID variant.
552    pub fn as_jid(&self) -> Option<&JidRef<'a>> {
553        match self {
554            ValueRef::Jid(j) => Some(j),
555            ValueRef::String(_) => None,
556        }
557    }
558
559    /// Convert to an owned Jid, parsing from string if necessary.
560    pub fn to_jid(&self) -> Option<Jid> {
561        match self {
562            ValueRef::Jid(j) => Some(j.to_owned()),
563            ValueRef::String(s) => Jid::from_str(s.as_ref()).ok(),
564        }
565    }
566
567    /// Convert to an owned NodeValue, preserving the variant (JID stays JID).
568    pub fn to_node_value(&self) -> NodeValue {
569        match self {
570            ValueRef::String(s) => NodeValue::String(s.to_compact_string()),
571            ValueRef::Jid(j) => NodeValue::Jid(j.to_owned()),
572        }
573    }
574}
575
576use std::str::FromStr;
577
578impl<'a> fmt::Display for ValueRef<'a> {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        match self {
581            ValueRef::String(s) => write!(f, "{}", s),
582            ValueRef::Jid(j) => write!(f, "{}", j),
583        }
584    }
585}
586
587pub type NodeVec<'a> = Vec<NodeRef<'a>>;
588
589#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
590#[derive(Debug, Clone, PartialEq)]
591pub enum NodeContent {
592    Bytes(Vec<u8>),
593    String(CompactString),
594    Nodes(Vec<Node>),
595}
596
597#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
598pub enum NodeContentRef<'a> {
599    Bytes(Cow<'a, [u8]>),
600    String(NodeStr<'a>),
601    Nodes(Box<[NodeRef<'a>]>),
602}
603
604#[cfg(feature = "serde")]
605impl serde::Serialize for NodeContentRef<'_> {
606    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
607        match self {
608            NodeContentRef::Bytes(b) => {
609                serializer.serialize_newtype_variant("NodeContent", 0, "Bytes", b.as_ref())
610            }
611            NodeContentRef::String(s) => {
612                serializer.serialize_newtype_variant("NodeContent", 1, "String", &**s)
613            }
614            NodeContentRef::Nodes(nodes) => {
615                serializer.serialize_newtype_variant("NodeContent", 2, "Nodes", &**nodes)
616            }
617        }
618    }
619}
620
621impl NodeContent {
622    /// Convert an owned NodeContent to a borrowed NodeContentRef.
623    pub fn as_content_ref(&self) -> NodeContentRef<'_> {
624        match self {
625            NodeContent::Bytes(b) => NodeContentRef::Bytes(Cow::Borrowed(b)),
626            NodeContent::String(s) => NodeContentRef::String(NodeStr::Borrowed(s.as_str())),
627            NodeContent::Nodes(nodes) => {
628                let v: Vec<_> = nodes.iter().map(|n| n.as_node_ref()).collect();
629                NodeContentRef::Nodes(v.into_boxed_slice())
630            }
631        }
632    }
633}
634
635#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
636#[derive(Debug, Clone, PartialEq, Default)]
637pub struct Node {
638    pub tag: Cow<'static, str>,
639    pub attrs: Attrs,
640    pub content: Option<NodeContent>,
641}
642
643#[derive(Debug, Clone, PartialEq, yoke::Yokeable)]
644pub struct NodeRef<'a> {
645    pub tag: NodeStr<'a>,
646    pub attrs: AttrsRef<'a>,
647    pub content: Option<Box<NodeContentRef<'a>>>,
648}
649
650impl Node {
651    pub fn new(
652        tag: impl Into<Cow<'static, str>>,
653        attrs: Attrs,
654        content: Option<NodeContent>,
655    ) -> Self {
656        Self {
657            tag: tag.into(),
658            attrs,
659            content,
660        }
661    }
662
663    /// Convert an owned Node to a borrowed NodeRef.
664    /// The returned NodeRef borrows from self.
665    pub fn as_node_ref(&self) -> NodeRef<'_> {
666        NodeRef {
667            tag: NodeStr::Borrowed(self.tag.as_ref()),
668            attrs: self
669                .attrs
670                .iter()
671                .map(|(k, v)| {
672                    let value_ref = match v {
673                        NodeValue::String(s) => ValueRef::String(NodeStr::Borrowed(s.as_str())),
674                        NodeValue::Jid(j) => ValueRef::Jid(JidRef {
675                            user: NodeStr::Borrowed(&j.user),
676                            server: j.server,
677                            agent: j.agent,
678                            device: j.device,
679                            integrator: j.integrator,
680                        }),
681                    };
682                    (NodeStr::Borrowed(k.as_ref()), value_ref)
683                })
684                .collect(),
685            content: self.content.as_ref().map(|c| Box::new(c.as_content_ref())),
686        }
687    }
688
689    pub fn children(&self) -> Option<&[Node]> {
690        match &self.content {
691            Some(NodeContent::Nodes(nodes)) => Some(nodes),
692            _ => None,
693        }
694    }
695
696    pub fn attrs(&self) -> AttrParser<'_> {
697        AttrParser::new(self)
698    }
699
700    pub fn get_optional_child_by_tag<'a>(&'a self, tags: &[&str]) -> Option<&'a Node> {
701        let mut current_node = self;
702        for &tag in tags {
703            if let Some(children) = current_node.children() {
704                if let Some(found) = children.iter().find(|c| c.tag == tag) {
705                    current_node = found;
706                } else {
707                    return None;
708                }
709            } else {
710                return None;
711            }
712        }
713        Some(current_node)
714    }
715
716    pub fn get_children_by_tag<'a>(&'a self, tag: &'a str) -> impl Iterator<Item = &'a Node> {
717        self.children()
718            .into_iter()
719            .flatten()
720            .filter(move |c| c.tag == tag)
721    }
722
723    pub fn get_optional_child(&self, tag: &str) -> Option<&Node> {
724        self.children()
725            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
726    }
727
728    /// Extract text content, handling both String and Bytes (lossy UTF-8).
729    pub fn content_as_string(&self) -> Option<CompactString> {
730        match &self.content {
731            Some(NodeContent::String(s)) => Some(s.clone()),
732            Some(NodeContent::Bytes(b)) => {
733                Some(CompactString::from(String::from_utf8_lossy(b).as_ref()))
734            }
735            _ => None,
736        }
737    }
738}
739
740/// Wrapper that serializes `AttrsRef` with the same newtype-struct framing
741/// that serde's derive produces for `Attrs(Vec<...>)`. Without this, binary
742/// formats (bincode, postcard, etc.) would see a bare sequence instead of a
743/// newtype struct wrapper.
744#[cfg(feature = "serde")]
745struct AttrsRefWrapper<'a, 'b>(&'b AttrsRef<'a>);
746
747#[cfg(feature = "serde")]
748impl serde::Serialize for AttrsRefWrapper<'_, '_> {
749    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
750        serializer.serialize_newtype_struct("Attrs", self.0.as_slice())
751    }
752}
753
754#[cfg(feature = "serde")]
755impl serde::Serialize for NodeRef<'_> {
756    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
757        use serde::ser::SerializeStruct;
758        let mut s = serializer.serialize_struct("Node", 3)?;
759        s.serialize_field("tag", &*self.tag)?;
760        s.serialize_field("attrs", &AttrsRefWrapper(&self.attrs))?;
761        s.serialize_field("content", &self.content)?;
762        s.end()
763    }
764}
765
766impl<'a> NodeRef<'a> {
767    pub fn new(tag: NodeStr<'a>, attrs: AttrsRef<'a>, content: Option<NodeContentRef<'a>>) -> Self {
768        Self {
769            tag,
770            attrs,
771            content: content.map(Box::new),
772        }
773    }
774
775    pub fn attrs(&self) -> AttrParserRef<'_> {
776        AttrParserRef::new(self)
777    }
778
779    pub fn children(&self) -> Option<&[NodeRef<'a>]> {
780        match self.content.as_deref() {
781            Some(NodeContentRef::Nodes(nodes)) => Some(nodes),
782            _ => None,
783        }
784    }
785
786    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'a>> {
787        self.attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
788    }
789
790    pub fn attrs_iter(&self) -> impl Iterator<Item = (&NodeStr<'a>, &ValueRef<'a>)> {
791        self.attrs.iter().map(|(k, v)| (k, v))
792    }
793
794    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'a>> {
795        let mut current_node = self;
796        for &tag in tags {
797            if let Some(children) = current_node.children() {
798                if let Some(found) = children.iter().find(|c| c.tag == tag) {
799                    current_node = found;
800                } else {
801                    return None;
802                }
803            } else {
804                return None;
805            }
806        }
807        Some(current_node)
808    }
809
810    pub fn get_children_by_tag<'b>(&'b self, tag: &'b str) -> impl Iterator<Item = &'b NodeRef<'a>>
811    where
812        'a: 'b,
813    {
814        self.children()
815            .into_iter()
816            .flatten()
817            .filter(move |c| c.tag == tag)
818    }
819
820    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'a>> {
821        self.children()
822            .and_then(|nodes| nodes.iter().find(|node| node.tag == tag))
823    }
824
825    /// Extract text content, handling both String and Bytes (lossy UTF-8).
826    pub fn content_as_string(&self) -> Option<CompactString> {
827        match self.content.as_deref() {
828            Some(NodeContentRef::String(s)) => Some(s.to_compact_string()),
829            Some(NodeContentRef::Bytes(b)) => Some(CompactString::from(
830                String::from_utf8_lossy(b.as_ref()).as_ref(),
831            )),
832            _ => None,
833        }
834    }
835
836    /// Zero-copy byte content, if this node has Bytes content.
837    pub fn content_bytes(&self) -> Option<&[u8]> {
838        match self.content.as_deref() {
839            Some(NodeContentRef::Bytes(b)) => Some(b.as_ref()),
840            _ => None,
841        }
842    }
843
844    /// Zero-copy string content, if this node has String content.
845    pub fn content_str(&self) -> Option<&str> {
846        match self.content.as_deref() {
847            Some(NodeContentRef::String(s)) => Some(s.as_ref()),
848            _ => None,
849        }
850    }
851
852    /// Child nodes from content, if this node has Nodes content.
853    /// Alias for `children()`.
854    #[inline]
855    pub fn content_nodes(&self) -> Option<&[NodeRef<'a>]> {
856        self.children()
857    }
858
859    pub fn to_owned(&self) -> Node {
860        Node {
861            tag: intern_cow(&self.tag),
862            attrs: self
863                .attrs
864                .iter()
865                .map(|(k, v)| {
866                    let value = match v {
867                        ValueRef::String(s) => NodeValue::String(s.to_compact_string()),
868                        ValueRef::Jid(j) => NodeValue::Jid(j.to_owned()),
869                    };
870                    (intern_cow(k), value)
871                })
872                .collect::<Attrs>(),
873            content: self.content.as_deref().map(|c| match c {
874                NodeContentRef::Bytes(b) => NodeContent::Bytes(b.to_vec()),
875                NodeContentRef::String(s) => NodeContent::String(s.to_compact_string()),
876                NodeContentRef::Nodes(nodes) => {
877                    NodeContent::Nodes(nodes.iter().map(|n| n.to_owned()).collect())
878                }
879            }),
880        }
881    }
882}
883
884// ---------------------------------------------------------------------------
885// OwnedNodeRef — self-referential zero-copy node via yoke
886// ---------------------------------------------------------------------------
887
888use yoke::Yoke;
889
890#[derive(Clone)]
891struct BytesCart(Bytes);
892
893impl std::ops::Deref for BytesCart {
894    type Target = [u8];
895
896    fn deref(&self) -> &Self::Target {
897        self.0.as_ref()
898    }
899}
900
901// Safety: `Bytes` points to immutable backing storage whose deref target
902// remains stable for the lifetime of the value, even when the wrapper moves.
903unsafe impl StableDeref for BytesCart {}
904
905/// A decoded node that owns its decompressed buffer. The inner `NodeRef`
906/// borrows string/byte payloads directly from the buffer, avoiding copies.
907/// Container allocations (attribute Vec, child Vec) still occur during decode.
908///
909/// Wrap in `Arc<OwnedNodeRef>` for cheap sharing across handlers.
910pub struct OwnedNodeRef {
911    inner: Yoke<NodeRef<'static>, BytesCart>,
912}
913
914impl OwnedNodeRef {
915    /// Decode a node from an owned buffer. The buffer should be the raw
916    /// binary-protocol bytes (after decompression, without the leading
917    /// format byte which `unpack` already strips).
918    pub fn new(buffer: impl Into<Bytes>) -> crate::error::Result<Self> {
919        let inner = Yoke::try_attach_to_cart(BytesCart(buffer.into()), |buf| {
920            crate::marshal::unmarshal_ref(buf)
921        })?;
922        Ok(Self { inner })
923    }
924
925    /// Access the borrowed node.
926    #[inline]
927    pub fn get(&self) -> &NodeRef<'_> {
928        self.inner.get()
929    }
930
931    /// Convert to an owned `Node`, cloning all data out of the buffer.
932    /// Use sparingly — this is the allocation path that yoke is designed to avoid.
933    pub fn to_owned_node(&self) -> Node {
934        self.inner.get().to_owned()
935    }
936
937    /// Return a zero-copy `Bytes` sub-view for a slice that borrows from this
938    /// node's backing buffer. Panics if `slice` does not point within the buffer.
939    pub fn slice_bytes(&self, slice: &[u8]) -> Bytes {
940        let cart = &self.inner.backing_cart().0;
941        let base = cart.as_ptr() as usize;
942        let end = base + cart.len();
943        let ptr = slice.as_ptr() as usize;
944        assert!(
945            ptr >= base && ptr + slice.len() <= end,
946            "slice is not within the backing buffer"
947        );
948        let offset = ptr - base;
949        cart.slice(offset..offset + slice.len())
950    }
951
952    /// The tag name of this node.
953    #[inline]
954    pub fn tag(&self) -> &str {
955        &self.get().tag
956    }
957
958    /// Get an attribute parser for this node.
959    #[inline]
960    pub fn attrs(&self) -> AttrParserRef<'_> {
961        self.get().attrs()
962    }
963
964    /// Look up a single attribute by key.
965    #[inline]
966    pub fn get_attr(&self, key: &str) -> Option<&ValueRef<'_>> {
967        self.get().get_attr(key)
968    }
969
970    /// Get child nodes, if content is a node list.
971    #[inline]
972    pub fn children(&self) -> Option<&[NodeRef<'_>]> {
973        self.get().children()
974    }
975
976    /// Find a child node by tag.
977    #[inline]
978    pub fn get_optional_child(&self, tag: &str) -> Option<&NodeRef<'_>> {
979        self.get().get_optional_child(tag)
980    }
981
982    /// Find a child by traversing a path of tags.
983    #[inline]
984    pub fn get_optional_child_by_tag(&self, tags: &[&str]) -> Option<&NodeRef<'_>> {
985        self.get().get_optional_child_by_tag(tags)
986    }
987
988    /// Get children matching a tag.
989    #[inline]
990    pub fn get_children_by_tag<'b>(
991        &'b self,
992        tag: &'b str,
993    ) -> impl Iterator<Item = &'b NodeRef<'b>> {
994        self.get().get_children_by_tag(tag)
995    }
996
997    /// Zero-copy byte content, if this node has Bytes content.
998    #[inline]
999    pub fn content_bytes(&self) -> Option<&[u8]> {
1000        self.get().content_bytes()
1001    }
1002
1003    /// Zero-copy string content, if this node has String content.
1004    #[inline]
1005    pub fn content_str(&self) -> Option<&str> {
1006        self.get().content_str()
1007    }
1008
1009    /// Child nodes from content, if this node has Nodes content.
1010    #[inline]
1011    pub fn content_nodes(&self) -> Option<&[NodeRef<'_>]> {
1012        self.get().content_nodes()
1013    }
1014
1015    /// Extract text content, handling both String and Bytes (lossy UTF-8).
1016    #[inline]
1017    pub fn content_as_string(&self) -> Option<CompactString> {
1018        self.get().content_as_string()
1019    }
1020}
1021
1022#[cfg(feature = "serde")]
1023impl serde::Serialize for OwnedNodeRef {
1024    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1025        self.get().serialize(serializer)
1026    }
1027}
1028
1029impl std::fmt::Debug for OwnedNodeRef {
1030    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1031        self.inner.get().fmt(f)
1032    }
1033}
1034
1035#[cfg(test)]
1036#[cfg(feature = "serde")]
1037mod serde_tests {
1038    use super::*;
1039    use crate::jid::{Jid, Server};
1040
1041    #[test]
1042    fn node_ref_serializes_same_as_node() {
1043        let node = Node::new(
1044            Cow::Borrowed("message"),
1045            Attrs(vec![
1046                (Cow::Borrowed("type"), NodeValue::String("text".into())),
1047                (Cow::Borrowed("from"), NodeValue::Jid(Jid::pn("5550199999"))),
1048            ]),
1049            Some(NodeContent::String("hello".into())),
1050        );
1051        let node_ref = node.as_node_ref();
1052
1053        let owned_json = serde_json::to_value(&node).unwrap();
1054        let ref_json = serde_json::to_value(&node_ref).unwrap();
1055        assert_eq!(owned_json, ref_json);
1056    }
1057
1058    #[test]
1059    fn nested_nodes_serialize_same() {
1060        let child = Node::new(Cow::Borrowed("item"), Attrs::new(), None);
1061        let parent = Node::new(
1062            Cow::Borrowed("list"),
1063            Attrs::new(),
1064            Some(NodeContent::Nodes(vec![child])),
1065        );
1066        let parent_ref = parent.as_node_ref();
1067
1068        assert_eq!(
1069            serde_json::to_value(&parent).unwrap(),
1070            serde_json::to_value(&parent_ref).unwrap(),
1071        );
1072    }
1073
1074    #[test]
1075    fn bytes_content_serializes_same() {
1076        let node = Node::new(
1077            Cow::Borrowed("iq"),
1078            Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("1".into()))]),
1079            Some(NodeContent::Bytes(vec![0xDE, 0xAD])),
1080        );
1081        let node_ref = node.as_node_ref();
1082
1083        let owned_json = serde_json::to_value(&node).unwrap();
1084        let ref_json = serde_json::to_value(&node_ref).unwrap();
1085        assert_eq!(owned_json, ref_json);
1086    }
1087
1088    #[test]
1089    fn value_ref_matches_node_value() {
1090        let string_val = NodeValue::String("hello".into());
1091        let string_ref = ValueRef::String(NodeStr::Borrowed("hello"));
1092        assert_eq!(
1093            serde_json::to_value(&string_val).unwrap(),
1094            serde_json::to_value(&string_ref).unwrap(),
1095        );
1096
1097        let jid = Jid {
1098            user: "5550199999".into(),
1099            server: Server::Group,
1100            agent: 1,
1101            device: 2,
1102            integrator: 3,
1103        };
1104        let jid_val = NodeValue::Jid(jid.clone());
1105        let jid_ref_val = ValueRef::Jid(JidRef {
1106            user: NodeStr::Borrowed("5550199999"),
1107            server: Server::Group,
1108            agent: 1,
1109            device: 2,
1110            integrator: 3,
1111        });
1112        assert_eq!(
1113            serde_json::to_value(&jid_val).unwrap(),
1114            serde_json::to_value(&jid_ref_val).unwrap(),
1115        );
1116    }
1117
1118    #[test]
1119    fn owned_node_ref_serializes_same_as_owned() {
1120        let node = Node::new(
1121            Cow::Borrowed("iq"),
1122            Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("abc".into()))]),
1123            Some(NodeContent::String("payload".into())),
1124        );
1125
1126        let bytes = crate::marshal::marshal(&node).unwrap();
1127        // marshal writes a leading format byte that unmarshal_ref doesn't expect
1128        let owned_ref = OwnedNodeRef::new(Bytes::from(bytes[1..].to_vec())).unwrap();
1129
1130        let from_ref = serde_json::to_value(&owned_ref).unwrap();
1131        let from_owned = serde_json::to_value(owned_ref.to_owned_node()).unwrap();
1132        assert_eq!(from_ref, from_owned);
1133    }
1134}