From 256a800c20b1f55665eb7c12375a663468dcbb80 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:38:44 +0000 Subject: [PATCH] fix(settings): keep doc comments on every list item in generated YAML to_yaml_string reset its list item counter on every non-list key, so each list item after the first was looked up as item 0. Docs that only exist for a later item, such as the fields of an Option that is None in the first item and Some in a later one, were dropped. Track the next item index per list indentation instead. --- foundations/src/settings/mod.rs | 21 ++++++++++-- .../serde-saphyr/with_vec_of_options.yaml | 16 ++++++++++ .../data/serde-yaml/with_vec_of_options.yaml | 17 ++++++++++ foundations/tests/settings.rs | 32 +++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 foundations/tests/data/serde-saphyr/with_vec_of_options.yaml create mode 100644 foundations/tests/data/serde-yaml/with_vec_of_options.yaml diff --git a/foundations/src/settings/mod.rs b/foundations/src/settings/mod.rs index c9f85569..1f831e22 100644 --- a/foundations/src/settings/mod.rs +++ b/foundations/src/settings/mod.rs @@ -444,7 +444,8 @@ pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult { let mut doc_comments = Default::default(); let mut yaml_with_docs = String::new(); let mut key_stack = vec![]; - let mut list_index = 0; + // Indentation and next item index of each list enclosing the current line. + let mut list_indices: Vec<(usize, usize)> = vec![]; settings.add_docs(&[], &mut doc_comments); @@ -463,13 +464,27 @@ pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult { // NOTE: if we have a list item, then append the index of the item to the key stack. if is_list_item { + // Lists nested in the previous item end here. + list_indices.retain(|&(indent, _)| indent <= spaces); + + let list_index = match list_indices.last_mut() { + Some((indent, next_index)) if *indent == spaces => { + *next_index += 1; + *next_index - 1 + } + _ => { + list_indices.push((spaces, 1)); + 0 + } + }; + key_stack.push(list_index.to_string()); field_name = field_name[LIST_ITEM_PREFIX.len()..].trim().to_string(); spaces += LIST_ITEM_PREFIX.len(); - list_index += 1; } else { - list_index = 0; + // A key at or left of a list's dash ends that list. + list_indices.retain(|&(indent, _)| indent < spaces); } key_stack.push(field_name); diff --git a/foundations/tests/data/serde-saphyr/with_vec_of_options.yaml b/foundations/tests/data/serde-saphyr/with_vec_of_options.yaml new file mode 100644 index 00000000..e7d519df --- /dev/null +++ b/foundations/tests/data/serde-saphyr/with_vec_of_options.yaml @@ -0,0 +1,16 @@ +# Items +items: + # Name of the item + - name: foo + # Optional field + optional: null + # Name of the item + - name: bar + # Optional field + optional: + # A field, which is named the same as another field. + a: 1 + # multi-line + # doc comment + b: 2 + c: 3 \ No newline at end of file diff --git a/foundations/tests/data/serde-yaml/with_vec_of_options.yaml b/foundations/tests/data/serde-yaml/with_vec_of_options.yaml new file mode 100644 index 00000000..10d924a3 --- /dev/null +++ b/foundations/tests/data/serde-yaml/with_vec_of_options.yaml @@ -0,0 +1,17 @@ +--- +# Items +items: + # Name of the item + - name: foo + # Optional field + optional: ~ + # Name of the item + - name: bar + # Optional field + optional: + # A field, which is named the same as another field. + a: 1 + # multi-line + # doc comment + b: 2 + c: 3 \ No newline at end of file diff --git a/foundations/tests/settings.rs b/foundations/tests/settings.rs index d9bf0d46..894a67d1 100644 --- a/foundations/tests/settings.rs +++ b/foundations/tests/settings.rs @@ -161,6 +161,20 @@ struct WithVec { items: Vec, } +#[settings] +struct NamedOption { + /// Name of the item + name: String, + /// Optional field + optional: Option, +} + +#[settings] +struct WithVecOfOptions { + /// Items + items: Vec, +} + mod foundations_reexport { pub(crate) mod nested { pub(crate) use foundations::*; @@ -293,6 +307,24 @@ fn vec() { assert_ser_eq!(s, "with_vec.yaml"); } +#[test] +fn vec_items_with_different_fields() { + let s = WithVecOfOptions { + items: vec![ + NamedOption { + name: "foo".into(), + optional: None, + }, + NamedOption { + name: "bar".into(), + optional: Some(NestedStruct { a: 1, b: 2, c: 3 }), + }, + ], + }; + + assert_ser_eq!(s, "with_vec_of_options.yaml"); +} + #[test] fn parse() { const INVALID_YAML_WITH_SECRET: &str = r#"