From 5c88c47370cfba0c734a144cc2d4e30d4945a829 Mon Sep 17 00:00:00 2001 From: voidentente Date: Mon, 5 Aug 2024 22:15:46 +0200 Subject: [PATCH 01/19] Add provisional metadata ser in named field pos --- src/lib.rs | 1 + src/meta.rs | 22 ++++++++++++++++++++++ src/ser/mod.rs | 16 ++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/meta.rs diff --git a/src/lib.rs b/src/lib.rs index 36414b1f..e44d43dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub mod de; pub mod ser; pub mod error; +pub mod meta; pub mod value; pub mod extensions; diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 00000000..9d51687e --- /dev/null +++ b/src/meta.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct Meta { + inner: HashMap, +} + +impl Meta { + pub fn new() -> Self { + Self::default() + } + + pub fn insert(&mut self, k: impl Into, v: impl Into) { + self.inner.insert(k.into(), v.into()); + } + + pub fn get(&self, k: &str) -> Option<&str> { + self.inner.get(k).map(String::as_str) + } +} diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 6157f05f..2d63849d 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,6 +7,7 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, + meta::Meta, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; @@ -109,6 +110,8 @@ pub struct PrettyConfig { pub compact_maps: bool, /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, + /// Additional metadata to serialize + pub meta: Meta, } impl PrettyConfig { @@ -359,6 +362,7 @@ impl Default for PrettyConfig { compact_structs: false, compact_maps: false, number_suffixes: false, + meta: Meta::default(), } } } @@ -620,6 +624,10 @@ impl Serializer { Ok(()) } + fn fmt_meta(&self, meta: &str) -> String { + meta.lines().map(|line| format!("/// {line}\n")).collect() + } + #[allow(clippy::unused_self)] fn validate_identifier(&self, name: &str) -> Result<()> { if name.is_empty() || !name.chars().all(is_ident_raw_char) { @@ -1329,6 +1337,14 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if !self.ser.compact_structs() { self.ser.indent()?; + + if let Some((ref config, ref _pretty)) = self.ser.pretty { + if let Some(meta) = config.meta.get(key) { + let s = self.ser.fmt_meta(meta); + self.ser.output.write_str(&s)?; + self.ser.indent()?; + } + } } self.ser.write_identifier(key)?; From 5ed4636b313261fba48cc545aef7e27ef8f6b96d Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 6 Aug 2024 01:32:47 +0200 Subject: [PATCH 02/19] Use hierarchy to fix collision; fix multiline indent bug --- src/meta.rs | 61 +++++++++++++++++++++++++++++++++++++++++++------- src/ser/mod.rs | 56 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 9d51687e..741bad98 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -3,20 +3,65 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct Meta { - inner: HashMap, +pub struct Field { + meta: String, + inner: Option, } -impl Meta { +impl Field { + pub const fn new() -> Self { + Self { + meta: String::new(), + inner: None, + } + } + + pub fn get_meta(&self) -> &str { + self.meta.as_str() + } + + pub fn set_meta(&mut self, meta: impl Into) { + self.meta = meta.into(); + } + + pub fn has_inner(&self) -> bool { + self.inner.is_some() + } + + pub fn set_inner(&mut self, fields: Fields) { + self.inner = Some(fields); + } + + pub fn inner(&self) -> Option<&Fields> { + self.inner.as_ref() + } + + pub fn inner_mut(&mut self) -> Option<&mut Fields> { + self.inner.as_mut() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct Fields { + fields: HashMap, +} + +impl Fields { pub fn new() -> Self { - Self::default() + Self { + fields: HashMap::default(), + } + } + + pub fn field(&self, name: impl AsRef) -> Option<&Field> { + self.fields.get(name.as_ref()) } - pub fn insert(&mut self, k: impl Into, v: impl Into) { - self.inner.insert(k.into(), v.into()); + pub fn field_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { + self.fields.get_mut(name.as_ref()) } - pub fn get(&self, k: &str) -> Option<&str> { - self.inner.get(k).map(String::as_str) + pub fn field_mut_or_default(&mut self, name: impl Into) -> &mut Field { + self.fields.entry(name.into()).or_default() } } diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 2d63849d..3010309e 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,7 +7,7 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, - meta::Meta, + meta::Fields, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; @@ -111,7 +111,7 @@ pub struct PrettyConfig { /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, /// Additional metadata to serialize - pub meta: Meta, + pub meta: Fields, } impl PrettyConfig { @@ -362,7 +362,7 @@ impl Default for PrettyConfig { compact_structs: false, compact_maps: false, number_suffixes: false, - meta: Meta::default(), + meta: Fields::default(), } } } @@ -380,6 +380,7 @@ pub struct Serializer { recursion_limit: Option, // Tracks the number of opened implicit `Some`s, set to 0 on backtracking implicit_some_depth: usize, + field_memory: Vec<&'static str>, } impl Serializer { @@ -432,6 +433,7 @@ impl Serializer { newtype_variant: false, recursion_limit: options.recursion_limit, implicit_some_depth: 0, + field_memory: Vec::new(), }) } @@ -624,10 +626,6 @@ impl Serializer { Ok(()) } - fn fmt_meta(&self, meta: &str) -> String { - meta.lines().map(|line| format!("/// {line}\n")).collect() - } - #[allow(clippy::unused_self)] fn validate_identifier(&self, name: &str) -> Result<()> { if name.is_empty() || !name.chars().all(is_ident_raw_char) { @@ -1321,6 +1319,8 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { where T: ?Sized + Serialize, { + self.ser.field_memory.push(key); + if let State::First = self.state { self.state = State::Rest; } else { @@ -1338,11 +1338,40 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if !self.ser.compact_structs() { self.ser.indent()?; - if let Some((ref config, ref _pretty)) = self.ser.pretty { - if let Some(meta) = config.meta.get(key) { - let s = self.ser.fmt_meta(meta); - self.ser.output.write_str(&s)?; - self.ser.indent()?; + if let Some((ref config, _)) = self.ser.pretty { + let mut iter = self.ser.field_memory.iter(); + + let name = iter.next().expect( + "is always at least one, because we push one at the beginning of this function", + ); + let mut field = config.meta.field(name); + + while let Some(name) = iter.next() { + let Some(some_field) = field else { break }; + + let Some(fields) = some_field.inner() else { + field = None; + break; + }; + + field = fields.field(name); + } + + if let Some(field) = field { + for line in field.get_meta().lines() { + let s = format!("/// {line}\n"); + self.ser.output.write_str(&s)?; + + // FIXME: Can't call `self.ser.indent` because we immutably borrow ser via field, + // but we don't need the entire ser mutable, only ser.output so this works + if let Some((ref config, ref pretty)) = self.ser.pretty { + if pretty.indent <= config.depth_limit { + for _ in 0..pretty.indent { + self.ser.output.write_str(&config.indentor)?; + } + } + } + } } } } @@ -1356,6 +1385,8 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; + self.ser.field_memory.pop(); + Ok(()) } @@ -1376,6 +1407,7 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if !self.newtype_variant { self.ser.output.write_char(')')?; } + Ok(()) } } From 4edf36035e7df92bcb0086b1a20b23cba8ef84d5 Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 6 Aug 2024 03:20:33 +0200 Subject: [PATCH 03/19] Add CHANGELOG.md entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b6f41c..61528696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: Enforce that ron always writes valid UTF-8 ([#488](https://github.com/ron-rs/ron/pull/488)) - Add convenient `Value::from` impls ([#498](https://github.com/ron-rs/ron/pull/498)) - Add new extension `explicit_struct_names` which requires that struct names are included during deserialization ([#522](https://github.com/ron-rs/ron/pull/522)) +- Add new metadata serialization support in named field position via `PrettyConfig` ([#544](https://github.com/ron-rs/ron/pull/544)) ### Format Changes From 1664ad1c6364f1dbd9c23219a4c66398288ee1ed Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 6 Aug 2024 03:26:47 +0200 Subject: [PATCH 04/19] Touch up on API, add some docs, fix inline call to indent --- src/meta.rs | 72 +++++++++++++++++++++++++++++++++++--------------- src/ser/mod.rs | 28 +++++++++----------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 741bad98..8c455be1 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -2,66 +2,96 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; +/// The metadata and inner [Fields] of a field #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Field { meta: String, - inner: Option, + fields: Option, } impl Field { - pub const fn new() -> Self { + /// Create a new empty field metadata + pub const fn empty() -> Self { Self { meta: String::new(), - inner: None, + fields: None, } } - pub fn get_meta(&self) -> &str { - self.meta.as_str() + /// Create a new field metadata + pub fn new(meta: impl Into, fields: Option) -> Self { + Self { + meta: meta.into(), + fields, + } } - pub fn set_meta(&mut self, meta: impl Into) { + /// Set the metadata of this field + pub fn with_meta(&mut self, meta: impl Into) -> &mut Self { self.meta = meta.into(); + self } - pub fn has_inner(&self) -> bool { - self.inner.is_some() + /// Set the inner fields of this field + pub fn with_fields(&mut self, fields: Option) -> &mut Self { + self.fields = fields; + self + } + + /// Get the metadata of this field + pub fn get_meta(&self) -> &str { + self.meta.as_str() } - pub fn set_inner(&mut self, fields: Fields) { - self.inner = Some(fields); + /// Return whether this field has inner fields + pub fn has_fields(&self) -> bool { + self.fields.is_some() } - pub fn inner(&self) -> Option<&Fields> { - self.inner.as_ref() + /// Get a reference to the inner fields of this field, if it has any + pub fn fields(&self) -> Option<&Fields> { + self.fields.as_ref() } - pub fn inner_mut(&mut self) -> Option<&mut Fields> { - self.inner.as_mut() + /// Get a mutable reference to the inner fields of this field, if it has any + pub fn fields_mut(&mut self) -> Option<&mut Fields> { + self.fields.as_mut() } } +/// Mapping of names to [Field]s #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Fields { fields: HashMap, } impl Fields { + /// Return a new, empty metadata field map pub fn new() -> Self { - Self { - fields: HashMap::default(), - } + Self::default() } - pub fn field(&self, name: impl AsRef) -> Option<&Field> { + /// Get a reference to the field with the provided `name`, if it exists + pub fn get_field(&self, name: impl AsRef) -> Option<&Field> { self.fields.get(name.as_ref()) } - pub fn field_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { + /// Get a mutable reference to the field with the provided `name`, if it exists + pub fn get_field_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { self.fields.get_mut(name.as_ref()) } - pub fn field_mut_or_default(&mut self, name: impl Into) -> &mut Field { - self.fields.entry(name.into()).or_default() + /// Get a mutable reference to the field with the provided `name`, + /// inserting an empty [`Field`] if it didn't exist + pub fn field(&mut self, name: impl Into) -> &mut Field { + self.fields.entry(name.into()).or_insert_with(Field::empty) + } +} + +impl> FromIterator<(K, Field)> for Fields { + fn from_iter>(iter: T) -> Self { + Self { + fields: HashMap::from_iter(iter.into_iter().map(|(k, v)| (k.into(), v))), + } } } diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 3010309e..18ed19f1 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -1344,33 +1344,29 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { let name = iter.next().expect( "is always at least one, because we push one at the beginning of this function", ); - let mut field = config.meta.field(name); + let mut field = config.meta.get_field(name); while let Some(name) = iter.next() { let Some(some_field) = field else { break }; - let Some(fields) = some_field.inner() else { + let Some(fields) = some_field.fields() else { field = None; break; }; - field = fields.field(name); + field = fields.get_field(name); } if let Some(field) = field { - for line in field.get_meta().lines() { - let s = format!("/// {line}\n"); - self.ser.output.write_str(&s)?; - - // FIXME: Can't call `self.ser.indent` because we immutably borrow ser via field, - // but we don't need the entire ser mutable, only ser.output so this works - if let Some((ref config, ref pretty)) = self.ser.pretty { - if pretty.indent <= config.depth_limit { - for _ in 0..pretty.indent { - self.ser.output.write_str(&config.indentor)?; - } - } - } + let lines: Vec<_> = field + .get_meta() + .lines() + .map(|line| format!("/// {line}\n")) + .collect(); + + for line in lines { + self.ser.output.write_str(&line)?; + self.ser.indent()?; } } } From 709ff5e25d2f019eda2a36659cfbad1c8752de14 Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 6 Aug 2024 14:21:03 +0200 Subject: [PATCH 05/19] Change let .. else to iterator --- src/ser/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 18ed19f1..022ae2da 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -1344,18 +1344,14 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { let name = iter.next().expect( "is always at least one, because we push one at the beginning of this function", ); - let mut field = config.meta.get_field(name); - while let Some(name) = iter.next() { - let Some(some_field) = field else { break }; - - let Some(fields) = some_field.fields() else { - field = None; - break; - }; - - field = fields.get_field(name); - } + let field = iter + .try_fold(config.meta.get_field(name), |field, name| { + field + .and_then(|field| field.fields()) + .map(|fields| fields.get_field(name)) + }) + .flatten(); if let Some(field) = field { let lines: Vec<_> = field From 191dda63834b2000cf5cde1f32ef0d9542d6cb2c Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 6 Aug 2024 15:34:39 +0200 Subject: [PATCH 06/19] Add build_fields as ergonomic shortcut --- src/meta.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/meta.rs b/src/meta.rs index 8c455be1..8c137792 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -38,6 +38,14 @@ impl Field { self } + /// Ergonomic shortcut for building some inner fields + pub fn build_fields(&mut self, builder: impl FnOnce(&mut Fields)) -> &mut Self { + let mut fields = Fields::default(); + builder(&mut fields); + self.fields = Some(fields); + self + } + /// Get the metadata of this field pub fn get_meta(&self) -> &str { self.meta.as_str() From e586fee1a07fcfb01746ab133514785138492b65 Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 02:32:07 +0200 Subject: [PATCH 07/19] Remove expect, use collect, add must_use --- src/meta.rs | 7 ++++++- src/ser/mod.rs | 11 ++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 8c137792..6adbe08c 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -11,6 +11,7 @@ pub struct Field { impl Field { /// Create a new empty field metadata + #[must_use] pub const fn empty() -> Self { Self { meta: String::new(), @@ -47,16 +48,19 @@ impl Field { } /// Get the metadata of this field + #[must_use] pub fn get_meta(&self) -> &str { self.meta.as_str() } /// Return whether this field has inner fields + #[must_use] pub fn has_fields(&self) -> bool { self.fields.is_some() } /// Get a reference to the inner fields of this field, if it has any + #[must_use] pub fn fields(&self) -> Option<&Fields> { self.fields.as_ref() } @@ -75,6 +79,7 @@ pub struct Fields { impl Fields { /// Return a new, empty metadata field map + #[must_use] pub fn new() -> Self { Self::default() } @@ -99,7 +104,7 @@ impl Fields { impl> FromIterator<(K, Field)> for Fields { fn from_iter>(iter: T) -> Self { Self { - fields: HashMap::from_iter(iter.into_iter().map(|(k, v)| (k.into(), v))), + fields: iter.into_iter().map(|(k, v)| (k.into(), v)).collect(), } } } diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 022ae2da..e12acf85 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,7 +7,7 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, - meta::Fields, + meta::{Field, Fields}, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; @@ -1341,14 +1341,11 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if let Some((ref config, _)) = self.ser.pretty { let mut iter = self.ser.field_memory.iter(); - let name = iter.next().expect( - "is always at least one, because we push one at the beginning of this function", - ); - + let init = iter.next().and_then(|name| config.meta.get_field(name)); let field = iter - .try_fold(config.meta.get_field(name), |field, name| { + .try_fold(init, |field, name| { field - .and_then(|field| field.fields()) + .and_then(Field::fields) .map(|fields| fields.get_field(name)) }) .flatten(); From 5cc47859450e282b9d3a0732331c6503d55a5627 Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 16:51:44 +0200 Subject: [PATCH 08/19] Add test file with named field struct hierarchy test --- tests/544_meta.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/544_meta.rs diff --git a/tests/544_meta.rs b/tests/544_meta.rs new file mode 100644 index 00000000..ee0b6b3e --- /dev/null +++ b/tests/544_meta.rs @@ -0,0 +1,100 @@ +#[test] +fn serialize_field() { + #[derive(serde::Serialize)] + enum PetKind { + Isopod, + } + + #[derive(serde::Serialize)] + struct Pet { + name: &'static str, + age: u8, + kind: PetKind, + } + + #[derive(serde::Serialize)] + struct Person { + name: &'static str, + age: u8, + knows: Vec, + pet: Option, + } + + let value = vec![ + Person { + name: "Alice", + age: 29, + knows: vec![1], + pet: Some(Pet { + name: "Herbert", + age: 7, + kind: PetKind::Isopod, + }), + }, + Person { + name: "Bob", + age: 29, + knows: vec![0], + pet: None, + }, + ]; + + let mut config = ron::ser::PrettyConfig::default(); + + // layer 0 + config + .meta + .field("age") + .with_meta("0@age (person) must be within range 0..256"); + config + .meta + .field("knows") + .with_meta("0@knows (person) must be list of valid person indices"); + config.meta.field("pet").build_fields(|fields| { + // layer 1 + fields + .field("age") + .with_meta("1@age (pet) must be valid range 0..256"); + fields + .field("kind") + .with_meta("1@kind (pet) must be `Isopod`"); + }); + + // provide meta for a field that doesn't exist; + // this should not end up anywhere in the final string + config.meta.field("0").with_meta("unreachable"); + + let s = ron::ser::to_string_pretty(&value, config).unwrap(); + + assert_eq!( + s, + r#"[ + ( + name: "Alice", + /// 0@age (person) must be within range 0..256 + age: 29, + /// 0@knows (person) must be list of valid person indices + knows: [ + 1, + ], + pet: Some(( + name: "Herbert", + /// 1@age (pet) must be valid range 0..256 + age: 7, + /// 1@kind (pet) must be `Isopod` + kind: Isopod, + )), + ), + ( + name: "Bob", + /// 0@age (person) must be within range 0..256 + age: 29, + /// 0@knows (person) must be list of valid person indices + knows: [ + 0, + ], + pet: None, + ), +]"# + ); +} From 84a4c39f17fb203bd0129a8493561f020ff61a7b Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 16:53:18 +0200 Subject: [PATCH 09/19] Touch up on API, add doctests --- src/meta.rs | 186 ++++++++++++++++++++++++++++++++++++++++--------- src/ser/mod.rs | 8 +-- 2 files changed, 156 insertions(+), 38 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 6adbe08c..626759e4 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; -/// The metadata and inner [Fields] of a field +/// The metadata and inner [Fields] of a field. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Field { meta: String, @@ -10,7 +10,7 @@ pub struct Field { } impl Field { - /// Create a new empty field metadata + /// Create a new empty field metadata. #[must_use] pub const fn empty() -> Self { Self { @@ -19,7 +19,7 @@ impl Field { } } - /// Create a new field metadata + /// Create a new field metadata. pub fn new(meta: impl Into, fields: Option) -> Self { Self { meta: meta.into(), @@ -27,75 +27,195 @@ impl Field { } } - /// Set the metadata of this field + /// Get the metadata of this field. + #[must_use] + pub fn meta(&self) -> &str { + &self.meta + } + + /// Set the metadata of this field. + /// + /// ``` + /// let mut field = Field::empty(); + /// + /// assert_eq!(field.meta(), ""); + /// + /// field.with_meta("some meta"); + /// + /// assert_eq!(field.meta(), "some meta"); + /// ``` pub fn with_meta(&mut self, meta: impl Into) -> &mut Self { self.meta = meta.into(); self } - /// Set the inner fields of this field - pub fn with_fields(&mut self, fields: Option) -> &mut Self { - self.fields = fields; - self + /// Return whether the Field has metadata. + /// + /// ``` + /// let mut field = Field::empty(); + /// + /// assert!(!field.has_meta()); + /// + /// field.with_meta("some"); + /// + /// assert!(field.has_meta()); + /// ``` + pub fn has_meta(&self) -> bool { + !self.meta.is_empty() } - /// Ergonomic shortcut for building some inner fields - pub fn build_fields(&mut self, builder: impl FnOnce(&mut Fields)) -> &mut Self { - let mut fields = Fields::default(); - builder(&mut fields); - self.fields = Some(fields); - self + /// Get a reference to the inner fields of this field, if it has any. + #[must_use] + pub fn fields(&self) -> Option<&Fields> { + self.fields.as_ref() } - /// Get the metadata of this field - #[must_use] - pub fn get_meta(&self) -> &str { - self.meta.as_str() + /// Get a mutable reference to the inner fields of this field, if it has any. + pub fn fields_mut(&mut self) -> Option<&mut Fields> { + self.fields.as_mut() } - /// Return whether this field has inner fields + /// Return whether this field has inner fields. + /// + /// ``` + /// let mut field = Field::empty(); + /// + /// assert!(!field.has_fields()); + /// + /// field.with_fields(Some(Fields::default())); + /// + /// assert!(field.has_fields()); + /// ``` #[must_use] pub fn has_fields(&self) -> bool { self.fields.is_some() } - /// Get a reference to the inner fields of this field, if it has any - #[must_use] - pub fn fields(&self) -> Option<&Fields> { - self.fields.as_ref() + /// Set the inner fields of this field. + /// + /// ``` + /// let mut field = Field::empty(); + /// + /// assert!(!field.has_fields()); + /// + /// field.with_fields(Some(Fields::default())); + /// + /// assert!(field.has_fields()); + /// + /// field.with_fields(None); + /// + /// assert!(!field.has_fields()); + /// ``` + pub fn with_fields(&mut self, fields: Option) -> &mut Self { + self.fields = fields; + self } - /// Get a mutable reference to the inner fields of this field, if it has any - pub fn fields_mut(&mut self) -> Option<&mut Fields> { - self.fields.as_mut() + /// Ergonomic shortcut for building some inner fields. + /// + /// ``` + /// let field = Field::empty().build_fields(|fields| { + /// fields.field("inner field"); + /// }); + /// + /// assert!(field.fields().and_then(|fields| fields.contains("inner field"))); + /// ``` + pub fn build_fields(&mut self, builder: impl FnOnce(&mut Fields)) -> &mut Self { + let mut fields = Fields::default(); + builder(&mut fields); + self.with_fields(Some(fields)); + self } } -/// Mapping of names to [Field]s +/// Mapping of names to [Field]s. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Fields { fields: HashMap, } impl Fields { - /// Return a new, empty metadata field map + /// Return a new, empty metadata field map. #[must_use] pub fn new() -> Self { Self::default() } - /// Get a reference to the field with the provided `name`, if it exists - pub fn get_field(&self, name: impl AsRef) -> Option<&Field> { + /// Return whether this field map contains no fields. + /// + /// ``` + /// let mut fields = Fields::default(); + /// + /// assert!(fields.is_empty()); + /// + /// fields.insert("", Field::empty()); + /// + /// assert!(!fields.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.fields.is_empty() + } + + /// Return whether this field map contains a field with the given name. + /// + /// ``` + /// let fields: Fields = [("a thing", Field::empty())].collect(); + /// + /// assert!(fields.contains("a thing")); + /// assert!(!fields.contains("not a thing")); + /// ``` + pub fn contains(&self, name: impl AsRef) -> bool { + self.fields.contains_key(name.as_ref()) + } + + /// Get a reference to the field with the provided `name`, if it exists. + /// + /// ``` + /// let fields: Fields = [("a thing", Field::empty())].collect(); + /// + /// assert!(fields.get("a thing").is_some()) + /// assert!(fields.get("not a thing").is_none()); + /// ``` + pub fn get(&self, name: impl AsRef) -> Option<&Field> { self.fields.get(name.as_ref()) } - /// Get a mutable reference to the field with the provided `name`, if it exists - pub fn get_field_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { + /// Get a mutable reference to the field with the provided `name`, if it exists. + /// + /// ``` + /// let mut fields: Fields = [("a thing", Field::empty())].collect(); + /// + /// assert!(fields.get_mut("a thing").is_some()) + /// assert!(fields.get_mut("not a thing").is_none()); + /// ``` + pub fn get_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { self.fields.get_mut(name.as_ref()) } + /// Insert a field with the given name into the map. + /// + /// ``` + /// let mut fields = Fields::default(); + /// + /// assert!(fields.insert("field").is_none()); + /// assert!(fields.insert("field").is_some()); + /// ``` + pub fn insert(&mut self, name: impl Into, field: Field) -> Option { + self.fields.insert(name.into(), field) + } + /// Get a mutable reference to the field with the provided `name`, - /// inserting an empty [`Field`] if it didn't exist + /// inserting an empty [`Field`] if it didn't exist. + /// + /// ``` + /// let mut fields = Fields::default(); + /// + /// assert!(!fields.contains("thing")); + /// + /// fields.field("thing"); + /// + /// assert!(fields.contains("thing")); + /// ``` pub fn field(&mut self, name: impl Into) -> &mut Field { self.fields.entry(name.into()).or_insert_with(Field::empty) } diff --git a/src/ser/mod.rs b/src/ser/mod.rs index e12acf85..83454068 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -1341,18 +1341,16 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if let Some((ref config, _)) = self.ser.pretty { let mut iter = self.ser.field_memory.iter(); - let init = iter.next().and_then(|name| config.meta.get_field(name)); + let init = iter.next().and_then(|name| config.meta.get(name)); let field = iter .try_fold(init, |field, name| { - field - .and_then(Field::fields) - .map(|fields| fields.get_field(name)) + field.and_then(Field::fields).map(|fields| fields.get(name)) }) .flatten(); if let Some(field) = field { let lines: Vec<_> = field - .get_meta() + .meta() .lines() .map(|line| format!("/// {line}\n")) .collect(); From 619108761e484affbcb8e475beb042dabacbd149 Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 17:12:51 +0200 Subject: [PATCH 10/19] Fix doctests --- src/meta.rs | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 626759e4..1a49d3f4 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -36,6 +36,8 @@ impl Field { /// Set the metadata of this field. /// /// ``` + /// # use ron::meta::Field; + /// /// let mut field = Field::empty(); /// /// assert_eq!(field.meta(), ""); @@ -52,6 +54,8 @@ impl Field { /// Return whether the Field has metadata. /// /// ``` + /// # use ron::meta::Field; + /// /// let mut field = Field::empty(); /// /// assert!(!field.has_meta()); @@ -78,6 +82,8 @@ impl Field { /// Return whether this field has inner fields. /// /// ``` + /// # use ron::meta::{Field, Fields}; + /// /// let mut field = Field::empty(); /// /// assert!(!field.has_fields()); @@ -94,6 +100,8 @@ impl Field { /// Set the inner fields of this field. /// /// ``` + /// # use ron::meta::{Field, Fields}; + /// /// let mut field = Field::empty(); /// /// assert!(!field.has_fields()); @@ -114,11 +122,15 @@ impl Field { /// Ergonomic shortcut for building some inner fields. /// /// ``` - /// let field = Field::empty().build_fields(|fields| { + /// # use ron::meta::Field; + /// + /// let mut field = Field::empty(); + /// + /// field.build_fields(|fields| { /// fields.field("inner field"); /// }); /// - /// assert!(field.fields().and_then(|fields| fields.contains("inner field"))); + /// assert!(field.fields().is_some_and(|fields| fields.contains("inner field"))); /// ``` pub fn build_fields(&mut self, builder: impl FnOnce(&mut Fields)) -> &mut Self { let mut fields = Fields::default(); @@ -144,6 +156,8 @@ impl Fields { /// Return whether this field map contains no fields. /// /// ``` + /// # use ron::meta::{Fields, Field}; + /// /// let mut fields = Fields::default(); /// /// assert!(fields.is_empty()); @@ -159,7 +173,9 @@ impl Fields { /// Return whether this field map contains a field with the given name. /// /// ``` - /// let fields: Fields = [("a thing", Field::empty())].collect(); + /// # use ron::meta::{Fields, Field}; + /// + /// let fields: Fields = [("a thing", Field::empty())].into_iter().collect(); /// /// assert!(fields.contains("a thing")); /// assert!(!fields.contains("not a thing")); @@ -171,9 +187,11 @@ impl Fields { /// Get a reference to the field with the provided `name`, if it exists. /// /// ``` - /// let fields: Fields = [("a thing", Field::empty())].collect(); + /// # use ron::meta::{Fields, Field}; + /// + /// let fields: Fields = [("a thing", Field::empty())].into_iter().collect(); /// - /// assert!(fields.get("a thing").is_some()) + /// assert!(fields.get("a thing").is_some()); /// assert!(fields.get("not a thing").is_none()); /// ``` pub fn get(&self, name: impl AsRef) -> Option<&Field> { @@ -183,9 +201,11 @@ impl Fields { /// Get a mutable reference to the field with the provided `name`, if it exists. /// /// ``` - /// let mut fields: Fields = [("a thing", Field::empty())].collect(); + /// # use ron::meta::{Fields, Field}; /// - /// assert!(fields.get_mut("a thing").is_some()) + /// let mut fields: Fields = [("a thing", Field::empty())].into_iter().collect(); + /// + /// assert!(fields.get_mut("a thing").is_some()); /// assert!(fields.get_mut("not a thing").is_none()); /// ``` pub fn get_mut(&mut self, name: impl AsRef) -> Option<&mut Field> { @@ -195,10 +215,12 @@ impl Fields { /// Insert a field with the given name into the map. /// /// ``` + /// # use ron::meta::{Fields, Field}; + /// /// let mut fields = Fields::default(); /// - /// assert!(fields.insert("field").is_none()); - /// assert!(fields.insert("field").is_some()); + /// assert!(fields.insert("field", Field::empty()).is_none()); + /// assert!(fields.insert("field", Field::empty()).is_some()); /// ``` pub fn insert(&mut self, name: impl Into, field: Field) -> Option { self.fields.insert(name.into(), field) @@ -208,6 +230,8 @@ impl Fields { /// inserting an empty [`Field`] if it didn't exist. /// /// ``` + /// # use ron::meta::Fields; + /// /// let mut fields = Fields::default(); /// /// assert!(!fields.contains("thing")); From 6420d2d0fbce50ffbea6935f8c481e824a950cfc Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 17:15:57 +0200 Subject: [PATCH 11/19] Add meta with newline to test file --- tests/544_meta.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/544_meta.rs b/tests/544_meta.rs index ee0b6b3e..20998f86 100644 --- a/tests/544_meta.rs +++ b/tests/544_meta.rs @@ -45,19 +45,19 @@ fn serialize_field() { config .meta .field("age") - .with_meta("0@age (person) must be within range 0..256"); + .with_meta("0@age (person)\nmust be within range 0..256"); config .meta .field("knows") - .with_meta("0@knows (person) must be list of valid person indices"); + .with_meta("0@knows (person)\nmust be list of valid person indices"); config.meta.field("pet").build_fields(|fields| { // layer 1 fields .field("age") - .with_meta("1@age (pet) must be valid range 0..256"); + .with_meta("1@age (pet)\nmust be valid range 0..256"); fields .field("kind") - .with_meta("1@kind (pet) must be `Isopod`"); + .with_meta("1@kind (pet)\nmust be `Isopod`"); }); // provide meta for a field that doesn't exist; @@ -71,25 +71,31 @@ fn serialize_field() { r#"[ ( name: "Alice", - /// 0@age (person) must be within range 0..256 + /// 0@age (person) + /// must be within range 0..256 age: 29, - /// 0@knows (person) must be list of valid person indices + /// 0@knows (person) + /// must be list of valid person indices knows: [ 1, ], pet: Some(( name: "Herbert", - /// 1@age (pet) must be valid range 0..256 + /// 1@age (pet) + /// must be valid range 0..256 age: 7, - /// 1@kind (pet) must be `Isopod` + /// 1@kind (pet) + /// must be `Isopod` kind: Isopod, )), ), ( name: "Bob", - /// 0@age (person) must be within range 0..256 + /// 0@age (person) + /// must be within range 0..256 age: 29, - /// 0@knows (person) must be list of valid person indices + /// 0@knows (person) + /// must be list of valid person indices knows: [ 0, ], From 54858f06153c7615e6902fd1d4d592723c218f38 Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 7 Aug 2024 17:20:58 +0200 Subject: [PATCH 12/19] Apply clippy lints --- src/meta.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/meta.rs b/src/meta.rs index 1a49d3f4..253d54b8 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -64,6 +64,7 @@ impl Field { /// /// assert!(field.has_meta()); /// ``` + #[must_use] pub fn has_meta(&self) -> bool { !self.meta.is_empty() } @@ -166,6 +167,7 @@ impl Fields { /// /// assert!(!fields.is_empty()); /// ``` + #[must_use] pub fn is_empty(&self) -> bool { self.fields.is_empty() } From 1bf4d06839e59bfd114ba6a8e7b22a1f232bc8ef Mon Sep 17 00:00:00 2001 From: voidentente Date: Thu, 8 Aug 2024 14:55:49 +0200 Subject: [PATCH 13/19] Remove is_some_and, wrap meta, add tuple to test --- src/meta.rs | 20 +++++++++++- src/ser/mod.rs | 8 ++--- tests/544_meta.rs | 80 +++++++++++++++++++++++++++++++---------------- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index 253d54b8..f975fcc4 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -2,6 +2,24 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct Meta { + fields: Fields, +} + +impl Meta { + /// Get a reference to the named field position metadata. + #[must_use] + pub fn fields(&self) -> &Fields { + &self.fields + } + + /// Get a mutable reference to the named field position metadata. + pub fn fields_mut(&mut self) -> &mut Fields { + &mut self.fields + } +} + /// The metadata and inner [Fields] of a field. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Field { @@ -131,7 +149,7 @@ impl Field { /// fields.field("inner field"); /// }); /// - /// assert!(field.fields().is_some_and(|fields| fields.contains("inner field"))); + /// assert_eq!(field.fields().map(|fields| fields.contains("inner field")), Some(true)); /// ``` pub fn build_fields(&mut self, builder: impl FnOnce(&mut Fields)) -> &mut Self { let mut fields = Fields::default(); diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 83454068..8236f67e 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,7 +7,7 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, - meta::{Field, Fields}, + meta::{Field, Meta}, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; @@ -111,7 +111,7 @@ pub struct PrettyConfig { /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, /// Additional metadata to serialize - pub meta: Fields, + pub meta: Meta, } impl PrettyConfig { @@ -362,7 +362,7 @@ impl Default for PrettyConfig { compact_structs: false, compact_maps: false, number_suffixes: false, - meta: Fields::default(), + meta: Meta::default(), } } } @@ -1341,7 +1341,7 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { if let Some((ref config, _)) = self.ser.pretty { let mut iter = self.ser.field_memory.iter(); - let init = iter.next().and_then(|name| config.meta.get(name)); + let init = iter.next().and_then(|name| config.meta.fields().get(name)); let field = iter .try_fold(init, |field, name| { field.and_then(Field::fields).map(|fields| fields.get(name)) diff --git a/tests/544_meta.rs b/tests/544_meta.rs index 20998f86..c679ea8a 100644 --- a/tests/544_meta.rs +++ b/tests/544_meta.rs @@ -20,55 +20,81 @@ fn serialize_field() { pet: Option, } - let value = vec![ + let value = ( Person { - name: "Alice", - age: 29, - knows: vec![1], - pet: Some(Pet { - name: "Herbert", - age: 7, - kind: PetKind::Isopod, - }), - }, - Person { - name: "Bob", - age: 29, - knows: vec![0], + name: "Walter", + age: 43, + knows: vec![0, 1], pet: None, }, - ]; + vec![ + Person { + name: "Alice", + age: 29, + knows: vec![1], + pet: Some(Pet { + name: "Herbert", + age: 7, + kind: PetKind::Isopod, + }), + }, + Person { + name: "Bob", + age: 29, + knows: vec![0], + pet: None, + }, + ], + ); let mut config = ron::ser::PrettyConfig::default(); // layer 0 config .meta + .fields_mut() .field("age") .with_meta("0@age (person)\nmust be within range 0..256"); config .meta + .fields_mut() .field("knows") .with_meta("0@knows (person)\nmust be list of valid person indices"); - config.meta.field("pet").build_fields(|fields| { - // layer 1 - fields - .field("age") - .with_meta("1@age (pet)\nmust be valid range 0..256"); - fields - .field("kind") - .with_meta("1@kind (pet)\nmust be `Isopod`"); - }); + config + .meta + .fields_mut() + .field("pet") + .build_fields(|fields| { + // layer 1 + fields + .field("age") + .with_meta("1@age (pet)\nmust be valid range 0..256"); + fields + .field("kind") + .with_meta("1@kind (pet)\nmust be `Isopod`"); + }); // provide meta for a field that doesn't exist; // this should not end up anywhere in the final string - config.meta.field("0").with_meta("unreachable"); + config.meta.fields_mut().field("0").with_meta("unreachable"); let s = ron::ser::to_string_pretty(&value, config).unwrap(); assert_eq!( s, - r#"[ + r#"(( + name: "Walter", + /// 0@age (person) + /// must be within range 0..256 + age: 43, + /// 0@knows (person) + /// must be list of valid person indices + knows: [ + 0, + 1, + ], + pet: None, +), [ ( name: "Alice", /// 0@age (person) @@ -101,6 +127,6 @@ fn serialize_field() { ], pet: None, ), -]"# +])"# ); } From dd036edaa9ed7100f112ea6b38df577c23ddd895 Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 24 Sep 2024 13:53:44 +0200 Subject: [PATCH 14/19] Apply change request --- src/meta.rs | 189 ++++++++++++++++++++++++++++++++++------------ src/ser/mod.rs | 58 ++++++++------ tests/544_meta.rs | 42 +++++------ 3 files changed, 196 insertions(+), 93 deletions(-) diff --git a/src/meta.rs b/src/meta.rs index f975fcc4..69a8e017 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -2,28 +2,115 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; +/// Path-based metadata to serialize with a value. +/// +/// Path-based in this context means that the metadata is linked +/// to the data in a relative and hierarchical fashion by tracking +/// the current absolute path of the field being serialized. +/// +/// # Example +/// +/// ``` +/// # use ron::{ser::PrettyConfig, meta::Field}; +/// +/// #[derive(serde::Serialize)] +/// struct Creature { +/// seconds_since_existing: usize, +/// linked: Option>, +/// } +/// +/// let mut config = PrettyConfig::default(); +/// +/// config +/// .meta +/// .field +/// // The path meta defaults to no root structure, +/// // so we either provide a prebuilt one or initialize +/// // an empty one to build. +/// .get_or_insert_with(Field::empty) +/// .build_fields(|fields| { +/// fields +/// // Get or insert the named field +/// .field("seconds_since_existing") +/// .with_doc("Outer seconds_since_existing"); +/// fields +/// .field("linked") +/// // Doc metadata is serialized preceded by three forward slashes and a space for each line +/// .with_doc("Optional.\nProvide another creature to be wrapped.") +/// // Even though it's another Creature, the fields have different paths, so they are addressed separately. +/// .build_fields(|fields| { +/// fields +/// .field("seconds_since_existing") +/// .with_doc("Inner seconds_since_existing"); +/// }); +/// }); +/// +/// let value = Creature { +/// seconds_since_existing: 0, +/// linked: Some(Box::new(Creature { +/// seconds_since_existing: 0, +/// linked: None, +/// })), +/// }; +/// +/// let s = ron::ser::to_string_pretty(&value, config).unwrap(); +/// +/// assert_eq!(s, r#"( +/// /// Outer seconds_since_existing +/// seconds_since_existing: 0, +/// /// Optional. +/// /// Provide another creature to be wrapped. +/// linked: Some(( +/// /// Inner seconds_since_existing +/// seconds_since_existing: 0, +/// linked: None, +/// )), +/// )"#); +/// ``` +/// +/// # Identical paths +/// +/// Especially in enums and tuples it's possible for fields +/// to share a path, thus being unable to be addressed separately. +/// +/// ```no_run +/// enum Kind { +/// A { +/// field: (), +/// }, // ^ +/// // cannot be addressed separately because they have the same path +/// B { // v +/// field: (), +/// }, +/// } +/// ``` +/// +/// ```no_run +/// struct A { +/// field: (), +/// } +/// +/// struct B { +/// field: (), +/// } +/// +/// type Value = ( +/// A, +/// // ^ +/// // These are different types, but they share two fields with the same path: `buf` and `len` +/// // v +/// B, +/// ); +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct Meta { - fields: Fields, +pub struct PathMeta { + pub field: Option, } -impl Meta { - /// Get a reference to the named field position metadata. - #[must_use] - pub fn fields(&self) -> &Fields { - &self.fields - } - - /// Get a mutable reference to the named field position metadata. - pub fn fields_mut(&mut self) -> &mut Fields { - &mut self.fields - } -} - -/// The metadata and inner [Fields] of a field. +/// The metadata and inner [`Fields`] of a field. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Field { - meta: String, + doc: String, fields: Option, } @@ -32,62 +119,52 @@ impl Field { #[must_use] pub const fn empty() -> Self { Self { - meta: String::new(), + doc: String::new(), fields: None, } } - /// Create a new field metadata. - pub fn new(meta: impl Into, fields: Option) -> Self { + /// Create a new field metadata from parts. + pub fn new(doc: impl Into, fields: Option) -> Self { Self { - meta: meta.into(), + doc: doc.into(), fields, } } - /// Get the metadata of this field. + /// Get a shared reference to the documentation metadata of this field. + #[inline] #[must_use] - pub fn meta(&self) -> &str { - &self.meta + pub fn doc(&self) -> &str { + self.doc.as_str() } - /// Set the metadata of this field. - /// - /// ``` - /// # use ron::meta::Field; - /// - /// let mut field = Field::empty(); - /// - /// assert_eq!(field.meta(), ""); - /// - /// field.with_meta("some meta"); - /// - /// assert_eq!(field.meta(), "some meta"); - /// ``` - pub fn with_meta(&mut self, meta: impl Into) -> &mut Self { - self.meta = meta.into(); - self + /// Get a mutable reference to the documentation metadata of this field. + #[inline] + #[must_use] + pub fn doc_mut(&mut self) -> &mut String { + &mut self.doc } - /// Return whether the Field has metadata. + /// Set the documentation metadata of this field. /// /// ``` /// # use ron::meta::Field; /// /// let mut field = Field::empty(); /// - /// assert!(!field.has_meta()); + /// assert_eq!(field.doc(), ""); /// - /// field.with_meta("some"); + /// field.with_doc("some meta"); /// - /// assert!(field.has_meta()); + /// assert_eq!(field.doc(), "some meta"); /// ``` - #[must_use] - pub fn has_meta(&self) -> bool { - !self.meta.is_empty() + pub fn with_doc(&mut self, doc: impl Into) -> &mut Self { + self.doc = doc.into(); + self } - /// Get a reference to the inner fields of this field, if it has any. + /// Get a shared reference to the inner fields of this field, if it has any. #[must_use] pub fn fields(&self) -> Option<&Fields> { self.fields.as_ref() @@ -159,7 +236,7 @@ impl Field { } } -/// Mapping of names to [Field]s. +/// Mapping of names to [`Field`]s. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Fields { fields: HashMap, @@ -246,6 +323,20 @@ impl Fields { self.fields.insert(name.into(), field) } + /// Remove a field with the given name from the map. + /// + /// ``` + /// # use ron::meta::{Fields, Field}; + /// + /// let mut fields: Fields = [("a", Field::empty())].into_iter().collect(); + /// + /// assert_eq!(fields.remove("a"), Some(Field::empty())); + /// assert_eq!(fields.remove("a"), None); + /// ``` + pub fn remove(&mut self, name: impl AsRef) -> Option { + self.fields.remove(name.as_ref()) + } + /// Get a mutable reference to the field with the provided `name`, /// inserting an empty [`Field`] if it didn't exist. /// diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 3bedbc76..11a39403 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,7 +7,7 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, - meta::{Field, Meta}, + meta::PathMeta, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; @@ -111,7 +111,7 @@ pub struct PrettyConfig { /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, /// Additional metadata to serialize - pub meta: Meta, + pub meta: PathMeta, } impl PrettyConfig { @@ -362,7 +362,7 @@ impl Default for PrettyConfig { compact_structs: false, compact_maps: false, number_suffixes: false, - meta: Meta::default(), + meta: PathMeta::default(), } } } @@ -380,7 +380,6 @@ pub struct Serializer { recursion_limit: Option, // Tracks the number of opened implicit `Some`s, set to 0 on backtracking implicit_some_depth: usize, - field_memory: Vec<&'static str>, } impl Serializer { @@ -433,7 +432,6 @@ impl Serializer { newtype_variant: false, recursion_limit: options.recursion_limit, implicit_some_depth: 0, - field_memory: Vec::new(), }) } @@ -1319,7 +1317,14 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { where T: ?Sized + Serialize, { - self.ser.field_memory.push(key); + let mut restore_field = self.ser.pretty.as_mut().and_then(|(config, _)| { + config.meta.field.take().map(|mut field| { + if let Some(fields) = field.fields_mut() { + config.meta.field = fields.remove(key); + } + field + }) + }); if let State::First = self.state { self.state = State::Rest; @@ -1339,24 +1344,23 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { self.ser.indent()?; if let Some((ref config, _)) = self.ser.pretty { - let mut iter = self.ser.field_memory.iter(); - - let init = iter.next().and_then(|name| config.meta.fields().get(name)); - let field = iter - .try_fold(init, |field, name| { - field.and_then(Field::fields).map(|fields| fields.get(name)) - }) - .flatten(); - - if let Some(field) = field { - let lines: Vec<_> = field - .meta() + if let Some(ref field) = config.meta.field { + // TODO: `self.ser.indent()` borrows the entire serializer mutably, + // consider constraining the signature in the future to avoid this heap allocation. + let doc_lines: Vec<_> = field + .doc() .lines() - .map(|line| format!("/// {line}\n")) + .map(|line| { + let mut buf = String::with_capacity(line.len() + 5); + buf.push_str("/// "); + buf.push_str(line); + buf.push('\n'); + buf + }) .collect(); - for line in lines { - self.ser.output.write_str(&line)?; + for doc_line in doc_lines { + self.ser.output.write_str(&doc_line)?; self.ser.indent()?; } } @@ -1372,7 +1376,17 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; - self.ser.field_memory.pop(); + if let Some((ref mut config, _)) = self.ser.pretty { + std::mem::swap(&mut config.meta.field, &mut restore_field); + + if let Some(ref mut field) = config.meta.field { + if let Some(fields) = field.fields_mut() { + if let Some(restore_field) = restore_field { + fields.insert(key, restore_field); + } + } + } + }; Ok(()) } diff --git a/tests/544_meta.rs b/tests/544_meta.rs index c679ea8a..6193969a 100644 --- a/tests/544_meta.rs +++ b/tests/544_meta.rs @@ -1,3 +1,5 @@ +use ron::{meta::Field, ser::PrettyConfig}; + #[test] fn serialize_field() { #[derive(serde::Serialize)] @@ -47,36 +49,32 @@ fn serialize_field() { ], ); - let mut config = ron::ser::PrettyConfig::default(); + let mut config = PrettyConfig::default(); - // layer 0 - config - .meta - .fields_mut() - .field("age") - .with_meta("0@age (person)\nmust be within range 0..256"); config .meta - .fields_mut() - .field("knows") - .with_meta("0@knows (person)\nmust be list of valid person indices"); - config - .meta - .fields_mut() - .field("pet") + .field + .get_or_insert_with(Field::empty) .build_fields(|fields| { - // layer 1 fields .field("age") - .with_meta("1@age (pet)\nmust be valid range 0..256"); + .with_doc("0@age (person)\nmust be within range 0..256"); fields - .field("kind") - .with_meta("1@kind (pet)\nmust be `Isopod`"); - }); + .field("knows") + .with_doc("0@knows (person)\nmust be list of valid person indices"); + fields.field("pet").build_fields(|fields| { + fields + .field("age") + .with_doc("1@age (pet)\nmust be valid range 0..256"); + fields + .field("kind") + .with_doc("1@kind (pet)\nmust be `Isopod`"); + }); - // provide meta for a field that doesn't exist; - // this should not end up anywhere in the final string - config.meta.fields_mut().field("0").with_meta("unreachable"); + // provide meta for a field that doesn't exist; + // this should not end up anywhere in the final string + fields.field("0").with_doc("unreachable"); + }); let s = ron::ser::to_string_pretty(&value, config).unwrap(); From 4e448e9d5a3c0bee92cc76993961d038db9e9dfa Mon Sep 17 00:00:00 2001 From: voidentente Date: Tue, 24 Sep 2024 17:42:57 +0200 Subject: [PATCH 15/19] Refactor --- src/lib.rs | 1 - src/ser/mod.rs | 86 ++++----- src/{meta.rs => ser/path_meta.rs} | 227 ++++++++++++------------ tests/{544_meta.rs => 544_path_meta.rs} | 5 +- 4 files changed, 158 insertions(+), 161 deletions(-) rename src/{meta.rs => ser/path_meta.rs} (68%) rename tests/{544_meta.rs => 544_path_meta.rs} (97%) diff --git a/src/lib.rs b/src/lib.rs index e44d43dd..36414b1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,6 @@ pub mod de; pub mod ser; pub mod error; -pub mod meta; pub mod value; pub mod extensions; diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 11a39403..7868d1d7 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -7,11 +7,12 @@ use unicode_ident::is_xid_continue; use crate::{ error::{Error, Result}, extensions::Extensions, - meta::PathMeta, options::Options, parse::{is_ident_first_char, is_ident_raw_char, is_whitespace_char, LargeSInt, LargeUInt}, }; +pub mod path_meta; + mod raw; #[cfg(test)] mod tests; @@ -111,7 +112,7 @@ pub struct PrettyConfig { /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, /// Additional metadata to serialize - pub meta: PathMeta, + pub path_meta: Option, } impl PrettyConfig { @@ -362,7 +363,7 @@ impl Default for PrettyConfig { compact_structs: false, compact_maps: false, number_suffixes: false, - meta: PathMeta::default(), + path_meta: None, } } } @@ -382,6 +383,15 @@ pub struct Serializer { implicit_some_depth: usize, } +fn indent(mut output: W, config: &PrettyConfig, pretty: &Pretty) -> fmt::Result { + if pretty.indent <= config.depth_limit { + for _ in 0..pretty.indent { + output.write_str(&config.indentor)?; + } + } + Ok(()) +} + impl Serializer { /// Creates a new [`Serializer`]. /// @@ -493,16 +503,16 @@ impl Serializer { Ok(()) } - fn indent(&mut self) -> fmt::Result { - if let Some((ref config, ref pretty)) = self.pretty { - if pretty.indent <= config.depth_limit { - for _ in 0..pretty.indent { - self.output.write_str(&config.indentor)?; - } - } - } - Ok(()) - } + // fn indent(&mut self) -> fmt::Result { + // if let Some((ref config, ref pretty)) = self.pretty { + // if pretty.indent <= config.depth_limit { + // for _ in 0..pretty.indent { + // self.output.write_str(&config.indentor)?; + // } + // } + // } + // Ok(()) + // } fn end_indent(&mut self) -> fmt::Result { if let Some((ref config, ref mut pretty)) = self.pretty { @@ -1126,7 +1136,9 @@ impl<'a, W: fmt::Write> ser::SerializeSeq for Compound<'a, W> { } if !self.ser.compact_arrays() { - self.ser.indent()?; + if let Some((ref config, ref pretty)) = self.ser.pretty { + indent(&mut self.ser.output, config, pretty)?; + } } if let Some((ref mut config, ref mut pretty)) = self.ser.pretty { @@ -1183,7 +1195,9 @@ impl<'a, W: fmt::Write> ser::SerializeTuple for Compound<'a, W> { } if self.ser.separate_tuple_members() { - self.ser.indent()?; + if let Some((ref config, ref pretty)) = self.ser.pretty { + indent(&mut self.ser.output, config, pretty)?; + } } guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; @@ -1268,7 +1282,9 @@ impl<'a, W: fmt::Write> ser::SerializeMap for Compound<'a, W> { } if !self.ser.compact_maps() { - self.ser.indent()?; + if let Some((ref config, ref pretty)) = self.ser.pretty { + indent(&mut self.ser.output, config, pretty)?; + } } guard_recursion! { self.ser => key.serialize(&mut *self.ser) } @@ -1318,9 +1334,9 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { T: ?Sized + Serialize, { let mut restore_field = self.ser.pretty.as_mut().and_then(|(config, _)| { - config.meta.field.take().map(|mut field| { + config.path_meta.take().map(|mut field| { if let Some(fields) = field.fields_mut() { - config.meta.field = fields.remove(key); + config.path_meta = fields.remove(key); } field }) @@ -1341,27 +1357,15 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { } if !self.ser.compact_structs() { - self.ser.indent()?; - - if let Some((ref config, _)) = self.ser.pretty { - if let Some(ref field) = config.meta.field { - // TODO: `self.ser.indent()` borrows the entire serializer mutably, - // consider constraining the signature in the future to avoid this heap allocation. - let doc_lines: Vec<_> = field - .doc() - .lines() - .map(|line| { - let mut buf = String::with_capacity(line.len() + 5); - buf.push_str("/// "); - buf.push_str(line); - buf.push('\n'); - buf - }) - .collect(); - - for doc_line in doc_lines { - self.ser.output.write_str(&doc_line)?; - self.ser.indent()?; + if let Some((ref config, ref pretty)) = self.ser.pretty { + indent(&mut self.ser.output, config, pretty)?; + + if let Some(ref field) = config.path_meta { + for doc_line in field.doc().lines() { + self.ser.output.write_str("/// ")?; + self.ser.output.write_str(doc_line)?; + self.ser.output.write_char('\n')?; + indent(&mut self.ser.output, config, pretty)?; } } } @@ -1377,9 +1381,9 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; if let Some((ref mut config, _)) = self.ser.pretty { - std::mem::swap(&mut config.meta.field, &mut restore_field); + std::mem::swap(&mut config.path_meta, &mut restore_field); - if let Some(ref mut field) = config.meta.field { + if let Some(ref mut field) = config.path_meta { if let Some(fields) = field.fields_mut() { if let Some(restore_field) = restore_field { fields.insert(key, restore_field); diff --git a/src/meta.rs b/src/ser/path_meta.rs similarity index 68% rename from src/meta.rs rename to src/ser/path_meta.rs index 69a8e017..7c8b41c3 100644 --- a/src/meta.rs +++ b/src/ser/path_meta.rs @@ -1,112 +1,107 @@ +//! Path-based metadata to serialize with a value. +//! +//! Path-based in this context means that the metadata is linked +//! to the data in a relative and hierarchical fashion by tracking +//! the current absolute path of the field being serialized. +//! +//! # Example +//! +//! ``` +//! # use ron::{ser::{PrettyConfig, path_meta::Field}}; +//! +//! #[derive(serde::Serialize)] +//! struct Creature { +//! seconds_since_existing: usize, +//! linked: Option>, +//! } +//! +//! let mut config = PrettyConfig::default(); +//! +//! config +//! .path_meta +//! // The path meta defaults to no root structure, +//! // so we either provide a prebuilt one or initialize +//! // an empty one to build. +//! .get_or_insert_with(Field::empty) +//! .build_fields(|fields| { +//! fields +//! // Get or insert the named field +//! .field("seconds_since_existing") +//! .with_doc("Outer seconds_since_existing"); +//! fields +//! .field("linked") +//! // Doc metadata is serialized preceded by three forward slashes and a space for each line +//! .with_doc("Optional.\nProvide another creature to be wrapped.") +//! // Even though it's another Creature, the fields have different paths, so they are addressed separately. +//! .build_fields(|fields| { +//! fields +//! .field("seconds_since_existing") +//! .with_doc("Inner seconds_since_existing"); +//! }); +//! }); +//! +//! let value = Creature { +//! seconds_since_existing: 0, +//! linked: Some(Box::new(Creature { +//! seconds_since_existing: 0, +//! linked: None, +//! })), +//! }; +//! +//! let s = ron::ser::to_string_pretty(&value, config).unwrap(); +//! +//! assert_eq!(s, r#"( +//! /// Outer seconds_since_existing +//! seconds_since_existing: 0, +//! /// Optional. +//! /// Provide another creature to be wrapped. +//! linked: Some(( +//! /// Inner seconds_since_existing +//! seconds_since_existing: 0, +//! linked: None, +//! )), +//! )"#); +//! ``` +//! +//! # Identical paths +//! +//! Especially in enums and tuples it's possible for fields +//! to share a path, thus being unable to be addressed separately. +//! +//! ```no_run +//! enum Kind { +//! A { +//! field: (), +//! }, // ^ +//! // cannot be addressed separately because they have the same path +//! B { // v +//! field: (), +//! }, +//! } +//! ``` +//! +//! ```no_run +//! struct A { +//! field: (), +//! } +//! +//! struct B { +//! field: (), +//! } +//! +//! type Value = ( +//! A, +//! // ^ +//! // These are different types, but they share two fields with the same path: `buf` and `len` +//! // v +//! B, +//! ); +//! ``` + use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; -/// Path-based metadata to serialize with a value. -/// -/// Path-based in this context means that the metadata is linked -/// to the data in a relative and hierarchical fashion by tracking -/// the current absolute path of the field being serialized. -/// -/// # Example -/// -/// ``` -/// # use ron::{ser::PrettyConfig, meta::Field}; -/// -/// #[derive(serde::Serialize)] -/// struct Creature { -/// seconds_since_existing: usize, -/// linked: Option>, -/// } -/// -/// let mut config = PrettyConfig::default(); -/// -/// config -/// .meta -/// .field -/// // The path meta defaults to no root structure, -/// // so we either provide a prebuilt one or initialize -/// // an empty one to build. -/// .get_or_insert_with(Field::empty) -/// .build_fields(|fields| { -/// fields -/// // Get or insert the named field -/// .field("seconds_since_existing") -/// .with_doc("Outer seconds_since_existing"); -/// fields -/// .field("linked") -/// // Doc metadata is serialized preceded by three forward slashes and a space for each line -/// .with_doc("Optional.\nProvide another creature to be wrapped.") -/// // Even though it's another Creature, the fields have different paths, so they are addressed separately. -/// .build_fields(|fields| { -/// fields -/// .field("seconds_since_existing") -/// .with_doc("Inner seconds_since_existing"); -/// }); -/// }); -/// -/// let value = Creature { -/// seconds_since_existing: 0, -/// linked: Some(Box::new(Creature { -/// seconds_since_existing: 0, -/// linked: None, -/// })), -/// }; -/// -/// let s = ron::ser::to_string_pretty(&value, config).unwrap(); -/// -/// assert_eq!(s, r#"( -/// /// Outer seconds_since_existing -/// seconds_since_existing: 0, -/// /// Optional. -/// /// Provide another creature to be wrapped. -/// linked: Some(( -/// /// Inner seconds_since_existing -/// seconds_since_existing: 0, -/// linked: None, -/// )), -/// )"#); -/// ``` -/// -/// # Identical paths -/// -/// Especially in enums and tuples it's possible for fields -/// to share a path, thus being unable to be addressed separately. -/// -/// ```no_run -/// enum Kind { -/// A { -/// field: (), -/// }, // ^ -/// // cannot be addressed separately because they have the same path -/// B { // v -/// field: (), -/// }, -/// } -/// ``` -/// -/// ```no_run -/// struct A { -/// field: (), -/// } -/// -/// struct B { -/// field: (), -/// } -/// -/// type Value = ( -/// A, -/// // ^ -/// // These are different types, but they share two fields with the same path: `buf` and `len` -/// // v -/// B, -/// ); -/// ``` -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct PathMeta { - pub field: Option, -} - /// The metadata and inner [`Fields`] of a field. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Field { @@ -149,7 +144,7 @@ impl Field { /// Set the documentation metadata of this field. /// /// ``` - /// # use ron::meta::Field; + /// # use ron::ser::path_meta::Field; /// /// let mut field = Field::empty(); /// @@ -178,7 +173,7 @@ impl Field { /// Return whether this field has inner fields. /// /// ``` - /// # use ron::meta::{Field, Fields}; + /// # use ron::ser::path_meta::{Field, Fields}; /// /// let mut field = Field::empty(); /// @@ -196,7 +191,7 @@ impl Field { /// Set the inner fields of this field. /// /// ``` - /// # use ron::meta::{Field, Fields}; + /// # use ron::ser::path_meta::{Field, Fields}; /// /// let mut field = Field::empty(); /// @@ -218,7 +213,7 @@ impl Field { /// Ergonomic shortcut for building some inner fields. /// /// ``` - /// # use ron::meta::Field; + /// # use ron::ser::path_meta::Field; /// /// let mut field = Field::empty(); /// @@ -252,7 +247,7 @@ impl Fields { /// Return whether this field map contains no fields. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let mut fields = Fields::default(); /// @@ -270,7 +265,7 @@ impl Fields { /// Return whether this field map contains a field with the given name. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let fields: Fields = [("a thing", Field::empty())].into_iter().collect(); /// @@ -284,7 +279,7 @@ impl Fields { /// Get a reference to the field with the provided `name`, if it exists. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let fields: Fields = [("a thing", Field::empty())].into_iter().collect(); /// @@ -298,7 +293,7 @@ impl Fields { /// Get a mutable reference to the field with the provided `name`, if it exists. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let mut fields: Fields = [("a thing", Field::empty())].into_iter().collect(); /// @@ -312,7 +307,7 @@ impl Fields { /// Insert a field with the given name into the map. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let mut fields = Fields::default(); /// @@ -326,7 +321,7 @@ impl Fields { /// Remove a field with the given name from the map. /// /// ``` - /// # use ron::meta::{Fields, Field}; + /// # use ron::ser::path_meta::{Fields, Field}; /// /// let mut fields: Fields = [("a", Field::empty())].into_iter().collect(); /// @@ -341,7 +336,7 @@ impl Fields { /// inserting an empty [`Field`] if it didn't exist. /// /// ``` - /// # use ron::meta::Fields; + /// # use ron::ser::path_meta::Fields; /// /// let mut fields = Fields::default(); /// diff --git a/tests/544_meta.rs b/tests/544_path_meta.rs similarity index 97% rename from tests/544_meta.rs rename to tests/544_path_meta.rs index 6193969a..a165bcf7 100644 --- a/tests/544_meta.rs +++ b/tests/544_path_meta.rs @@ -1,4 +1,4 @@ -use ron::{meta::Field, ser::PrettyConfig}; +use ron::ser::{path_meta::Field, PrettyConfig}; #[test] fn serialize_field() { @@ -52,8 +52,7 @@ fn serialize_field() { let mut config = PrettyConfig::default(); config - .meta - .field + .path_meta .get_or_insert_with(Field::empty) .build_fields(|fields| { fields From f664e78abee9afd3ea8db44b28aaa6e02cc50ef9 Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 25 Sep 2024 08:48:08 +0200 Subject: [PATCH 16/19] Cleanup --- src/ser/mod.rs | 11 ----------- src/ser/path_meta.rs | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 7868d1d7..d62ef145 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -503,17 +503,6 @@ impl Serializer { Ok(()) } - // fn indent(&mut self) -> fmt::Result { - // if let Some((ref config, ref pretty)) = self.pretty { - // if pretty.indent <= config.depth_limit { - // for _ in 0..pretty.indent { - // self.output.write_str(&config.indentor)?; - // } - // } - // } - // Ok(()) - // } - fn end_indent(&mut self) -> fmt::Result { if let Some((ref config, ref mut pretty)) = self.pretty { if pretty.indent <= config.depth_limit { diff --git a/src/ser/path_meta.rs b/src/ser/path_meta.rs index 7c8b41c3..8a870523 100644 --- a/src/ser/path_meta.rs +++ b/src/ser/path_meta.rs @@ -7,7 +7,7 @@ //! # Example //! //! ``` -//! # use ron::{ser::{PrettyConfig, path_meta::Field}}; +//! # use ron::ser::{PrettyConfig, path_meta::Field}; //! //! #[derive(serde::Serialize)] //! struct Creature { @@ -92,7 +92,7 @@ //! type Value = ( //! A, //! // ^ -//! // These are different types, but they share two fields with the same path: `buf` and `len` +//! // These are different types, but they share the path `field` //! // v //! B, //! ); From 988fdb470e1148d00672b1b867706172f49e64ff Mon Sep 17 00:00:00 2001 From: voidentente Date: Wed, 25 Sep 2024 10:25:20 +0200 Subject: [PATCH 17/19] Make indent take mutable reference, make method a wrapper --- src/ser/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ser/mod.rs b/src/ser/mod.rs index d62ef145..67755dd4 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -383,7 +383,7 @@ pub struct Serializer { implicit_some_depth: usize, } -fn indent(mut output: W, config: &PrettyConfig, pretty: &Pretty) -> fmt::Result { +fn indent(output: &mut W, config: &PrettyConfig, pretty: &Pretty) -> fmt::Result { if pretty.indent <= config.depth_limit { for _ in 0..pretty.indent { output.write_str(&config.indentor)?; @@ -503,6 +503,13 @@ impl Serializer { Ok(()) } + fn indent(&mut self) -> fmt::Result { + if let Some((ref config, ref pretty)) = self.pretty { + indent(&mut self.output, config, pretty)?; + } + Ok(()) + } + fn end_indent(&mut self) -> fmt::Result { if let Some((ref config, ref mut pretty)) = self.pretty { if pretty.indent <= config.depth_limit { @@ -1125,9 +1132,7 @@ impl<'a, W: fmt::Write> ser::SerializeSeq for Compound<'a, W> { } if !self.ser.compact_arrays() { - if let Some((ref config, ref pretty)) = self.ser.pretty { - indent(&mut self.ser.output, config, pretty)?; - } + self.ser.indent()?; } if let Some((ref mut config, ref mut pretty)) = self.ser.pretty { @@ -1184,9 +1189,7 @@ impl<'a, W: fmt::Write> ser::SerializeTuple for Compound<'a, W> { } if self.ser.separate_tuple_members() { - if let Some((ref config, ref pretty)) = self.ser.pretty { - indent(&mut self.ser.output, config, pretty)?; - } + self.ser.indent()?; } guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; @@ -1271,9 +1274,7 @@ impl<'a, W: fmt::Write> ser::SerializeMap for Compound<'a, W> { } if !self.ser.compact_maps() { - if let Some((ref config, ref pretty)) = self.ser.pretty { - indent(&mut self.ser.output, config, pretty)?; - } + self.ser.indent()?; } guard_recursion! { self.ser => key.serialize(&mut *self.ser) } From 7b909d0cf9ba820f35f254b7efc9b63397e6d9a3 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:44:20 +0300 Subject: [PATCH 18/19] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b318bcbd..edb40a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: Enforce that ron always writes valid UTF-8 ([#488](https://github.com/ron-rs/ron/pull/488)) - Add convenient `Value::from` impls ([#498](https://github.com/ron-rs/ron/pull/498)) - Add new extension `explicit_struct_names` which requires that struct names are included during deserialization ([#522](https://github.com/ron-rs/ron/pull/522)) -- Add new metadata serialization support in named field position via `PrettyConfig` ([#544](https://github.com/ron-rs/ron/pull/544)) +- Add new path-based field metadata serialization support via `PrettyConfig` ([#544](https://github.com/ron-rs/ron/pull/544)) - Breaking: Change `PrettyConfig` so that `new_line`, `indentor` and `separator` are all `Cow<'static, str>` instead of `String` ([#546](https://github.com/ron-rs/ron/pull/546)) ### Format Changes From cad42d1a804617e25949d0a53a9c3592196276d4 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:44:29 +0300 Subject: [PATCH 19/19] Update src/ser/mod.rs --- src/ser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 67755dd4..1d3b6c11 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -111,7 +111,7 @@ pub struct PrettyConfig { pub compact_maps: bool, /// Enable explicit number type suffixes like `1u16` pub number_suffixes: bool, - /// Additional metadata to serialize + /// Additional path-based field metadata to serialize pub path_meta: Option, }