Skip to content

RUST-1764 Add HumanReadable convenience wrapper #471

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

Merged
merged 4 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 0 additions & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ combine_control_expr = false
comment_width = 100
condense_wildcard_suffixes = true
format_strings = true
normalize_comments = true
use_try_shorthand = true
wrap_comments = true
imports_layout = "HorizontalVertical"
Expand Down
10 changes: 9 additions & 1 deletion src/de/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use serde::{
use crate::{
oid::ObjectId,
raw::{RawBinaryRef, RAW_ARRAY_NEWTYPE, RAW_BSON_NEWTYPE, RAW_DOCUMENT_NEWTYPE},
serde_helpers::HUMAN_READABLE_NEWTYPE,
spec::{BinarySubtype, ElementType},
uuid::UUID_NEWTYPE_NAME,
Bson,
Expand Down Expand Up @@ -51,6 +52,8 @@ pub(crate) struct Deserializer<'de> {
/// but given that there's no difference between deserializing an embedded document and a
/// top level one, the distinction isn't necessary.
current_type: ElementType,

human_readable: bool,
}

/// Enum used to determine what the type of document being deserialized is in
Expand All @@ -65,6 +68,7 @@ impl<'de> Deserializer<'de> {
Self {
bytes: BsonBuf::new(buf, utf8_lossy),
current_type: ElementType::EmbeddedDocument,
human_readable: false,
}
}

Expand Down Expand Up @@ -454,12 +458,16 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {

self.deserialize_next(visitor, DeserializerHint::RawBson)
}
HUMAN_READABLE_NEWTYPE => {
self.human_readable = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be an issue when a HumanReadable<T> is nested in a struct with non-wrapped types following it? e.g.

struct Test {
    human_readable: HumanReadable<Document>,
    non_human_readable: Document,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I've fixed the logic and updated the test to flag that kind of failure.

visitor.visit_newtype_struct(self)
}
_ => visitor.visit_newtype_struct(self),
}
}

fn is_human_readable(&self) -> bool {
false
self.human_readable
}

forward_to_deserialize_any! {
Expand Down
7 changes: 6 additions & 1 deletion src/de/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
document::{Document, IntoIter},
oid::ObjectId,
raw::{RawBsonRef, RAW_ARRAY_NEWTYPE, RAW_BSON_NEWTYPE, RAW_DOCUMENT_NEWTYPE},
serde_helpers::HUMAN_READABLE_NEWTYPE,
spec::BinarySubtype,
uuid::UUID_NEWTYPE_NAME,
Binary,
Expand Down Expand Up @@ -815,7 +816,7 @@ impl<'de> de::Deserializer<'de> for Deserializer {

#[inline]
fn deserialize_newtype_struct<V>(
self,
mut self,
name: &'static str,
visitor: V,
) -> crate::de::Result<V::Value>
Expand Down Expand Up @@ -848,6 +849,10 @@ impl<'de> de::Deserializer<'de> for Deserializer {

self.deserialize_next(visitor, DeserializerHint::RawBson)
}
HUMAN_READABLE_NEWTYPE => {
self.options.human_readable = Some(true);
visitor.visit_newtype_struct(self)
}
_ => visitor.visit_newtype_struct(self),
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/ser/raw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::{write_binary, write_cstring, write_f64, write_i32, write_i64, write_
use crate::{
raw::{RAW_ARRAY_NEWTYPE, RAW_DOCUMENT_NEWTYPE},
ser::{Error, Result},
serde_helpers::HUMAN_READABLE_NEWTYPE,
spec::{BinarySubtype, ElementType},
uuid::UUID_NEWTYPE_NAME,
};
Expand All @@ -30,6 +31,8 @@ pub(crate) struct Serializer {

/// Hint provided by the type being serialized.
hint: SerializerHint,

human_readable: bool,
}

/// Various bits of information that the serialized type can provide to the serializer to
Expand Down Expand Up @@ -60,6 +63,7 @@ impl Serializer {
bytes: Vec::new(),
type_index: 0,
hint: SerializerHint::None,
human_readable: false,
}
}

Expand Down Expand Up @@ -115,7 +119,7 @@ impl<'a> serde::Serializer for &'a mut Serializer {
type SerializeStructVariant = VariantSerializer<'a>;

fn is_human_readable(&self) -> bool {
false
self.human_readable
}

#[inline]
Expand Down Expand Up @@ -267,6 +271,7 @@ impl<'a> serde::Serializer for &'a mut Serializer {
UUID_NEWTYPE_NAME => self.hint = SerializerHint::Uuid,
RAW_DOCUMENT_NEWTYPE => self.hint = SerializerHint::RawDocument,
RAW_ARRAY_NEWTYPE => self.hint = SerializerHint::RawArray,
HUMAN_READABLE_NEWTYPE => self.human_readable = true,
_ => {}
}
value.serialize(self)
Expand Down
7 changes: 6 additions & 1 deletion src/ser/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
extjson,
oid::ObjectId,
raw::{RawDbPointerRef, RawRegexRef, RAW_ARRAY_NEWTYPE, RAW_DOCUMENT_NEWTYPE},
serde_helpers::HUMAN_READABLE_NEWTYPE,
spec::BinarySubtype,
uuid::UUID_NEWTYPE_NAME,
Binary,
Expand Down Expand Up @@ -296,7 +297,7 @@ impl ser::Serializer for Serializer {

#[inline]
fn serialize_newtype_struct<T: ?Sized>(
self,
mut self,
name: &'static str,
value: &T,
) -> crate::ser::Result<Bson>
Expand Down Expand Up @@ -348,6 +349,10 @@ impl ser::Serializer for Serializer {
b
))),
},
HUMAN_READABLE_NEWTYPE => {
self.options.human_readable = Some(true);
value.serialize(self)
}
_ => value.serialize(self),
}
}
Expand Down
45 changes: 43 additions & 2 deletions src/serde_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Collection of helper functions for serializing to and deserializing from BSON using Serde

use std::{convert::TryFrom, result::Result};
use std::{convert::TryFrom, marker::PhantomData, result::Result};

use serde::{ser, Serialize, Serializer};
use serde::{de::Visitor, ser, Deserialize, Serialize, Serializer};

use crate::oid::ObjectId;

Expand Down Expand Up @@ -794,3 +794,44 @@ pub mod timestamp_as_u32 {
Ok(Timestamp { time, increment: 0 })
}
}

/// Wrapping a type in `HumanReadable` signals to the BSON serde integration that it and all
/// recursively contained types should be handled as if
/// [`SerializerOptions::human_readable`](crate::SerializerOptions::human_readable) and
/// [`DeserializerOptions::human_readable`](crate::DeserializerOptions::human_readable) are
/// set to `true`.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct HumanReadable<T>(pub T);

pub(crate) const HUMAN_READABLE_NEWTYPE: &str = "$__bson_private_human_readable";

impl<T: Serialize> Serialize for HumanReadable<T> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct(HUMAN_READABLE_NEWTYPE, &self.0)
}
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for HumanReadable<T> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V<T>(PhantomData<fn() -> T>);
impl<'de, T: Deserialize<'de>> Visitor<'de> for V<T> {
type Value = HumanReadable<T>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("HumanReadable wrapper")
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
T::deserialize(deserializer).map(HumanReadable)
}
}
deserializer.deserialize_newtype_struct(HUMAN_READABLE_NEWTYPE, V(PhantomData))
}
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod binary_subtype;
mod datetime;
mod modules;
mod serde;
mod serde_helpers;
mod spec;

use modules::TestLock;
Expand Down
128 changes: 128 additions & 0 deletions src/tests/serde_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use serde::{de::Visitor, Deserialize, Serialize};

use crate::serde_helpers::HumanReadable;

#[test]
fn human_readable_wrapper() {
#[derive(PartialEq, Eq, Debug)]
struct Detector {
serialized_as: bool,
deserialized_as: bool,
}
impl Detector {
fn new() -> Self {
Detector {
serialized_as: false,
deserialized_as: false,
}
}
}
impl Serialize for Detector {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = if serializer.is_human_readable() {
"human readable"
} else {
"not human readable"
};
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for Detector {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = bool;

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

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
"human readable" => Ok(true),
"not human readable" => Ok(false),
_ => Err(E::custom(format!("invalid detector string {:?}", v))),
}
}
}
let deserialized_as = deserializer.is_human_readable();
let serialized_as = deserializer.deserialize_str(V)?;
Ok(Detector {
serialized_as,
deserialized_as,
})
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
struct Data {
outer: Detector,
wrapped: HumanReadable<Detector>,
inner: HumanReadable<SubData>,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
struct SubData {
value: Detector,
}
let data = Data {
outer: Detector::new(),
wrapped: HumanReadable(Detector::new()),
inner: HumanReadable(SubData {
value: Detector::new(),
}),
};
let bson = crate::to_bson_with_options(
&data,
crate::SerializerOptions::builder()
.human_readable(false)
.build(),
)
.unwrap();
assert_eq!(
bson.as_document().unwrap(),
&doc! {
"outer": "not human readable",
"wrapped": "human readable",
"inner": {
"value": "human readable",
}
}
);

let tripped: Data = crate::from_bson_with_options(
bson,
crate::DeserializerOptions::builder()
.human_readable(false)
.build(),
)
.unwrap();
let expected = Data {
outer: Detector {
serialized_as: false,
deserialized_as: false,
},
wrapped: HumanReadable(Detector {
serialized_as: true,
deserialized_as: true,
}),
inner: HumanReadable(SubData {
value: Detector {
serialized_as: true,
deserialized_as: true,
},
}),
};
assert_eq!(&tripped, &expected);

let bytes = crate::to_vec(&data).unwrap();
let raw_tripped: Data = crate::from_slice(&bytes).unwrap();
assert_eq!(&raw_tripped, &expected);
}