Skip to content

feat(stackable-versioned-macros): Handle attribute forwarding / doc generation #816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
4 changes: 4 additions & 0 deletions crates/stackable-operator-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/src/attrs/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub(crate) struct VersionAttributes {
pub(crate) deprecated: Flag,
pub(crate) name: Version,
pub(crate) skip: Option<SkipOptions>,
pub(crate) doc: Option<String>,
}

/// This struct contains supported container options.
Expand Down
24 changes: 23 additions & 1 deletion crates/stackable-versioned-macros/src/attrs/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,6 +36,8 @@ pub(crate) struct FieldAttributes {
pub(crate) renames: Vec<RenamedAttributes>,

pub(crate) deprecated: Option<DeprecatedAttributes>,

pub(crate) attrs: Vec<syn::Attribute>,
}

#[derive(Clone, Debug, FromMeta)]
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion crates/stackable-versioned-macros/src/gen/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,6 +22,7 @@ use crate::{
pub(crate) struct VersionedField {
chain: Option<BTreeMap<Version, FieldStatus>>,
inner: Field,
attrs: Vec<Attribute>,
}

impl VersionedField {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -123,6 +125,7 @@ impl VersionedField {
Ok(Self {
chain: Some(actions),
inner: field,
attrs: attrs.attrs,
})
} else {
if let Some(added) = attrs.added {
Expand All @@ -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,
})
}
}
Expand Down Expand Up @@ -211,6 +216,7 @@ impl VersionedField {
&self,
container_version: &ContainerVersion,
) -> Option<TokenStream> {
let attrs = &self.attrs;
match &self.chain {
Some(chain) => {
// Check if the provided container version is present in the map
Expand All @@ -228,21 +234,25 @@ 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 {
ident: field_ident,
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,
}),
}
Expand All @@ -255,6 +265,7 @@ impl VersionedField {
let field_type = &self.inner.ty;

Some(quote! {
#(#attrs)*
pub #field_ident: #field_type,
})
}
Expand Down
4 changes: 3 additions & 1 deletion crates/stackable-versioned-macros/src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ pub(crate) mod vstruct;

pub(crate) fn expand(attrs: ContainerAttributes, input: DeriveInput) -> Result<TokenStream> {
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(),
Expand Down
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/src/gen/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}
20 changes: 19 additions & 1 deletion crates/stackable-versioned-macros/src/gen/vstruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -30,13 +30,17 @@ pub(crate) struct VersionedStruct {
pub(crate) fields: Vec<VersionedField>,

pub(crate) skip_from: bool,

/// The original attributes that were added to the struct.
pub(crate) original_attrs: Vec<Attribute>,
}

impl VersionedStruct {
pub(crate) fn new(
ident: Ident,
data: DataStruct,
attributes: ContainerAttributes,
original_attrs: Vec<Attribute>,
) -> Result<Self> {
// Convert the raw version attributes into a container version.
let versions = attributes
Expand All @@ -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();
Expand Down Expand Up @@ -98,6 +103,7 @@ impl VersionedStruct {
versions,
fields,
ident,
original_attrs,
})
}

Expand Down Expand Up @@ -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
}
Expand Down
50 changes: 50 additions & 0 deletions crates/stackable-versioned-macros/tests/attributes.rs
Original file line number Diff line number Diff line change
@@ -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"),
};
}
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct Foo {
baz: bool,
}

#[ignore]
#[test]
fn basic() {
let _ = v1alpha1::Foo { jjj: 0, baz: false };
Expand Down
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/tests/deprecate.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use stackable_versioned_macros::versioned;

#[ignore]
#[test]
fn deprecate() {
#[versioned(
Expand Down
2 changes: 2 additions & 0 deletions crates/stackable-versioned-macros/tests/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fn from_custom_default_fn() {
assert!(foo_v1beta1.baz);
}

#[ignore]
#[test]
fn skip_from_all() {
#[versioned(
Expand All @@ -70,6 +71,7 @@ fn skip_from_all() {
}
}

#[ignore]
#[test]
fn skip_from_version() {
#[versioned(
Expand Down
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/tests/rename.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use stackable_versioned_macros::versioned;

#[ignore]
#[test]
fn rename() {
#[versioned(
Expand Down
Loading