Skip to content

Commit

Permalink
Merge pull request #34 from rust-scraper/serde
Browse files Browse the repository at this point in the history
Implement serde traits for ego-tree
  • Loading branch information
cfvescovo authored Aug 29, 2024
2 parents da8049b + 2520ba0 commit b3a9be1
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 49 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target
Cargo.lock
.vscode
.vscode
.idea
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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))
}
Expand All @@ -75,7 +78,7 @@ struct Node<T> {
}

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::<Node<()>, [usize; 5]>;
Expand Down
157 changes: 157 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -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<SerNode<'a, T>>,
}

impl<'a, T> From<NodeRef<'a, T>> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<T: Serialize> Serialize for Tree<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
SerNode::from(self.root()).serialize(serializer)
}
}

#[derive(Debug)]
struct DeserNode<T> {
value: T,
children: Vec<DeserNode<T>>,
}

impl<T> DeserNode<T> {
fn into_tree_node(self, parent: &mut NodeMut<T>) {
let mut node = parent.append(self.value);

for child in self.children {
child.into_tree_node(&mut node);
}
}
}

impl<T> From<DeserNode<T>> for Tree<T> {
fn from(root: DeserNode<T>) -> Self {
let mut tree: Tree<T> = 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<T> {
marker: PhantomData<fn() -> DeserNode<T>>,
}

impl<T> DeserNodeVisitor<T> {
fn new() -> Self {
DeserNodeVisitor {
marker: PhantomData,
}
}
}

impl<'de, T> Visitor<'de> for DeserNodeVisitor<T>
where
T: Deserialize<'de>,
{
type Value = DeserNode<T>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Node")
}

fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
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<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct("Node", &["value", "children"], DeserNodeVisitor::new())
}
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for Tree<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let deser = DeserNode::<T>::deserialize(deserializer)?;
Ok(deser.into())
}
}
3 changes: 1 addition & 2 deletions tests/iter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#[macro_use]
extern crate ego_tree;
use ego_tree::tree;

#[test]
fn into_iter() {
Expand Down
5 changes: 1 addition & 4 deletions tests/macro.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#[macro_use]
extern crate ego_tree;

use ego_tree::Tree;
use ego_tree::{tree, Tree};

#[test]
fn root() {
Expand Down
5 changes: 1 addition & 4 deletions tests/node_mut.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#[macro_use]
extern crate ego_tree;

use ego_tree::NodeRef;
use ego_tree::{tree, NodeRef};

#[test]
fn value() {
Expand Down
3 changes: 1 addition & 2 deletions tests/node_ref.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#[macro_use]
extern crate ego_tree;
use ego_tree::tree;

#[test]
fn value() {
Expand Down
96 changes: 96 additions & 0 deletions tests/serde.rs
Original file line number Diff line number Diff line change
@@ -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,
],
);
}
Loading

0 comments on commit b3a9be1

Please sign in to comment.