From 5d7902142fe5c7ab0a820117a80b5f4a4e34d5c9 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Tue, 6 Oct 2026 13:30:17 +0200 Subject: [PATCH 1/9] feat(versioned): Add support for from_docs attribute --- .../src/attrs/item/mod.rs | 23 ++- .../src/codegen/item/field.rs | 21 ++- .../src/codegen/item/mod.rs | 34 +++- .../src/codegen/item/variant.rs | 21 ++- crates/stackable-versioned-macros/src/lib.rs | 54 +++++++ .../tests/inputs/fail/changed.stderr | 2 +- .../tests/inputs/pass/docs.rs | 31 ++++ ...ioned_macros__snapshots__pass@docs.rs.snap | 149 ++++++++++++++++++ crates/stackable-versioned/CHANGELOG.md | 2 + 9 files changed, 318 insertions(+), 19 deletions(-) diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index 4c8e2dfcf..fe63e28d3 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -8,7 +8,7 @@ use syn::{Attribute, Path, Type, spanned::Spanned}; use crate::{ codegen::{VersionDefinition, item::ItemStatus}, - utils::ItemIdents, + utils::{ItemIdents, doc_comments::DocComments as _}, }; mod field; @@ -223,9 +223,12 @@ impl CommonItemAttributes { let mut errors = Error::accumulator(); for change in &self.changes { - if change.from_name.is_none() && change.from_type.is_none() { + if change.from_name.is_none() + && change.from_type.is_none() + && change.from_docs.is_none() + { errors.push(Error::custom( - "both `from_name` and `from_type` are unset. Is this `changed()` action needed?" + "`from_name`, `from_type` and `from_docs` are unset. Is this `changed()` action needed?" ).with_span(&change.since.span())); } @@ -288,6 +291,18 @@ impl CommonItemAttributes { } impl CommonItemAttributes { + /// Returns the doc comments of the item before each change which provides `from_docs`, keyed + /// by the version of the change. + pub fn previous_docs(&self) -> BTreeMap> { + self.changes + .iter() + .filter_map(|change| { + let docs = change.from_docs.as_deref()?; + Some((*change.since, docs.as_str().into_doc_comments())) + }) + .collect() + } + #[expect(clippy::too_many_lines)] pub fn into_changeset( self, @@ -458,11 +473,13 @@ fn default_default_fn() -> SpannedValue { /// - `changed(since = "...", from_name = "...", from_type="...")` /// - `changed(since = "...", from_name = "...", from_type="...", upgrade_with = "...")` /// - `changed(since = "...", from_name = "...", from_type="...", downgrade_with = "...")` +/// - `changed(since = "...", from_docs = "...")` #[derive(Clone, Debug, FromMeta)] pub struct ChangedAttributes { pub since: SpannedValue, pub from_name: Option>, pub from_type: Option>, + pub from_docs: Option>, pub upgrade_with: Option>, pub downgrade_with: Option>, } diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index ea074aeea..ecc81155e 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -11,7 +11,7 @@ use crate::{ codegen::{ Direction, VersionDefinition, changes::{BTreeMapExt, ChangesetExt}, - item::ItemStatus, + item::{ItemStatus, generate_attributes}, module::ModuleGenerationContext, }, utils::{ItemIdentExt, ItemIdents}, @@ -20,6 +20,7 @@ use crate::{ #[derive(Debug)] pub struct VersionedField { pub original_attributes: Vec, + pub previous_docs: BTreeMap>, pub changes: Option>, pub idents: FieldIdents, pub hint: Option, @@ -45,6 +46,7 @@ impl VersionedField { })?; let idents = FieldIdents::from(ident); + let previous_docs = field_attributes.common.previous_docs(); let changes = field_attributes .common .into_changeset(&idents, field.ty.clone()); @@ -53,6 +55,7 @@ impl VersionedField { Ok(Self { original_attributes: field_attributes.attrs, hint: field_attributes.hint, + previous_docs, ty: field.ty, changes, idents, @@ -82,7 +85,11 @@ impl VersionedField { /// } /// ``` pub fn generate_for_container(&self, version: &VersionDefinition) -> Option { - let original_attributes = &self.original_attributes; + let attributes = generate_attributes( + &self.original_attributes, + &self.previous_docs, + &version.inner, + ); #[allow(clippy::single_match_else)] match &self.changes { @@ -106,13 +113,13 @@ impl VersionedField { ) }) { ItemStatus::Addition { ident, ty, .. } => Some(quote! { - #(#original_attributes)* + #attributes pub #ident: #ty, }), ItemStatus::Change { to_ident, to_type, .. } => Some(quote! { - #(#original_attributes)* + #attributes pub #to_ident: #to_type, }), ItemStatus::Deprecation { @@ -133,7 +140,7 @@ impl VersionedField { }; Some(quote! { - #(#original_attributes)* + #attributes #deprecated_attr pub #field_ident: #field_type, }) @@ -149,7 +156,7 @@ impl VersionedField { let deprecated_attr = previously_deprecated.then(|| quote! {#[deprecated]}); Some(quote! { - #(#original_attributes)* + #attributes #deprecated_attr pub #ident: #ty, }) @@ -163,7 +170,7 @@ impl VersionedField { let field_type = &self.ty; Some(quote! { - #(#original_attributes)* + #attributes pub #field_ident: #field_type, }) } diff --git a/crates/stackable-versioned-macros/src/codegen/item/mod.rs b/crates/stackable-versioned-macros/src/codegen/item/mod.rs index 46abd7df8..f26b17747 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/mod.rs @@ -1,5 +1,12 @@ +use std::{collections::BTreeMap, ops::Bound}; + use darling::util::IdentString; -use syn::{Path, Type}; +use k8s_version::Version; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Attribute, Meta, Path, Type}; + +use crate::codegen::changes::Neighbors as _; mod field; pub use field::*; @@ -7,6 +14,31 @@ pub use field::*; mod variant; pub use variant::*; +/// Generates the attributes of an item (field or variant) for the provided `version`. +/// +/// If the docs of the item are changed in a later version (via `from_docs`), the original doc +/// comments are replaced by the docs which are valid in `version`. +pub fn generate_attributes( + original_attributes: &[Attribute], + previous_docs: &BTreeMap>, + version: &Version, +) -> TokenStream { + // The docs valid in this version are the previous docs of the closest change after this + // version. If there is no such change, the original docs are still valid. + let Some((_, docs)) = previous_docs.up_bound(Bound::Excluded(version)) else { + return quote! { #(#original_attributes)* }; + }; + + let attributes = original_attributes.iter().filter(|attribute| { + !matches!(&attribute.meta, Meta::NameValue(name_value) if name_value.path.is_ident("doc")) + }); + + quote! { + #(#[doc = #docs])* + #(#attributes)* + } +} + #[derive(Debug, PartialEq, Eq)] pub enum ItemStatus { Addition { diff --git a/crates/stackable-versioned-macros/src/codegen/item/variant.rs b/crates/stackable-versioned-macros/src/codegen/item/variant.rs index 0175b43aa..6455ef6b7 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/variant.rs @@ -13,13 +13,14 @@ use crate::{ codegen::{ Direction, VersionDefinition, changes::{BTreeMapExt, ChangesetExt}, - item::ItemStatus, + item::{ItemStatus, generate_attributes}, }, utils::ItemIdents, }; pub struct VersionedVariant { pub original_attributes: Vec, + pub previous_docs: BTreeMap>, pub changes: Option>, pub idents: VariantIdents, pub fields: Fields, @@ -39,10 +40,12 @@ impl VersionedVariant { attrs: Vec::new(), bang_token: Not([Span::call_site()]), }); + let previous_docs = variant_attributes.common.previous_docs(); let changes = variant_attributes.common.into_changeset(&idents, ty); Ok(Self { original_attributes: variant_attributes.attrs, + previous_docs, fields: variant.fields, idents, changes, @@ -63,7 +66,11 @@ impl VersionedVariant { /// Generates tokens to be used in a container definition. pub fn generate_for_container(&self, version: &VersionDefinition) -> Option { - let original_attributes = &self.original_attributes; + let attributes = generate_attributes( + &self.original_attributes, + &self.previous_docs, + &version.inner, + ); let fields = &self.fields; #[allow(clippy::single_match_else)] @@ -80,11 +87,11 @@ impl VersionedVariant { ) }) { ItemStatus::Addition { ident, .. } => Some(quote! { - #(#original_attributes)* + #attributes #ident #fields, }), ItemStatus::Change { to_ident, .. } => Some(quote! { - #(#original_attributes)* + #attributes #to_ident #fields, }), ItemStatus::Deprecation { ident, note, .. } => { @@ -103,7 +110,7 @@ impl VersionedVariant { }; Some(quote! { - #(#original_attributes)* + #attributes #deprecated_attr #ident #fields, }) @@ -118,7 +125,7 @@ impl VersionedVariant { let deprecated_attr = previously_deprecated.then(|| quote! {#[deprecated]}); Some(quote! { - #(#original_attributes)* + #attributes #deprecated_attr #ident #fields, }) @@ -132,7 +139,7 @@ impl VersionedVariant { let ident = &self.idents.original; Some(quote! { - #(#original_attributes)* + #attributes #ident #fields, }) } diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 4e666a6b2..9859ea734 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -496,6 +496,7 @@ mod utils; /// - `since` to indicate since which version the item is changed. /// - `from_name` to indicate from which previous name the field is renamed. /// - `from_type` to indicate from which previous type the field is changed. +/// - `from_docs` to provide the previous doc comments of the item. /// - `upgrade_with` to provide a custom upgrade function. This argument can /// only be used in combination with the `from_type` argument. The expected /// function signature is: `fn (OLD_TYPE) -> NEW_TYPE`. This function must @@ -561,6 +562,59 @@ mod utils; /// ``` /// /// +/// #### Changed Docs +/// +/// The doc comments of an item can be changed using the `from_docs` argument. +/// The doc comments attached to the item are used since the version of the +/// change. All earlier versions use the doc comments provided via `from_docs` +/// instead. This is especially useful to adjust the description of a field in +/// the generated CRD schema without changing it for older versions. The +/// argument can be used on its own or in combination with any other argument. +/// +/// ``` +/// # use stackable_versioned_macros::versioned; +/// #[versioned(version(name = "v1alpha1"), version(name = "v1beta1"))] +/// mod versioned { +/// pub struct Foo { +/// /// The number of bars. +/// #[versioned(changed(since = "v1beta1", from_docs = "The bar."))] +/// bar: usize, +/// baz: bool, +/// } +/// } +/// ``` +/// +///
+/// Expand Generated Code +/// +/// 1. In version `v1alpha1` the field uses the doc comments provided via +/// `from_docs`. +/// 2. In the next version, `v1beta1`, the field uses the doc comments attached +/// to the field. +/// +/// ```ignore +/// pub mod v1alpha1 { +/// use super::*; +/// pub struct Foo { +/// /// The bar. // 1 +/// pub bar: usize, +/// pub baz: bool, +/// } +/// } +/// +/// // Snip +/// +/// pub mod v1beta1 { +/// use super::*; +/// pub struct Foo { +/// /// The number of bars. // 2 +/// pub bar: usize, +/// pub baz: bool, +/// } +/// } +/// ``` +///
+/// /// ### Deprecated Action /// /// This action indicates that an item is deprecated in a particular version. diff --git a/crates/stackable-versioned-macros/tests/inputs/fail/changed.stderr b/crates/stackable-versioned-macros/tests/inputs/fail/changed.stderr index 61d10138f..417ca1250 100644 --- a/crates/stackable-versioned-macros/tests/inputs/fail/changed.stderr +++ b/crates/stackable-versioned-macros/tests/inputs/fail/changed.stderr @@ -10,7 +10,7 @@ error: the previous name must not start with the deprecation prefix 13 | changed(since = "v1", from_name = "deprecated_baz"), | ^^^^^^^^^^^^^^^^ -error: both `from_name` and `from_type` are unset. Is this `changed()` action needed? +error: `from_name`, `from_type` and `from_docs` are unset. Is this `changed()` action needed? --> tests/inputs/fail/changed.rs:14:29 | 14 | changed(since = "v2") diff --git a/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs b/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs index 23888ab90..c00fd6d5b 100644 --- a/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs +++ b/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs @@ -35,6 +35,37 @@ mod versioned { #[versioned(changed(since = "v1beta1", from_name = "qoox"))] #[versioned(changed(since = "v1", from_name = "qaax"))] quux: String, + + /// The docs of this field changed in v1beta1 and v2. + #[versioned( + changed(since = "v1beta1", from_docs = "These are the docs in v1alpha1."), + changed( + since = "v2", + from_docs = r#" + These are the docs from v1beta1 until v1. + + Multi-line docs are also supported. + "# + ) + )] + #[doc(alias = "grault")] + corge: String, + + /// The docs of this field changed in v1, while it was renamed in v1beta1. + #[versioned( + changed(since = "v1beta1", from_name = "waldo"), + changed(since = "v1", from_docs = "These are the docs before v1.") + )] + fred: String, + } + + /// Test + #[derive(Default)] + enum Bar { + /// The docs of this variant changed in v1. + #[versioned(changed(since = "v1", from_docs = "These are the docs before v1."))] + #[default] + Baz, } } // --- diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap index d37c68374..684338443 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap @@ -15,6 +15,18 @@ mod v1alpha1 { pub bar: String, /// This is will keep changing over time. pub qoox: String, + ///These are the docs in v1alpha1. + #[doc(alias = "grault")] + pub corge: String, + ///These are the docs before v1. + pub waldo: String, + } + /// Test + #[derive(Default)] + pub enum Bar { + ///These are the docs before v1. + #[default] + Baz, } } #[automatically_derived] @@ -31,6 +43,8 @@ where deprecated_bar: __sv_foo.bar.into(), baz: ::std::default::Default::default(), qaax: __sv_foo.qoox.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.waldo.into(), }; if let Some(upgrades) = status.changes().upgrades.remove(&"v1beta1".to_owned()) { for ::stackable_versioned::ChangedValue { json_path, value } in upgrades { @@ -68,11 +82,29 @@ where foo: __sv_foo.foo.into(), bar: __sv_foo.deprecated_bar.into(), qoox: __sv_foo.qaax.into(), + corge: __sv_foo.corge.into(), + waldo: __sv_foo.fred.into(), }; spec } } #[automatically_derived] +impl ::core::convert::From for v1beta1::Bar { + fn from(__sv_bar: v1alpha1::Bar) -> Self { + match __sv_bar { + v1alpha1::Bar::Baz => v1beta1::Bar::Baz, + } + } +} +#[automatically_derived] +impl ::core::convert::From for v1alpha1::Bar { + fn from(__sv_bar: v1beta1::Bar) -> Self { + match __sv_bar { + v1beta1::Bar::Baz => v1alpha1::Bar::Baz, + } + } +} +#[automatically_derived] mod v1beta1 { use super::*; ///Additional docs for this version which are purposefully long to @@ -90,6 +122,23 @@ mod v1beta1 { pub baz: String, /// This is will keep changing over time. pub qaax: String, + ///These are the docs from v1beta1 until v1. + /// + ///Multi-line docs are also supported. + #[doc(alias = "grault")] + pub corge: String, + ///These are the docs before v1. + pub fred: String, + } + ///Additional docs for this version which are purposefully long to + ///show how manual line wrapping works. \ + ///Multi-line docs are also supported, as per regular doc-comments. + /// Test + #[derive(Default)] + pub enum Bar { + ///These are the docs before v1. + #[default] + Baz, } } #[automatically_derived] @@ -105,6 +154,8 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), qaax: __sv_foo.qaax.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } @@ -122,11 +173,29 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), qaax: __sv_foo.qaax.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } } #[automatically_derived] +impl ::core::convert::From for v1beta2::Bar { + fn from(__sv_bar: v1beta1::Bar) -> Self { + match __sv_bar { + v1beta1::Bar::Baz => v1beta2::Bar::Baz, + } + } +} +#[automatically_derived] +impl ::core::convert::From for v1beta1::Bar { + fn from(__sv_bar: v1beta2::Bar) -> Self { + match __sv_bar { + v1beta2::Bar::Baz => v1beta1::Bar::Baz, + } + } +} +#[automatically_derived] mod v1beta2 { use super::*; /// Test @@ -141,6 +210,20 @@ mod v1beta2 { pub baz: String, /// This is will keep changing over time. pub qaax: String, + ///These are the docs from v1beta1 until v1. + /// + ///Multi-line docs are also supported. + #[doc(alias = "grault")] + pub corge: String, + ///These are the docs before v1. + pub fred: String, + } + /// Test + #[derive(Default)] + pub enum Bar { + ///These are the docs before v1. + #[default] + Baz, } } #[automatically_derived] @@ -156,6 +239,8 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), quux: __sv_foo.qaax.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } @@ -173,11 +258,29 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), qaax: __sv_foo.quux.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } } #[automatically_derived] +impl ::core::convert::From for v1::Bar { + fn from(__sv_bar: v1beta2::Bar) -> Self { + match __sv_bar { + v1beta2::Bar::Baz => v1::Bar::Baz, + } + } +} +#[automatically_derived] +impl ::core::convert::From for v1beta2::Bar { + fn from(__sv_bar: v1::Bar) -> Self { + match __sv_bar { + v1::Bar::Baz => v1beta2::Bar::Baz, + } + } +} +#[automatically_derived] mod v1 { use super::*; /// Test @@ -192,6 +295,20 @@ mod v1 { pub baz: String, /// This is will keep changing over time. pub quux: String, + ///These are the docs from v1beta1 until v1. + /// + ///Multi-line docs are also supported. + #[doc(alias = "grault")] + pub corge: String, + /// The docs of this field changed in v1, while it was renamed in v1beta1. + pub fred: String, + } + /// Test + #[derive(Default)] + pub enum Bar { + /// The docs of this variant changed in v1. + #[default] + Baz, } } #[automatically_derived] @@ -207,6 +324,8 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), quux: __sv_foo.quux.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } @@ -224,11 +343,29 @@ where deprecated_bar: __sv_foo.deprecated_bar.into(), baz: __sv_foo.baz.into(), quux: __sv_foo.quux.into(), + corge: __sv_foo.corge.into(), + fred: __sv_foo.fred.into(), }; spec } } #[automatically_derived] +impl ::core::convert::From for v2::Bar { + fn from(__sv_bar: v1::Bar) -> Self { + match __sv_bar { + v1::Bar::Baz => v2::Bar::Baz, + } + } +} +#[automatically_derived] +impl ::core::convert::From for v1::Bar { + fn from(__sv_bar: v2::Bar) -> Self { + match __sv_bar { + v2::Bar::Baz => v1::Bar::Baz, + } + } +} +#[automatically_derived] mod v2 { use super::*; /// Test @@ -243,5 +380,17 @@ mod v2 { pub baz: String, /// This is will keep changing over time. pub quux: String, + /// The docs of this field changed in v1beta1 and v2. + #[doc(alias = "grault")] + pub corge: String, + /// The docs of this field changed in v1, while it was renamed in v1beta1. + pub fred: String, + } + /// Test + #[derive(Default)] + pub enum Bar { + /// The docs of this variant changed in v1. + #[default] + Baz, } } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 3cf01678b..a2bc02e7a 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -8,9 +8,11 @@ All notable changes to this project will be documented in this file. - Add `#[versioned(hint(map))]` to provide a hint for map types during conversion ([#1285]). - Emit generics in plain `From` impl ([#1284]). +- Add `from_docs` argument to the `changed()` action to provide the previous doc comments of an item ([#XXXX]). [#1284]: https://github.com/stackabletech/operator-rs/pull/1284 [#1285]: https://github.com/stackabletech/operator-rs/pull/1285 +[#XXXX]: https://github.com/stackabletech/operator-rs/pull/XXXX ## [0.11.1] - 2026-07-06 From 67f796f396b532e8fd2733e7e1c06c12d54c7208 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 13:07:05 +0200 Subject: [PATCH 2/9] chore(versioned): Improve doc comment formatting --- .../src/utils/doc_comments.rs | 6 ++++-- ...oned_macros__snapshots__pass@basic.rs.snap | 10 +++++----- ...napshots__pass@conversion_tracking.rs.snap | 10 +++++----- ...s__snapshots__pass@crate_overrides.rs.snap | 10 +++++----- ...ts__pass@crate_overrides_only_kube.rs.snap | 10 +++++----- ...ed_macros__snapshots__pass@crd_doc.rs.snap | 10 +++++----- ...ned_macros__snapshots__pass@module.rs.snap | 20 +++++++++---------- ...s__snapshots__pass@module_preserve.rs.snap | 20 +++++++++---------- ...cros__snapshots__pass@renamed_kind.rs.snap | 10 +++++----- ...oned_macros__snapshots__pass@scale.rs.snap | 10 +++++----- ...macros__snapshots__pass@shortnames.rs.snap | 10 +++++----- 11 files changed, 64 insertions(+), 62 deletions(-) diff --git a/crates/stackable-versioned-macros/src/utils/doc_comments.rs b/crates/stackable-versioned-macros/src/utils/doc_comments.rs index 6bd8b5c96..6301bbc97 100644 --- a/crates/stackable-versioned-macros/src/utils/doc_comments.rs +++ b/crates/stackable-versioned-macros/src/utils/doc_comments.rs @@ -12,8 +12,10 @@ impl DocComments for &str { .trim() .lines() // Trim the leading and trailing whitespace on each line that can be - // introduced when the developer indents multi-line comments. - .map(|line| line.trim().to_owned()) + // introduced when the developer indents multi-line comments. We then + // re-add a leading space to be in line with all other, non-generated + // dod comments. + .map(|line| format!(" {line}", line = line.trim())) .collect() } } diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@basic.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@basic.rs.snap index a14fda07e..7668bc375 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@basic.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@basic.rs.snap @@ -180,13 +180,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@conversion_tracking.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@conversion_tracking.rs.snap index 57f5cf4ae..4462b74b3 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@conversion_tracking.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@conversion_tracking.rs.snap @@ -224,13 +224,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides.rs.snap index 03cbedf1f..b2abee3ab 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides.rs.snap @@ -167,13 +167,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides_only_kube.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides_only_kube.rs.snap index 5a3a81261..b20b1a2c9 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides_only_kube.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crate_overrides_only_kube.rs.snap @@ -167,13 +167,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crd_doc.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crd_doc.rs.snap index 54b5f73f6..a238967b3 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crd_doc.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@crd_doc.rs.snap @@ -41,13 +41,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module.rs.snap index 16eeb2106..1ca637e89 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module.rs.snap @@ -383,13 +383,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { @@ -689,13 +689,13 @@ impl Bar { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Bar`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Bar`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module_preserve.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module_preserve.rs.snap index bcf1f5ef0..6478610a9 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@module_preserve.rs.snap @@ -356,13 +356,13 @@ pub(crate) mod versioned { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { @@ -657,13 +657,13 @@ pub(crate) mod versioned { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Bar`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Bar`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@renamed_kind.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@renamed_kind.rs.snap index 83968392c..d19138b94 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@renamed_kind.rs.snap @@ -167,13 +167,13 @@ impl FooBar { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`FooBar`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`FooBar`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@scale.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@scale.rs.snap index f05fdf09e..0f136f4e8 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@scale.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@scale.rs.snap @@ -49,13 +49,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@shortnames.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@shortnames.rs.snap index c7a3b584e..2d6d012ee 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@shortnames.rs.snap @@ -43,13 +43,13 @@ impl Foo { stored_apiversion.as_version_str(), ) } - ///Tries to convert a list of objects of kind [`Foo`] to the desired API version - ///specified in the [`ConversionReview`][cr]. + /// Tries to convert a list of objects of kind [`Foo`] to the desired API version + /// specified in the [`ConversionReview`][cr]. /// - ///The returned [`ConversionReview`][cr] either indicates a success or a failure, which - ///is handed back to the Kubernetes API server. + /// The returned [`ConversionReview`][cr] either indicates a success or a failure, which + /// is handed back to the Kubernetes API server. /// - ///[cr]: ::kube::core::conversion::ConversionReview + /// [cr]: ::kube::core::conversion::ConversionReview pub fn try_convert( review: ::kube::core::conversion::ConversionReview, ) -> ::kube::core::conversion::ConversionReview { From bfb42c620c09c761baf440a3bd4e3c7e8c60e4a0 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 13:08:31 +0200 Subject: [PATCH 3/9] feat(versioned): Implement DocComments for (&)String --- .../stackable-versioned-macros/src/attrs/item/mod.rs | 2 +- .../src/utils/doc_comments.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index fe63e28d3..68d2d9e65 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -298,7 +298,7 @@ impl CommonItemAttributes { .iter() .filter_map(|change| { let docs = change.from_docs.as_deref()?; - Some((*change.since, docs.as_str().into_doc_comments())) + Some((*change.since, docs.into_doc_comments())) }) .collect() } diff --git a/crates/stackable-versioned-macros/src/utils/doc_comments.rs b/crates/stackable-versioned-macros/src/utils/doc_comments.rs index 6301bbc97..f4c06ae9f 100644 --- a/crates/stackable-versioned-macros/src/utils/doc_comments.rs +++ b/crates/stackable-versioned-macros/src/utils/doc_comments.rs @@ -20,6 +20,18 @@ impl DocComments for &str { } } +impl DocComments for String { + fn into_doc_comments(self) -> Vec { + self.as_str().into_doc_comments() + } +} + +impl DocComments for &String { + fn into_doc_comments(self) -> Vec { + self.as_str().into_doc_comments() + } +} + impl DocComments for Option<&str> { fn into_doc_comments(self) -> Vec { self.map_or(vec![], DocComments::into_doc_comments) From e3565587564c2b0762e5f75a109bde3831a4ec26 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 13:10:31 +0200 Subject: [PATCH 4/9] test(versioned): Update snapshot test to ensure attributes are preserved --- .../tests/inputs/pass/docs.rs | 4 +- ...ioned_macros__snapshots__pass@docs.rs.snap | 53 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs b/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs index c00fd6d5b..13f7c8d11 100644 --- a/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs +++ b/crates/stackable-versioned-macros/tests/inputs/pass/docs.rs @@ -18,7 +18,7 @@ use stackable_versioned::versioned; // --- mod versioned { /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] struct Foo { /// This field is available in every version (so far). foo: String, @@ -48,7 +48,9 @@ mod versioned { "# ) )] + // Those must be preserved. #[doc(alias = "grault")] + #[serde(rename = "corge")] corge: String, /// The docs of this field changed in v1, while it was renamed in v1beta1. diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap index 684338443..e79b8d287 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshots__pass@docs.rs.snap @@ -7,7 +7,7 @@ input_file: crates/stackable-versioned-macros/tests/inputs/pass/docs.rs mod v1alpha1 { use super::*; /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] pub struct Foo { /// This field is available in every version (so far). pub foo: String, @@ -15,16 +15,17 @@ mod v1alpha1 { pub bar: String, /// This is will keep changing over time. pub qoox: String, - ///These are the docs in v1alpha1. + /// These are the docs in v1alpha1. #[doc(alias = "grault")] + #[serde(rename = "corge")] pub corge: String, - ///These are the docs before v1. + /// These are the docs before v1. pub waldo: String, } /// Test #[derive(Default)] pub enum Bar { - ///These are the docs before v1. + /// These are the docs before v1. #[default] Baz, } @@ -107,11 +108,11 @@ impl ::core::convert::From for v1alpha1::Bar { #[automatically_derived] mod v1beta1 { use super::*; - ///Additional docs for this version which are purposefully long to - ///show how manual line wrapping works. \ - ///Multi-line docs are also supported, as per regular doc-comments. + /// Additional docs for this version which are purposefully long to + /// show how manual line wrapping works. \ + /// Multi-line docs are also supported, as per regular doc-comments. /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] pub struct Foo { /// This field is available in every version (so far). pub foo: String, @@ -122,21 +123,22 @@ mod v1beta1 { pub baz: String, /// This is will keep changing over time. pub qaax: String, - ///These are the docs from v1beta1 until v1. + /// These are the docs from v1beta1 until v1. /// - ///Multi-line docs are also supported. + /// Multi-line docs are also supported. #[doc(alias = "grault")] + #[serde(rename = "corge")] pub corge: String, - ///These are the docs before v1. + /// These are the docs before v1. pub fred: String, } - ///Additional docs for this version which are purposefully long to - ///show how manual line wrapping works. \ - ///Multi-line docs are also supported, as per regular doc-comments. + /// Additional docs for this version which are purposefully long to + /// show how manual line wrapping works. \ + /// Multi-line docs are also supported, as per regular doc-comments. /// Test #[derive(Default)] pub enum Bar { - ///These are the docs before v1. + /// These are the docs before v1. #[default] Baz, } @@ -199,7 +201,7 @@ impl ::core::convert::From for v1beta1::Bar { mod v1beta2 { use super::*; /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] pub struct Foo { /// This field is available in every version (so far). pub foo: String, @@ -210,18 +212,19 @@ mod v1beta2 { pub baz: String, /// This is will keep changing over time. pub qaax: String, - ///These are the docs from v1beta1 until v1. + /// These are the docs from v1beta1 until v1. /// - ///Multi-line docs are also supported. + /// Multi-line docs are also supported. #[doc(alias = "grault")] + #[serde(rename = "corge")] pub corge: String, - ///These are the docs before v1. + /// These are the docs before v1. pub fred: String, } /// Test #[derive(Default)] pub enum Bar { - ///These are the docs before v1. + /// These are the docs before v1. #[default] Baz, } @@ -284,7 +287,7 @@ impl ::core::convert::From for v1beta2::Bar { mod v1 { use super::*; /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] pub struct Foo { /// This field is available in every version (so far). pub foo: String, @@ -295,10 +298,11 @@ mod v1 { pub baz: String, /// This is will keep changing over time. pub quux: String, - ///These are the docs from v1beta1 until v1. + /// These are the docs from v1beta1 until v1. /// - ///Multi-line docs are also supported. + /// Multi-line docs are also supported. #[doc(alias = "grault")] + #[serde(rename = "corge")] pub corge: String, /// The docs of this field changed in v1, while it was renamed in v1beta1. pub fred: String, @@ -369,7 +373,7 @@ impl ::core::convert::From for v1::Bar { mod v2 { use super::*; /// Test - #[derive(Default)] + #[derive(Default, serde::Deserialize)] pub struct Foo { /// This field is available in every version (so far). pub foo: String, @@ -382,6 +386,7 @@ mod v2 { pub quux: String, /// The docs of this field changed in v1beta1 and v2. #[doc(alias = "grault")] + #[serde(rename = "corge")] pub corge: String, /// The docs of this field changed in v1, while it was renamed in v1beta1. pub fred: String, From 21745dc2e8296159df2894bf1014b504b7414f30 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 14:48:43 +0200 Subject: [PATCH 5/9] chore(versioned): Add doc comments, rename previous_docs to changed_docs --- .../src/attrs/item/mod.rs | 14 +++++++++++--- .../src/codegen/item/field.rs | 17 +++++++++++++---- .../src/codegen/item/mod.rs | 4 ++-- .../src/codegen/item/variant.rs | 8 ++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index 68d2d9e65..7b4092f52 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -291,9 +291,17 @@ impl CommonItemAttributes { } impl CommonItemAttributes { - /// Returns the doc comments of the item before each change which provides `from_docs`, keyed - /// by the version of the change. - pub fn previous_docs(&self) -> BTreeMap> { + /// Returns a sparse map of the changed doc comments of the item, keyed by the version of the + /// change. + /// + /// Sparse in this context means that not every version is present as a key. Only versions + /// containing a doc change are included. This is explicitly different from + /// `VersionedField::changes` for which the code guarantees that there is an entry for all + /// versions. + // + // NOTE (@Techassi): Potentially roll this into the changeset below so that there will be a + // single sure of truth for field generation again. + pub fn changed_docs(&self) -> BTreeMap> { self.changes .iter() .filter_map(|change| { diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index ecc81155e..2b35f984c 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -20,7 +20,16 @@ use crate::{ #[derive(Debug)] pub struct VersionedField { pub original_attributes: Vec, - pub previous_docs: BTreeMap>, + + /// A sparse map of changed doc comments. + /// + /// This map being sparse means that access to it must be able to deal with a version not being + /// present. Using `unwrap()`, [`BTreeMapExt::get_expect`] or other similar functions is not + /// recommended. Also see `CommonItemAttributes::changed_docs` for further details. + // + // NOTE (@Techassi): Potentially roll this into the changeset below so that there will be a + // single sure of truth for field generation again. + pub changed_docs: BTreeMap>, pub changes: Option>, pub idents: FieldIdents, pub hint: Option, @@ -46,7 +55,7 @@ impl VersionedField { })?; let idents = FieldIdents::from(ident); - let previous_docs = field_attributes.common.previous_docs(); + let changed_docs = field_attributes.common.changed_docs(); let changes = field_attributes .common .into_changeset(&idents, field.ty.clone()); @@ -55,7 +64,7 @@ impl VersionedField { Ok(Self { original_attributes: field_attributes.attrs, hint: field_attributes.hint, - previous_docs, + changed_docs, ty: field.ty, changes, idents, @@ -87,7 +96,7 @@ impl VersionedField { pub fn generate_for_container(&self, version: &VersionDefinition) -> Option { let attributes = generate_attributes( &self.original_attributes, - &self.previous_docs, + &self.changed_docs, &version.inner, ); diff --git a/crates/stackable-versioned-macros/src/codegen/item/mod.rs b/crates/stackable-versioned-macros/src/codegen/item/mod.rs index f26b17747..9696b7adb 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/mod.rs @@ -20,12 +20,12 @@ pub use variant::*; /// comments are replaced by the docs which are valid in `version`. pub fn generate_attributes( original_attributes: &[Attribute], - previous_docs: &BTreeMap>, + changed_docs: &BTreeMap>, version: &Version, ) -> TokenStream { // The docs valid in this version are the previous docs of the closest change after this // version. If there is no such change, the original docs are still valid. - let Some((_, docs)) = previous_docs.up_bound(Bound::Excluded(version)) else { + let Some((_, docs)) = changed_docs.up_bound(Bound::Excluded(version)) else { return quote! { #(#original_attributes)* }; }; diff --git a/crates/stackable-versioned-macros/src/codegen/item/variant.rs b/crates/stackable-versioned-macros/src/codegen/item/variant.rs index 6455ef6b7..d97e10c1b 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/variant.rs @@ -20,7 +20,7 @@ use crate::{ pub struct VersionedVariant { pub original_attributes: Vec, - pub previous_docs: BTreeMap>, + pub changed_docs: BTreeMap>, pub changes: Option>, pub idents: VariantIdents, pub fields: Fields, @@ -40,12 +40,12 @@ impl VersionedVariant { attrs: Vec::new(), bang_token: Not([Span::call_site()]), }); - let previous_docs = variant_attributes.common.previous_docs(); + let changed_docs = variant_attributes.common.changed_docs(); let changes = variant_attributes.common.into_changeset(&idents, ty); Ok(Self { original_attributes: variant_attributes.attrs, - previous_docs, + changed_docs, fields: variant.fields, idents, changes, @@ -68,7 +68,7 @@ impl VersionedVariant { pub fn generate_for_container(&self, version: &VersionDefinition) -> Option { let attributes = generate_attributes( &self.original_attributes, - &self.previous_docs, + &self.changed_docs, &version.inner, ); let fields = &self.fields; From 6f91b5cbc110ff66369a86684991299567b1dfaa Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 15:23:15 +0200 Subject: [PATCH 6/9] docs(versioned): Tweak top-level doc comments --- crates/stackable-versioned-macros/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 9859ea734..6813c61d2 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -490,8 +490,9 @@ mod utils; /// ### Changed Action /// /// This action indicates that an item is changed in a particular version. It -/// combines renames and type changes into a single action. You can choose to -/// change the name, change the type or do both. Available arguments are: +/// combines renames, type changes and doc comment changes into a single action. +/// You can choose to change the name, change the type, change the doc comment +/// or combine multiple changes. Available arguments are: /// /// - `since` to indicate since which version the item is changed. /// - `from_name` to indicate from which previous name the field is renamed. @@ -565,11 +566,10 @@ mod utils; /// #### Changed Docs /// /// The doc comments of an item can be changed using the `from_docs` argument. -/// The doc comments attached to the item are used since the version of the -/// change. All earlier versions use the doc comments provided via `from_docs` -/// instead. This is especially useful to adjust the description of a field in -/// the generated CRD schema without changing it for older versions. The -/// argument can be used on its own or in combination with any other argument. +/// The doc comments attached to the item are used after the version of the +/// last relevant change. All earlier versions use the doc comments provided via +/// `from_docs` instead. This is especially useful to adjust the description of +/// a field in the generated CRD schema without changing it for older versions. /// /// ``` /// # use stackable_versioned_macros::versioned; @@ -596,7 +596,7 @@ mod utils; /// pub mod v1alpha1 { /// use super::*; /// pub struct Foo { -/// /// The bar. // 1 +/// /// The bar. // 1 /// pub bar: usize, /// pub baz: bool, /// } @@ -607,7 +607,7 @@ mod utils; /// pub mod v1beta1 { /// use super::*; /// pub struct Foo { -/// /// The number of bars. // 2 +/// /// The number of bars. // 2 /// pub bar: usize, /// pub baz: bool, /// } From 8cc0bbae2915995e316f1d446dcaeca54cd241e3 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 15:27:24 +0200 Subject: [PATCH 7/9] chore(versioned): Update changelog entry --- crates/stackable-versioned/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index a2bc02e7a..0a5a39fbf 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -6,13 +6,13 @@ All notable changes to this project will be documented in this file. ### Added +- Add `from_docs` argument to the `changed()` action to provide the previous doc comments of an item ([#1293]). - Add `#[versioned(hint(map))]` to provide a hint for map types during conversion ([#1285]). - Emit generics in plain `From` impl ([#1284]). -- Add `from_docs` argument to the `changed()` action to provide the previous doc comments of an item ([#XXXX]). [#1284]: https://github.com/stackabletech/operator-rs/pull/1284 [#1285]: https://github.com/stackabletech/operator-rs/pull/1285 -[#XXXX]: https://github.com/stackabletech/operator-rs/pull/XXXX +[#1293]: https://github.com/stackabletech/operator-rs/pull/1293 ## [0.11.1] - 2026-07-06 From 058ebeaeb39b8008058c02e5661531f29de62606 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 16:14:49 +0200 Subject: [PATCH 8/9] chore: Temporarily allow RUSTSEC-2024-0449 advisory --- deny.toml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/deny.toml b/deny.toml index 8ec7e4528..92f723dc0 100644 --- a/deny.toml +++ b/deny.toml @@ -38,6 +38,19 @@ ignore = [ # # This can only be removed again if we decide to use a different crate. "RUSTSEC-2024-0436", + + # https://rustsec.org/advisories/RUSTSEC-2024-0449 + # The https://github.com/dtolnay/unsafe-libyaml repository was archived by + # its owner on 2024-03-25 and is read-only. The last release is 0.2.11 + # (March 2024). + # + # `unsafe-libyaml` is a machine translation of libyaml's C code into unsafe + # Rust (c2rust). Bugs found in it will not be fixed upstream. It is used by + # `serde_yaml` (itself deprecated) and by forks such as `serde_yaml_ng`. + # + # This can only be removed again once we decide which alternative YAML crate we want to migrate + # to. Currently, we favor `serde-saphyr`. + "RUSTSEC-2024-0449", ] [bans] @@ -67,23 +80,17 @@ private = { ignore = true } [[licenses.clarify]] name = "ring" expression = "LicenseRef-ring" -license-files = [ - { path = "LICENSE", hash = 0xbd0eed23 }, -] +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] [[licenses.clarify]] name = "webpki" expression = "LicenseRef-webpki" -license-files = [ - { path = "LICENSE", hash = 0x001c7e6c }, -] +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] [sources] unknown-registry = "deny" unknown-git = "deny" -allow-git = [ - "https://github.com/kube-rs/kube-rs", -] +allow-git = ["https://github.com/kube-rs/kube-rs"] [sources.allow-org] github = ["stackabletech"] From 7b1f437e02a1af34cc2c8e748d8f5517d4ba9f18 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 12 Oct 2026 16:20:24 +0200 Subject: [PATCH 9/9] chore: Revert formatting in deny.toml --- deny.toml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/deny.toml b/deny.toml index 92f723dc0..cc96f58e1 100644 --- a/deny.toml +++ b/deny.toml @@ -80,17 +80,23 @@ private = { ignore = true } [[licenses.clarify]] name = "ring" expression = "LicenseRef-ring" -license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 }, +] [[licenses.clarify]] name = "webpki" expression = "LicenseRef-webpki" -license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] +license-files = [ + { path = "LICENSE", hash = 0x001c7e6c }, +] [sources] unknown-registry = "deny" unknown-git = "deny" -allow-git = ["https://github.com/kube-rs/kube-rs"] +allow-git = [ + "https://github.com/kube-rs/kube-rs", +] [sources.allow-org] github = ["stackabletech"]