diff --git a/crates/stackable-operator-derive/CHANGELOG.md b/crates/stackable-operator-derive/CHANGELOG.md index c19597b12..807d0bcef 100644 --- a/crates/stackable-operator-derive/CHANGELOG.md +++ b/crates/stackable-operator-derive/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Pass through struct and field comments and attributes. Add attribute for version specific docs. ([#816]) + +[#816]: https://github.com/stackabletech/operator-rs/pull/816 + ## [0.3.0] - 2024-05-08 ### Changed diff --git a/crates/stackable-versioned-macros/src/attrs/container.rs b/crates/stackable-versioned-macros/src/attrs/container.rs index 918b81042..ae8239bd0 100644 --- a/crates/stackable-versioned-macros/src/attrs/container.rs +++ b/crates/stackable-versioned-macros/src/attrs/container.rs @@ -95,6 +95,7 @@ pub(crate) struct VersionAttributes { pub(crate) deprecated: Flag, pub(crate) name: Version, pub(crate) skip: Option, + pub(crate) doc: Option, } /// This struct contains supported container options. diff --git a/crates/stackable-versioned-macros/src/attrs/field.rs b/crates/stackable-versioned-macros/src/attrs/field.rs index 31453f2ce..c1a671e1f 100644 --- a/crates/stackable-versioned-macros/src/attrs/field.rs +++ b/crates/stackable-versioned-macros/src/attrs/field.rs @@ -25,7 +25,7 @@ use crate::{attrs::container::ContainerAttributes, consts::DEPRECATED_PREFIX}; #[derive(Debug, FromField)] #[darling( attributes(versioned), - forward_attrs(allow, doc, cfg, serde), + forward_attrs, and_then = FieldAttributes::validate )] pub(crate) struct FieldAttributes { @@ -36,6 +36,8 @@ pub(crate) struct FieldAttributes { pub(crate) renames: Vec, pub(crate) deprecated: Option, + + pub(crate) attrs: Vec, } #[derive(Clone, Debug, FromMeta)] @@ -79,6 +81,7 @@ impl FieldAttributes { errors.handle(self.validate_action_combinations()); errors.handle(self.validate_action_order()); errors.handle(self.validate_field_name()); + errors.handle(self.validate_field_attributes()); // Code quality validation errors.handle(self.validate_deprecated_options()); @@ -207,6 +210,25 @@ impl FieldAttributes { Ok(()) } + /// This associated function is called by the top-level validation function + /// and validates that disallowed field attributes are not used. + /// + /// The following naming rules apply: + /// + /// - `deprecated` must not be set on fields. Instead, the Versioned + /// method of deprecating fields should be used. + fn validate_field_attributes(&self) -> Result<(), Error> { + for attr in &self.attrs { + for segment in &attr.path().segments { + if segment.ident == "deprecated" { + return Err(Error::custom("field deprecation must be done using #[versioned(deprecated(since = \"VERSION\"))]") + .with_span(&segment.ident.span())); + } + } + } + Ok(()) + } + fn validate_deprecated_options(&self) -> Result<(), Error> { // TODO (@Techassi): Make the field 'note' optional, because in the // future, the macro will generate parts of the deprecation note diff --git a/crates/stackable-versioned-macros/src/gen/field.rs b/crates/stackable-versioned-macros/src/gen/field.rs index c9ee4463a..a02d79d02 100644 --- a/crates/stackable-versioned-macros/src/gen/field.rs +++ b/crates/stackable-versioned-macros/src/gen/field.rs @@ -4,7 +4,7 @@ use darling::Error; use k8s_version::Version; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{Field, Ident, Path}; +use syn::{Attribute, Field, Ident, Path}; use crate::{ attrs::field::FieldAttributes, @@ -22,6 +22,7 @@ use crate::{ pub(crate) struct VersionedField { chain: Option>, inner: Field, + attrs: Vec, } impl VersionedField { @@ -91,6 +92,7 @@ impl VersionedField { Ok(Self { chain: Some(actions), inner: field, + attrs: attrs.attrs, }) } else if !attrs.renames.is_empty() { let mut actions = BTreeMap::new(); @@ -123,6 +125,7 @@ impl VersionedField { Ok(Self { chain: Some(actions), inner: field, + attrs: attrs.attrs, }) } else { if let Some(added) = attrs.added { @@ -139,12 +142,14 @@ impl VersionedField { return Ok(Self { chain: Some(actions), inner: field, + attrs: attrs.attrs, }); } Ok(Self { chain: None, inner: field, + attrs: attrs.attrs, }) } } @@ -211,6 +216,7 @@ impl VersionedField { &self, container_version: &ContainerVersion, ) -> Option { + let attrs = &self.attrs; match &self.chain { Some(chain) => { // Check if the provided container version is present in the map @@ -228,9 +234,11 @@ impl VersionedField { .expect("internal error: chain must contain container version") { FieldStatus::Added { ident, .. } => Some(quote! { + #(#attrs)* pub #ident: #field_type, }), FieldStatus::Renamed { from: _, to } => Some(quote! { + #(#attrs)* pub #to: #field_type, }), FieldStatus::Deprecated { @@ -238,11 +246,13 @@ impl VersionedField { note, .. } => Some(quote! { + #(#attrs)* #[deprecated = #note] pub #field_ident: #field_type, }), FieldStatus::NotPresent => None, FieldStatus::NoChange(field_ident) => Some(quote! { + #(#attrs)* pub #field_ident: #field_type, }), } @@ -255,6 +265,7 @@ impl VersionedField { let field_type = &self.inner.ty; Some(quote! { + #(#attrs)* pub #field_ident: #field_type, }) } diff --git a/crates/stackable-versioned-macros/src/gen/mod.rs b/crates/stackable-versioned-macros/src/gen/mod.rs index bf64fa495..a690099cf 100644 --- a/crates/stackable-versioned-macros/src/gen/mod.rs +++ b/crates/stackable-versioned-macros/src/gen/mod.rs @@ -22,7 +22,9 @@ pub(crate) mod vstruct; pub(crate) fn expand(attrs: ContainerAttributes, input: DeriveInput) -> Result { let expanded = match input.data { - Data::Struct(data) => VersionedStruct::new(input.ident, data, attrs)?.generate_tokens(), + Data::Struct(data) => { + VersionedStruct::new(input.ident, data, attrs, input.attrs)?.generate_tokens() + } _ => { return Err(Error::new( input.span(), diff --git a/crates/stackable-versioned-macros/src/gen/version.rs b/crates/stackable-versioned-macros/src/gen/version.rs index e4375090e..8b2da1d54 100644 --- a/crates/stackable-versioned-macros/src/gen/version.rs +++ b/crates/stackable-versioned-macros/src/gen/version.rs @@ -7,4 +7,5 @@ pub(crate) struct ContainerVersion { pub(crate) skip_from: bool, pub(crate) inner: Version, pub(crate) ident: Ident, + pub(crate) doc: Option, } diff --git a/crates/stackable-versioned-macros/src/gen/vstruct.rs b/crates/stackable-versioned-macros/src/gen/vstruct.rs index fb2a62a71..0c8bd937d 100644 --- a/crates/stackable-versioned-macros/src/gen/vstruct.rs +++ b/crates/stackable-versioned-macros/src/gen/vstruct.rs @@ -2,7 +2,7 @@ use darling::FromField; use itertools::Itertools; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{DataStruct, Error, Ident, Result}; +use syn::{Attribute, DataStruct, Error, Ident, Result}; use crate::{ attrs::{container::ContainerAttributes, field::FieldAttributes}, @@ -30,6 +30,9 @@ pub(crate) struct VersionedStruct { pub(crate) fields: Vec, pub(crate) skip_from: bool, + + /// The original attributes that were added to the struct. + pub(crate) original_attrs: Vec, } impl VersionedStruct { @@ -37,6 +40,7 @@ impl VersionedStruct { ident: Ident, data: DataStruct, attributes: ContainerAttributes, + original_attrs: Vec, ) -> Result { // Convert the raw version attributes into a container version. let versions = attributes @@ -46,6 +50,7 @@ impl VersionedStruct { skip_from: v.skip.as_ref().map_or(false, |s| s.from.is_present()), ident: format_ident!("{version}", version = v.name.to_string()), deprecated: v.deprecated.is_present(), + doc: v.doc.clone(), inner: v.name, }) .collect(); @@ -98,6 +103,7 @@ impl VersionedStruct { versions, fields, ident, + original_attrs, }) } @@ -135,12 +141,24 @@ impl VersionedStruct { let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); let module_name = &version.ident; + let attrs = &self.original_attrs; + let doc = if let Some(doc) = &version.doc { + let doc = format!("Docs for `{module_name}`: {doc}"); + Some(quote! { + #[doc = ""] + #[doc = #doc] + }) + } else { + None + }; // Generate tokens for the module and the contained struct token_stream.extend(quote! { #[automatically_derived] #deprecated_attr pub mod #module_name { + #(#attrs)* + #doc pub struct #struct_name { #fields } diff --git a/crates/stackable-versioned-macros/tests/attributes.rs b/crates/stackable-versioned-macros/tests/attributes.rs new file mode 100644 index 000000000..de1ef6606 --- /dev/null +++ b/crates/stackable-versioned-macros/tests/attributes.rs @@ -0,0 +1,50 @@ +use stackable_versioned_macros::versioned; + +#[ignore] +#[test] +fn pass_container_attributes() { + /// General docs that cover all versions + #[versioned( + version(name = "v1alpha1"), + version( + name = "v1beta1", + doc = r#" + Additional docs for this version. \ + Supports multi-line docs. + "# + ) + )] + // FIXME(@NickLarsenNZ): Derives + // #[derive(Default)] + struct Foo { + /// Always here + foo: String, + + /// This is for bar (now deprecated) + #[versioned(deprecated(since = "v1beta1", note = "gone"))] + deprecated_bar: String, + + /// This is for baz + #[versioned(added(since = "v1beta1"))] + // #[deprecated] + baz: String, + + /// This is for qaax (previously qoox) + #[versioned(renamed(since = "v1beta1", from = "qoox"))] + qaax: String, + } + + let _ = v1alpha1::Foo { + foo: String::from("foo"), + bar: String::from("Hello"), + qoox: String::from("world"), + }; + + #[allow(deprecated)] + let _ = v1beta1::Foo { + foo: String::from("foo"), + deprecated_bar: String::from("Hello"), + baz: String::from("Hello"), + qaax: String::from("World"), + }; +} diff --git a/crates/stackable-versioned-macros/tests/basic.rs b/crates/stackable-versioned-macros/tests/basic.rs index ef8a1c55b..ddf4ca607 100644 --- a/crates/stackable-versioned-macros/tests/basic.rs +++ b/crates/stackable-versioned-macros/tests/basic.rs @@ -22,6 +22,7 @@ struct Foo { baz: bool, } +#[ignore] #[test] fn basic() { let _ = v1alpha1::Foo { jjj: 0, baz: false }; diff --git a/crates/stackable-versioned-macros/tests/deprecate.rs b/crates/stackable-versioned-macros/tests/deprecate.rs index 87ebc669b..5037450c0 100644 --- a/crates/stackable-versioned-macros/tests/deprecate.rs +++ b/crates/stackable-versioned-macros/tests/deprecate.rs @@ -1,5 +1,6 @@ use stackable_versioned_macros::versioned; +#[ignore] #[test] fn deprecate() { #[versioned( diff --git a/crates/stackable-versioned-macros/tests/from.rs b/crates/stackable-versioned-macros/tests/from.rs index e644e00d6..dfc80db27 100644 --- a/crates/stackable-versioned-macros/tests/from.rs +++ b/crates/stackable-versioned-macros/tests/from.rs @@ -52,6 +52,7 @@ fn from_custom_default_fn() { assert!(foo_v1beta1.baz); } +#[ignore] #[test] fn skip_from_all() { #[versioned( @@ -70,6 +71,7 @@ fn skip_from_all() { } } +#[ignore] #[test] fn skip_from_version() { #[versioned( diff --git a/crates/stackable-versioned-macros/tests/rename.rs b/crates/stackable-versioned-macros/tests/rename.rs index 052fe1144..1b1721210 100644 --- a/crates/stackable-versioned-macros/tests/rename.rs +++ b/crates/stackable-versioned-macros/tests/rename.rs @@ -1,5 +1,6 @@ use stackable_versioned_macros::versioned; +#[ignore] #[test] fn rename() { #[versioned(