diff --git a/src/de.rs b/src/de.rs index 959ad302..337daa3c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -997,20 +997,9 @@ impl<'de, 'a, 'r> de::Deserializer<'de> for &'r mut Deserializer<'a> { } } -/// Deserialize an instance of type `T` from a string of YAML text. -/// -/// This conversion can fail if the structure of the Value does not match the -/// structure expected by `T`, for example if `T` is a struct type but the Value -/// contains something other than a YAML map. It can also fail if the structure -/// is correct but `T`'s implementation of `Deserialize` decides that something -/// is wrong with the data, for example required struct fields are missing from -/// the YAML map or some number is too big to fit in the expected primitive -/// type. -/// -/// YAML currently does not support zero-copy deserialization. -pub fn from_str(s: &str) -> Result +fn from_str_with(s: &str, op: F) -> Result where - T: DeserializeOwned, + F: FnOnce(&mut Deserializer) -> Result, { let mut parser = Parser::new(s.chars()); let mut loader = Loader { @@ -1024,7 +1013,7 @@ where Err(private::error_end_of_stream()) } else { let mut pos = 0; - let t = Deserialize::deserialize(&mut Deserializer { + let t = op(&mut Deserializer { events: &loader.events, aliases: &loader.aliases, pos: &mut pos, @@ -1039,6 +1028,64 @@ where } } +fn from_reader_with(mut rdr: R, op: F) -> Result +where + R: io::Read, + F: FnOnce(&str) -> Result, +{ + let mut bytes = Vec::new(); + rdr.read_to_end(&mut bytes).map_err(private::error_io)?; + let s = str::from_utf8(&bytes).map_err(private::error_str_utf8)?; + op(s) +} + +fn from_slice_with(v: &[u8], op: F) -> Result +where + F: FnOnce(&str) -> Result, +{ + let s = str::from_utf8(v).map_err(private::error_str_utf8)?; + op(s) +} + +/// Deserialize an instance of type `T` from a string of YAML text. +/// +/// This conversion can fail if the structure of the Value does not match the +/// structure expected by `T`, for example if `T` is a struct type but the Value +/// contains something other than a YAML map. It can also fail if the structure +/// is correct but `T`'s implementation of `Deserialize` decides that something +/// is wrong with the data, for example required struct fields are missing from +/// the YAML map or some number is too big to fit in the expected primitive +/// type. +/// +/// YAML currently does not support zero-copy deserialization. +pub fn from_str(s: &str) -> Result +where + T: DeserializeOwned, +{ + // This closure is required because Rust can't currently seem to understand + // that a for fn(T) is also a FnOnce(Type) where Type: Trait. + #[allow(redundant_closure)] + from_str_with(s, |de| Deserialize::deserialize(de)) +} + +/// Deserialize an instance of type `T` from a string of YAML text with a seed. +/// +/// This conversion can fail if the structure of the Value does not match the +/// structure expected by `T`, for example if `T` is a struct type but the Value +/// contains something other than a YAML map. It can also fail if the structure +/// is correct but `T`'s implementation of `Deserialize` decides that something +/// is wrong with the data, for example required struct fields are missing from +/// the YAML map or some number is too big to fit in the expected primitive +/// type. +/// +/// YAML currently does not support zero-copy deserialization. +pub fn from_str_seed(s: &str, seed: S) -> Result +where + S: for<'de> DeserializeSeed<'de, Value = T>, +{ + from_str_with(s, |de| seed.deserialize(de)) +} + /// Deserialize an instance of type `T` from an IO stream of YAML. /// /// This conversion can fail if the structure of the Value does not match the @@ -1048,15 +1095,29 @@ where /// is wrong with the data, for example required struct fields are missing from /// the YAML map or some number is too big to fit in the expected primitive /// type. -pub fn from_reader(mut rdr: R) -> Result +pub fn from_reader(rdr: R) -> Result where R: io::Read, T: DeserializeOwned, { - let mut bytes = Vec::new(); - rdr.read_to_end(&mut bytes).map_err(private::error_io)?; - let s = str::from_utf8(&bytes).map_err(private::error_str_utf8)?; - from_str(s) + from_reader_with(rdr, from_str) +} + +/// Deserialize an instance of type `T` from an IO stream of YAML with a seed. +/// +/// This conversion can fail if the structure of the Value does not match the +/// structure expected by `T`, for example if `T` is a struct type but the Value +/// contains something other than a YAML map. It can also fail if the structure +/// is correct but `T`'s implementation of `Deserialize` decides that something +/// is wrong with the data, for example required struct fields are missing from +/// the YAML map or some number is too big to fit in the expected primitive +/// type. +pub fn from_reader_seed(rdr: R, seed: S) -> Result +where + R: io::Read, + S: for<'de> DeserializeSeed<'de, Value = T>, +{ + from_reader_with(rdr, |s| from_str_seed(s, seed)) } /// Deserialize an instance of type `T` from bytes of YAML text. @@ -1074,6 +1135,23 @@ pub fn from_slice(v: &[u8]) -> Result where T: DeserializeOwned, { - let s = str::from_utf8(v).map_err(private::error_str_utf8)?; - from_str(s) + from_slice_with(v, from_str) +} + +/// Deserialize an instance of type `T` from bytes of YAML text with a seed. +/// +/// This conversion can fail if the structure of the Value does not match the +/// structure expected by `T`, for example if `T` is a struct type but the Value +/// contains something other than a YAML map. It can also fail if the structure +/// is correct but `T`'s implementation of `Deserialize` decides that something +/// is wrong with the data, for example required struct fields are missing from +/// the YAML map or some number is too big to fit in the expected primitive +/// type. +/// +/// YAML currently does not support zero-copy deserialization. +pub fn from_slice_seed(v: &[u8], seed: S) -> Result +where + S: for<'de> DeserializeSeed<'de, Value = T>, +{ + from_slice_with(v, |s| from_str_seed(s, seed)) } diff --git a/src/lib.rs b/src/lib.rs index fcf14182..3ca1b626 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,11 @@ pub use self::mapping::Mapping; pub use self::ser::{to_string, to_vec, to_writer}; pub use self::value::{from_value, to_value, Index, Number, Sequence, Value}; +/// Deserialization with seeds +pub mod seed { + pub use super::de::{from_reader_seed, from_slice_seed, from_str_seed}; +} + mod de; mod error; mod mapping; diff --git a/tests/test_de.rs b/tests/test_de.rs index 6b141ef4..c971e51c 100644 --- a/tests/test_de.rs +++ b/tests/test_de.rs @@ -25,6 +25,18 @@ where serde_yaml::from_str::(yaml).unwrap(); } +fn test_de_seed(yaml: &str, seed: S, expected: &T) +where + T: PartialEq + Debug, + S: for<'de> serde::de::DeserializeSeed<'de, Value = T>, +{ + let deserialized: T = serde_yaml::seed::from_str_seed(yaml, seed).unwrap(); + assert_eq!(*expected, deserialized); + + serde_yaml::from_str::(yaml).unwrap(); + serde_yaml::from_str::(yaml).unwrap(); +} + #[test] fn test_alias() { let yaml = unindent( @@ -352,3 +364,40 @@ fn test_numbers() { } } } + +#[test] +fn test_stateful() { + struct Seed(i64); + + impl<'de> serde::de::DeserializeSeed<'de> for Seed { + type Value = i64; + fn deserialize(self, deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + struct Visitor(i64); + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = i64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "an integer") + } + + fn visit_i64(self, v: i64) -> Result { + Ok(v * self.0) + } + + fn visit_u64(self, v: u64) -> Result { + Ok(v as i64 * self.0) + } + } + + deserializer.deserialize_any(Visitor(self.0)) + } + } + + let cases = [("3", 5, 15), ("6", 7, 42), ("-5", 9, -45)]; + for &(yaml, seed, expected) in &cases { + test_de_seed(yaml, Seed(seed), &expected); + } +}