Skip to content

Commit

Permalink
Support partial serialization & deserialization
Browse files Browse the repository at this point in the history
This feature allows writing structs which contain `Document` nodes.

- When deserializing, everything under the `Document` node is simply
  attached to the struct.
- When serializing, the `Document` node is passed through unmodified.

Signed-off-by: Chris Frantz <[email protected]>
  • Loading branch information
Chris Frantz authored and cfrantz committed Oct 31, 2022
1 parent 51a55bc commit f458c7b
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 11 deletions.
43 changes: 34 additions & 9 deletions src/annotate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt;

use crate::{AnnotatedSerializer, Document, Error};
use crate::{AnnotatedSerializer, Deserializer, Document, Error};

/// Specifies the formatting options to use when serializing.
pub enum Format {
Expand Down Expand Up @@ -61,18 +61,43 @@ impl<T: ?Sized + serde::Serialize> Annotate for T {
}

// We use a private trait to identify whether the Serializer passed to
// serde::Serialize for dyn Annotate is AnnotatedSerializer.
unsafe trait IsAnnotatedSerializer {
fn is_annotated_serializer(&self) -> bool;
// various functions is our Serializer.
pub(crate) unsafe trait IsSerializer {
fn is_serde_annotate(&self) -> bool;
}

unsafe impl<T: serde::Serializer> IsAnnotatedSerializer for T {
default fn is_annotated_serializer(&self) -> bool {
unsafe impl<T: serde::Serializer> IsSerializer for T {
default fn is_serde_annotate(&self) -> bool {
false
}
}
unsafe impl<'a> IsAnnotatedSerializer for &mut AnnotatedSerializer<'a> {
fn is_annotated_serializer(&self) -> bool {

unsafe impl<'a> IsSerializer for &mut AnnotatedSerializer<'a> {
fn is_serde_annotate(&self) -> bool {
true
}
}

// This marker trait is to avoid specifying lifetimes in the default
// implementation. When I specify lifetimes in the default impl, the
// compiler complains that the specialized impl repeats parameter `'de`.
trait _IsDeserializer {}
impl<'de, T: serde::Deserializer<'de>> _IsDeserializer for T {}

// We use a private trait to identify whether the Deserializer passed to
// various functions is our Deserializer.
pub(crate) unsafe trait IsDeserializer {
fn is_serde_annotate(&self) -> bool;
}

unsafe impl<T: _IsDeserializer> IsDeserializer for T {
default fn is_serde_annotate(&self) -> bool {
false
}
}

unsafe impl<'de> IsDeserializer for &mut Deserializer<'de> {
fn is_serde_annotate(&self) -> bool {
true
}
}
Expand All @@ -89,7 +114,7 @@ unsafe impl<'a> IsAnnotatedSerializer for &mut AnnotatedSerializer<'a> {
// AnnotatedSerializer and just force the types with `transmute`.
impl serde::Serialize for dyn Annotate {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if !serializer.is_annotated_serializer() {
if !serializer.is_serde_annotate() {
panic!(
"Expected to be called by AnnotatedSerializer, not {:?}",
std::any::type_name::<S>()
Expand Down
2 changes: 1 addition & 1 deletion src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Deserialize {

/// A `Deserializer` deserializes a parsed document.
pub struct Deserializer<'de> {
doc: &'de Document,
pub(crate) doc: &'de Document,
}

impl<'de> Deserializer<'de> {
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod error;
mod hexdump;
mod integer;
mod json;
mod partial;
mod relax;
mod ser;
mod yaml;
Expand All @@ -18,7 +19,7 @@ pub use annotate_derive::*;
pub use color::ColorProfile;
pub use de::{from_str, Deserialize, Deserializer};
pub use doc_iter::DocPath;
pub use document::Document;
pub use document::{BytesFormat, CommentFormat, Document, StrFormat};
pub use error::Error;
pub use integer::{Int, IntValue};
pub use json::Json;
Expand Down
50 changes: 50 additions & 0 deletions src/partial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::annotate::{IsDeserializer, IsSerializer};
use crate::Deserializer as AnnotatedDeserializer;
use crate::{Document, Error};

impl Serialize for Document {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_serde_annotate() {
// If `serializer` is the correct type, then we can clone the
// Document node and return it.
let r: Result<Document, Error> = Ok(self.clone());
let result = unsafe {
// We have to transmute because the we can't determine at compile
// time that `Result<Document, Error>` is the same type as
// `Result<S::Ok, S::Error>`. If the serializer is
// `AnnotatedSerializer`, then it must be the same.
std::mem::transmute_copy(&r)
};
std::mem::forget(r);
result
} else {
Err(serde::ser::Error::custom("Serializing document nodes is only supported with serde_annotate::AnnotatedSerializer"))
}
}
}

impl<'de> Deserialize<'de> for Document {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_serde_annotate() {
unsafe {
// If the deserializer is ours, then we can simply clone the
// deserializer's document node.
let dsz: &AnnotatedDeserializer = std::mem::transmute_copy(&deserializer);
std::mem::forget(deserializer);
Ok(dsz.doc.clone())
}
} else {
Err(serde::de::Error::custom(
"Deserializing document nodes is only supported with serde_annotate::Deserializer",
))
}
}
}
12 changes: 12 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,15 @@ rust_test(
"//third_party/rust/crates:serde",
],
)

rust_test(
name = "test_partial",
srcs = ["test_partial.rs"],
edition = "2021",
deps = [
"//:serde_annotate",
"//third_party/rust/crates:anyhow",
"//third_party/rust/crates:serde",
"//third_party/rust/crates:serde_json",
],
)
87 changes: 87 additions & 0 deletions tests/test_partial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![feature(min_specialization)]
use anyhow::Result;
use serde_annotate::serialize;
use serde_annotate::{Document, StrFormat};

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Partial {
n: i32,
doc: Document,
}

const SERIALIZE_RESULT: &str = r#"{
n: 5,
doc: [
"Hello",
"world"
]
}"#;

#[test]
fn test_partial_serialize() -> Result<()> {
let p = Partial {
n: 5,
doc: Document::Sequence(vec![
Document::String("Hello".into(), StrFormat::Standard),
Document::String("world".into(), StrFormat::Standard),
]),
};
let s = serialize(&p)?.to_json5().to_string();
assert_eq!(s, SERIALIZE_RESULT);
Ok(())
}

#[test]
fn test_partial_serialize_error() -> Result<()> {
let p = Partial {
n: 5,
doc: Document::Sequence(vec![
Document::String("Hello".into(), StrFormat::Standard),
Document::String("world".into(), StrFormat::Standard),
]),
};
let s = serde_json::to_string_pretty(&p);
assert!(s.is_err());
assert_eq!(
s.unwrap_err().to_string(),
"Serializing document nodes is only supported with serde_annotate::AnnotatedSerializer"
);
Ok(())
}

#[test]
fn test_partial_deserialize() -> Result<()> {
let doc = r#"{
n: 10,
doc: {
# A comment
key: "value",
i: 5,
j: 10,
}
}"#;
let p = serde_annotate::from_str::<Partial>(doc)?;
assert_eq!(p.n, 10);
let Document::Mapping(m) = p.doc else {
panic!("Expecting Document::Mapping");
};
let (k, v) = m[0].as_kv()?;
assert_eq!(k.as_str()?, "key");
assert_eq!(v.as_str()?, "value");
let (k, v) = m[1].as_kv()?;
assert_eq!(k.as_str()?, "i");
assert_eq!(u32::try_from(v)?, 5);
let (k, v) = m[2].as_kv()?;
assert_eq!(k.as_str()?, "j");
assert_eq!(u32::try_from(v)?, 10);
Ok(())
}

#[test]
fn test_partial_deserialize_error() -> Result<()> {
let p = serde_json::from_str::<Partial>(r#"{"n":5, "doc": []}"#);
assert!(p.is_err());
assert_eq!(p.unwrap_err().to_string(),
"Deserializing document nodes is only supported with serde_annotate::Deserializer at line 1 column 15");
Ok(())
}

0 comments on commit f458c7b

Please sign in to comment.