Skip to content

Commit

Permalink
Merge pull request #17 from not-my-profile/fix-empty-maps-and-seqs
Browse files Browse the repository at this point in the history
fix(serde): support deserializing empty scalars
  • Loading branch information
kinnison authored Oct 17, 2024
2 parents a5ea68b + 20f5d5b commit 2d2ba61
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 16 deletions.
17 changes: 4 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions marked-yaml/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# marked-yaml changelog

## [Unreleased]

### Bug fixes

* serde: Empty scalars can now be deserialized as a mapping, sequence or unit.
2 changes: 1 addition & 1 deletion marked-yaml/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ serde-path = ["serde", "dep:serde_path_to_error"]

[dependencies]
doc-comment = "0.3"
yaml-rust = { version = "0.8", package = "yaml-rust2" }
yaml-rust = { version = "0.9", package = "yaml-rust2" }
hashlink = "0.9.0"
serde = { version = "1.0.194", optional = true, features = ["derive"] }
serde_path_to_error = { version = "0.1.16", optional = true }
Expand Down
4 changes: 4 additions & 0 deletions marked-yaml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#![cfg_attr(
feature = "serde",
doc = r#"
## Serde
Should you so choose, you may use serde to deserialise YAML
strings directly into structures, any amount of which could be annotated
Expand All @@ -51,6 +52,9 @@ assert_eq!(roles["User"].span().start().copied(), Some(Marker::new(0, 2, 7)));
You do not have to have all values [`Spanned`], and you can deserialize from an already
parsed set of nodes with [`from_node`] instead.
Empty scalars can be deserialized to empty sequences, maps, the unit type `()`
and structs with a `#[serde(default)]` attribute.
"#
)]
#![deny(missing_docs)]
Expand Down
74 changes: 72 additions & 2 deletions marked-yaml/src/spanned_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,10 @@ impl<'de> Deserializer<'de> for MarkedScalarNodeDeserializer<'de> {
return visitor.visit_map(SpannedDeserializer::new(self.node));
}

if self.node.is_empty_scalar() {
return visitor.visit_map(EmptyMap);
}

self.deserialize_any(visitor)
}

Expand All @@ -1070,13 +1074,79 @@ impl<'de> Deserializer<'de> for MarkedScalarNodeDeserializer<'de> {
visitor.visit_enum(self.node.as_str().into_deserializer())
}

fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.node.is_empty_scalar() {
return visitor.visit_map(EmptyMap);
}

self.deserialize_any(visitor)
}

fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.node.is_empty_scalar() {
return visitor.visit_seq(EmptySeq);
}

self.deserialize_any(visitor)
}

fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.node.is_empty_scalar() {
return visitor.visit_unit();
}

self.deserialize_any(visitor)
}

forward_to_deserialize_any! [
char str string bytes byte_buf
unit unit_struct newtype_struct seq tuple tuple_struct map
char str string bytes byte_buf tuple
unit_struct newtype_struct tuple_struct
identifier ignored_any
];
}

struct EmptyMap;

impl<'de> MapAccess<'de> for EmptyMap {
type Error = Error; // FUTURE: change to never type once stable

fn next_key_seed<K>(&mut self, _seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: serde::de::DeserializeSeed<'de>,
{
Ok(None)
}

fn next_value_seed<V>(&mut self, _seed: V) -> Result<V::Value, Self::Error>
where
V: serde::de::DeserializeSeed<'de>,
{
unreachable!()
}
}

struct EmptySeq;

impl<'de> SeqAccess<'de> for EmptySeq {
type Error = Error; // FUTURE: change to never type once stable

fn next_element_seed<T>(&mut self, _seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: serde::de::DeserializeSeed<'de>,
{
Ok(None)
}
}

// -------------------------------------------------------------------------------

type MappingValueSeq<'de> = hashlink::linked_hash_map::Iter<'de, MarkedScalarNode, Node>;
Expand Down
5 changes: 5 additions & 0 deletions marked-yaml/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,11 @@ Note: this also applies for deserializing nodes via serde.
None
}
}

#[cfg(feature = "serde")]
pub(crate) fn is_empty_scalar(&self) -> bool {
self.value.is_empty() && self.may_coerce
}
}

impl<'a> From<&'a str> for MarkedScalarNode {
Expand Down
47 changes: 47 additions & 0 deletions marked-yaml/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,50 @@ fn parse_fails_coerce() {
assert!(s.starts_with("invalid type: string"));
assert!(s.contains("expected u8"));
}

#[test]
fn empty_scalar_map() {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Foo {
foo: HashMap<String, usize>,
}
let _: Foo = from_yaml(0, "foo:").unwrap();
}

#[test]
fn empty_scalar_seq() {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Foo {
foo: Vec<String>,
}
let _: Foo = from_yaml(0, "foo:").unwrap();
}

#[test]
fn empty_scalar_unit() {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Foo {
foo: (),
}
let _: Foo = from_yaml(0, "foo:").unwrap();
}

#[test]
fn empty_scalar_struct_with_default() {
#[allow(dead_code)]
#[derive(Default, Debug, Deserialize)]
struct Bar {
buz: Option<String>,
}

#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Foo {
#[serde(default)]
bar: Bar,
}
let _: Foo = from_yaml(0, "bar:").unwrap();
}

0 comments on commit 2d2ba61

Please sign in to comment.