diff --git a/.gitignore b/.gitignore index 110d010..5b99a65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target Cargo.lock -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 39448df..f6deaa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,13 @@ authors = [ license = "ISC" repository = "https://github.com/rust-scraper/ego-tree" readme = "README.md" + +[features] +serde = ["dep:serde"] + +[dependencies] +serde = { version = "1.0.209", optional = true } + +[dev-dependencies] +serde = "1.0.209" +serde_test = "1.0.177" diff --git a/src/lib.rs b/src/lib.rs index 566d84d..ee5636d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,9 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::num::NonZeroUsize; +#[cfg(feature = "serde")] +pub mod serde; + /// Vec-backed ID-tree. /// /// Always contains at least a root node. @@ -55,7 +58,7 @@ pub struct NodeId(NonZeroUsize); impl NodeId { // Safety: `n` must not equal `usize::MAX`. // (This is never the case for `Vec::len()`, that would mean it owns - // the entire address space without leaving space for even the its pointer.) + // the entire address space without leaving space even for its pointer.) unsafe fn from_index(n: usize) -> Self { NodeId(NonZeroUsize::new_unchecked(n + 1)) } @@ -75,7 +78,7 @@ struct Node { } fn _static_assert_size_of_node() { - // "Instanciating" the generic `transmute` function without calling it + // "Instantiating" the generic `transmute` function without calling it // still triggers the magic compile-time check // that input and output types have the same `size_of()`. let _ = std::mem::transmute::, [usize; 5]>; diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..137c2ad --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,157 @@ +//! Implement `serde::Serialize` and `serde::Deserialize` traits for Tree +//! +//! # Warning +//! Serialize and Deserialize implementations are recursive. They require an amount of stack memory +//! proportional to the depth of the tree. + +use std::{fmt, marker::PhantomData}; + +use serde::{ + de::{self, MapAccess, Visitor}, + ser::{Serialize, SerializeStruct}, + Deserialize, Deserializer, +}; + +use crate::{NodeMut, NodeRef, Tree}; + +#[derive(Debug)] +struct SerNode<'a, T> { + value: &'a T, + children: Vec>, +} + +impl<'a, T> From> for SerNode<'a, T> { + fn from(node: NodeRef<'a, T>) -> Self { + let value = node.value(); + let children = node.children().map(SerNode::from).collect(); + Self { value, children } + } +} + +impl<'a, T: Serialize> Serialize for SerNode<'a, T> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Node", 2)?; + state.serialize_field("value", &self.value)?; + state.serialize_field("children", &self.children)?; + state.end() + } +} + +impl Serialize for Tree { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + SerNode::from(self.root()).serialize(serializer) + } +} + +#[derive(Debug)] +struct DeserNode { + value: T, + children: Vec>, +} + +impl DeserNode { + fn into_tree_node(self, parent: &mut NodeMut) { + let mut node = parent.append(self.value); + + for child in self.children { + child.into_tree_node(&mut node); + } + } +} + +impl From> for Tree { + fn from(root: DeserNode) -> Self { + let mut tree: Tree = Tree::new(root.value); + let mut tree_root = tree.root_mut(); + + for child in root.children { + child.into_tree_node(&mut tree_root); + } + + tree + } +} + +struct DeserNodeVisitor { + marker: PhantomData DeserNode>, +} + +impl DeserNodeVisitor { + fn new() -> Self { + DeserNodeVisitor { + marker: PhantomData, + } + } +} + +impl<'de, T> Visitor<'de> for DeserNodeVisitor +where + T: Deserialize<'de>, +{ + type Value = DeserNode; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Node") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut value = None; + let mut children = None; + + while let Some(key) = map.next_key()? { + match key { + "value" => { + if value.is_some() { + return Err(de::Error::duplicate_field("value")); + } + value = Some(map.next_value()?); + } + "children" => { + if children.is_some() { + return Err(de::Error::duplicate_field("children")); + } + children = Some(map.next_value()?); + } + _ => { + return Err(de::Error::unknown_field(key, &["value", "children"])); + } + } + } + + let value = value.ok_or_else(|| de::Error::missing_field("value"))?; + let children = children.ok_or_else(|| de::Error::missing_field("children"))?; + + Ok(DeserNode { value, children }) + } +} + +impl<'de, T> Deserialize<'de> for DeserNode +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_struct("Node", &["value", "children"], DeserNodeVisitor::new()) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for Tree { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let deser = DeserNode::::deserialize(deserializer)?; + Ok(deser.into()) + } +} diff --git a/tests/iter.rs b/tests/iter.rs index 6ceffa8..5ce441a 100644 --- a/tests/iter.rs +++ b/tests/iter.rs @@ -1,5 +1,4 @@ -#[macro_use] -extern crate ego_tree; +use ego_tree::tree; #[test] fn into_iter() { diff --git a/tests/macro.rs b/tests/macro.rs index 14cf444..377fb8f 100644 --- a/tests/macro.rs +++ b/tests/macro.rs @@ -1,7 +1,4 @@ -#[macro_use] -extern crate ego_tree; - -use ego_tree::Tree; +use ego_tree::{tree, Tree}; #[test] fn root() { diff --git a/tests/node_mut.rs b/tests/node_mut.rs index 20aff6b..f2e8c63 100644 --- a/tests/node_mut.rs +++ b/tests/node_mut.rs @@ -1,7 +1,4 @@ -#[macro_use] -extern crate ego_tree; - -use ego_tree::NodeRef; +use ego_tree::{tree, NodeRef}; #[test] fn value() { diff --git a/tests/node_ref.rs b/tests/node_ref.rs index c46cb65..af4dff8 100644 --- a/tests/node_ref.rs +++ b/tests/node_ref.rs @@ -1,5 +1,4 @@ -#[macro_use] -extern crate ego_tree; +use ego_tree::tree; #[test] fn value() { diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..0e0deee --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,96 @@ +#![cfg(feature = "serde")] + +use ego_tree::tree; +use serde_test::{assert_tokens, Token}; + +#[test] +fn test_internal_serde_repr_trivial() { + let tree = tree!("a"); + + assert_tokens( + &tree, + &[ + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("a"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + ], + ); +} + +#[test] +fn test_internal_serde_repr() { + let tree = tree!("a" => {"b", "c" => {"d", "e"}, "f"}); + + assert_tokens( + &tree, + &[ + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("a"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(3) }, + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("b"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("c"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(2) }, + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("d"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("e"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + Token::Struct { + name: "Node", + len: 2, + }, + Token::BorrowedStr("value"), + Token::BorrowedStr("f"), + Token::BorrowedStr("children"), + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::StructEnd, + Token::SeqEnd, + Token::StructEnd, + ], + ); +} diff --git a/tests/subtree.rs b/tests/subtree.rs index 6af769d..30a279d 100644 --- a/tests/subtree.rs +++ b/tests/subtree.rs @@ -1,39 +1,35 @@ -#[macro_use] -extern crate ego_tree; +use ego_tree::tree; -#[cfg(test)] -mod test { - #[test] - fn prepend_subtree() { - let mut tree = tree!('a' => { 'b', 'c' => { 'd', 'e' } }); - let node_id = tree.root().first_child().unwrap().id(); - let mut node = tree.get_mut(node_id).unwrap(); - assert_eq!(node.value(), &'b'); +#[test] +fn prepend_subtree() { + let mut tree = tree!('a' => { 'b', 'c' => { 'd', 'e' } }); + let node_id = tree.root().first_child().unwrap().id(); + let mut node = tree.get_mut(node_id).unwrap(); + assert_eq!(node.value(), &'b'); - let subtree = tree!('f' => { 'g', 'h' => { 'i', 'j' } }); - let mut root_subtree = node.prepend_subtree(subtree); - assert_eq!(root_subtree.parent().unwrap().value(), &'b'); - assert_eq!( - root_subtree.parent().unwrap().parent().unwrap().value(), - &'a' - ); + let subtree = tree!('f' => { 'g', 'h' => { 'i', 'j' } }); + let mut root_subtree = node.prepend_subtree(subtree); + assert_eq!(root_subtree.parent().unwrap().value(), &'b'); + assert_eq!( + root_subtree.parent().unwrap().parent().unwrap().value(), + &'a' + ); - let new_tree = - tree!('a' => { 'b' => { 'f' => { 'g', 'h' => { 'i', 'j' } } }, 'c' => { 'd', 'e' } }); - assert_eq!(format!("{:#?}", tree), format!("{:#?}", new_tree)); - } + let new_tree = + tree!('a' => { 'b' => { 'f' => { 'g', 'h' => { 'i', 'j' } } }, 'c' => { 'd', 'e' } }); + assert_eq!(format!("{:#?}", tree), format!("{:#?}", new_tree)); +} - #[test] - fn append_subtree() { - let mut tree = tree!('a' => { 'b', 'c' }); - let mut node = tree.root_mut(); - assert_eq!(node.value(), &'a'); +#[test] +fn append_subtree() { + let mut tree = tree!('a' => { 'b', 'c' }); + let mut node = tree.root_mut(); + assert_eq!(node.value(), &'a'); - let subtree = tree!('d' => { 'e', 'f' }); - let mut root_subtree = node.append_subtree(subtree); - assert_eq!(root_subtree.parent().unwrap().value(), &'a'); + let subtree = tree!('d' => { 'e', 'f' }); + let mut root_subtree = node.append_subtree(subtree); + assert_eq!(root_subtree.parent().unwrap().value(), &'a'); - let new_tree = tree!('a' => { 'b', 'c', 'd' => { 'e', 'f' } }); - assert_eq!(format!("{:#?}", tree), format!("{:#?}", new_tree)); - } + let new_tree = tree!('a' => { 'b', 'c', 'd' => { 'e', 'f' } }); + assert_eq!(format!("{:#?}", tree), format!("{:#?}", new_tree)); } diff --git a/tests/tree.rs b/tests/tree.rs index 78eee6a..bd92b5b 100644 --- a/tests/tree.rs +++ b/tests/tree.rs @@ -1,5 +1,3 @@ -extern crate ego_tree; - use ego_tree::{tree, Tree}; #[test]