Skip to content

Commit ce620a2

Browse files
authored
feat(wacore-binary): zero-copy Serialize for NodeRef type family (#539)
1 parent 5bd0211 commit ce620a2

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

‎wacore/binary/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ serde_json = { workspace = true, features = ["std"] }
3434

3535
[dev-dependencies]
3636
iai-callgrind = { workspace = true }
37+
serde_json = { workspace = true, features = ["std"] }
3738

3839
[[bench]]
3940
name = "binary_benchmark"

‎wacore/binary/src/jid.rs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,20 @@ impl<'a> JidRef<'a> {
600600
}
601601
}
602602

603+
#[cfg(feature = "serde")]
604+
impl serde::Serialize for JidRef<'_> {
605+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
606+
use serde::ser::SerializeStruct;
607+
let mut s = serializer.serialize_struct("Jid", 5)?;
608+
s.serialize_field("user", &*self.user)?;
609+
s.serialize_field("server", &self.server)?;
610+
s.serialize_field("agent", &self.agent)?;
611+
s.serialize_field("device", &self.device)?;
612+
s.serialize_field("integrator", &self.integrator)?;
613+
s.end()
614+
}
615+
}
616+
603617
impl FromStr for Jid {
604618
type Err = JidError;
605619
fn from_str(s: &str) -> Result<Self, Self::Err> {

‎wacore/binary/src/node.rs‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ impl std::fmt::Display for NodeStr<'_> {
6363
}
6464
}
6565

66+
#[cfg(feature = "serde")]
67+
impl 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+
6673
impl PartialEq for NodeStr<'_> {
6774
#[inline]
6875
fn eq(&self, other: &Self) -> bool {
@@ -387,6 +394,18 @@ pub enum ValueRef<'a> {
387394
Jid(JidRef<'a>),
388395
}
389396

397+
#[cfg(feature = "serde")]
398+
impl serde::Serialize for ValueRef<'_> {
399+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
400+
match self {
401+
ValueRef::String(s) => {
402+
serializer.serialize_newtype_variant("NodeValue", 0, "String", &**s)
403+
}
404+
ValueRef::Jid(j) => serializer.serialize_newtype_variant("NodeValue", 1, "Jid", j),
405+
}
406+
}
407+
}
408+
390409
impl<'a> ValueRef<'a> {
391410
/// String view of the value. Borrows from `self`.
392411
/// - String variant: borrows the inner str — zero copy
@@ -451,6 +470,23 @@ pub enum NodeContentRef<'a> {
451470
Nodes(Box<NodeVec<'a>>),
452471
}
453472

473+
#[cfg(feature = "serde")]
474+
impl serde::Serialize for NodeContentRef<'_> {
475+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
476+
match self {
477+
NodeContentRef::Bytes(b) => {
478+
serializer.serialize_newtype_variant("NodeContent", 0, "Bytes", b.as_ref())
479+
}
480+
NodeContentRef::String(s) => {
481+
serializer.serialize_newtype_variant("NodeContent", 1, "String", &**s)
482+
}
483+
NodeContentRef::Nodes(nodes) => {
484+
serializer.serialize_newtype_variant("NodeContent", 2, "Nodes", nodes.as_slice())
485+
}
486+
}
487+
}
488+
}
489+
454490
impl NodeContent {
455491
/// Convert an owned NodeContent to a borrowed NodeContentRef.
456492
pub fn as_content_ref(&self) -> NodeContentRef<'_> {
@@ -569,6 +605,32 @@ impl Node {
569605
}
570606
}
571607

608+
/// Wrapper that serializes `AttrsRef` with the same newtype-struct framing
609+
/// that serde's derive produces for `Attrs(Vec<...>)`. Without this, binary
610+
/// formats (bincode, postcard, etc.) would see a bare sequence instead of a
611+
/// newtype struct wrapper.
612+
#[cfg(feature = "serde")]
613+
struct AttrsRefWrapper<'a, 'b>(&'b AttrsRef<'a>);
614+
615+
#[cfg(feature = "serde")]
616+
impl serde::Serialize for AttrsRefWrapper<'_, '_> {
617+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
618+
serializer.serialize_newtype_struct("Attrs", self.0)
619+
}
620+
}
621+
622+
#[cfg(feature = "serde")]
623+
impl serde::Serialize for NodeRef<'_> {
624+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
625+
use serde::ser::SerializeStruct;
626+
let mut s = serializer.serialize_struct("Node", 3)?;
627+
s.serialize_field("tag", &*self.tag)?;
628+
s.serialize_field("attrs", &AttrsRefWrapper(&self.attrs))?;
629+
s.serialize_field("content", &self.content)?;
630+
s.end()
631+
}
632+
}
633+
572634
impl<'a> NodeRef<'a> {
573635
pub fn new(tag: NodeStr<'a>, attrs: AttrsRef<'a>, content: Option<NodeContentRef<'a>>) -> Self {
574636
Self {
@@ -810,8 +872,116 @@ impl OwnedNodeRef {
810872
}
811873
}
812874

875+
#[cfg(feature = "serde")]
876+
impl serde::Serialize for OwnedNodeRef {
877+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
878+
self.get().serialize(serializer)
879+
}
880+
}
881+
813882
impl std::fmt::Debug for OwnedNodeRef {
814883
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815884
self.inner.get().fmt(f)
816885
}
817886
}
887+
888+
#[cfg(test)]
889+
#[cfg(feature = "serde")]
890+
mod serde_tests {
891+
use super::*;
892+
use crate::jid::{Jid, Server};
893+
894+
#[test]
895+
fn node_ref_serializes_same_as_node() {
896+
let node = Node::new(
897+
Cow::Borrowed("message"),
898+
Attrs(vec![
899+
(Cow::Borrowed("type"), NodeValue::String("text".into())),
900+
(Cow::Borrowed("from"), NodeValue::Jid(Jid::pn("5550199999"))),
901+
]),
902+
Some(NodeContent::String("hello".into())),
903+
);
904+
let node_ref = node.as_node_ref();
905+
906+
let owned_json = serde_json::to_value(&node).unwrap();
907+
let ref_json = serde_json::to_value(&node_ref).unwrap();
908+
assert_eq!(owned_json, ref_json);
909+
}
910+
911+
#[test]
912+
fn nested_nodes_serialize_same() {
913+
let child = Node::new(Cow::Borrowed("item"), Attrs::new(), None);
914+
let parent = Node::new(
915+
Cow::Borrowed("list"),
916+
Attrs::new(),
917+
Some(NodeContent::Nodes(vec![child])),
918+
);
919+
let parent_ref = parent.as_node_ref();
920+
921+
assert_eq!(
922+
serde_json::to_value(&parent).unwrap(),
923+
serde_json::to_value(&parent_ref).unwrap(),
924+
);
925+
}
926+
927+
#[test]
928+
fn bytes_content_serializes_same() {
929+
let node = Node::new(
930+
Cow::Borrowed("iq"),
931+
Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("1".into()))]),
932+
Some(NodeContent::Bytes(vec![0xDE, 0xAD])),
933+
);
934+
let node_ref = node.as_node_ref();
935+
936+
let owned_json = serde_json::to_value(&node).unwrap();
937+
let ref_json = serde_json::to_value(&node_ref).unwrap();
938+
assert_eq!(owned_json, ref_json);
939+
}
940+
941+
#[test]
942+
fn value_ref_matches_node_value() {
943+
let string_val = NodeValue::String("hello".into());
944+
let string_ref = ValueRef::String(NodeStr::Borrowed("hello"));
945+
assert_eq!(
946+
serde_json::to_value(&string_val).unwrap(),
947+
serde_json::to_value(&string_ref).unwrap(),
948+
);
949+
950+
let jid = Jid {
951+
user: "5550199999".into(),
952+
server: Server::Group,
953+
agent: 1,
954+
device: 2,
955+
integrator: 3,
956+
};
957+
let jid_val = NodeValue::Jid(jid.clone());
958+
let jid_ref_val = ValueRef::Jid(JidRef {
959+
user: NodeStr::Borrowed("5550199999"),
960+
server: Server::Group,
961+
agent: 1,
962+
device: 2,
963+
integrator: 3,
964+
});
965+
assert_eq!(
966+
serde_json::to_value(&jid_val).unwrap(),
967+
serde_json::to_value(&jid_ref_val).unwrap(),
968+
);
969+
}
970+
971+
#[test]
972+
fn owned_node_ref_serializes_same_as_owned() {
973+
let node = Node::new(
974+
Cow::Borrowed("iq"),
975+
Attrs(vec![(Cow::Borrowed("id"), NodeValue::String("abc".into()))]),
976+
Some(NodeContent::String("payload".into())),
977+
);
978+
979+
let bytes = crate::marshal::marshal(&node).unwrap();
980+
// marshal writes a leading format byte that unmarshal_ref doesn't expect
981+
let owned_ref = OwnedNodeRef::new(Bytes::from(bytes[1..].to_vec())).unwrap();
982+
983+
let from_ref = serde_json::to_value(&owned_ref).unwrap();
984+
let from_owned = serde_json::to_value(owned_ref.to_owned_node()).unwrap();
985+
assert_eq!(from_ref, from_owned);
986+
}
987+
}

0 commit comments

Comments
 (0)