Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support partial serialization & deserialization #7

Merged
merged 1 commit into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(())
}