From 8db53e9fbde6203a0a9cfcb6cd1c148e98246058 Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 22 May 2024 14:01:05 +0200 Subject: [PATCH 01/15] Add basic From impl generation --- .../src/attrs/container.rs | 1 + crates/stackable-versioned/src/attrs/field.rs | 3 +- crates/stackable-versioned/src/gen/field.rs | 4 +- crates/stackable-versioned/src/gen/mod.rs | 15 ++-- crates/stackable-versioned/src/gen/version.rs | 2 +- crates/stackable-versioned/src/gen/vstruct.rs | 81 ++++++++++++++----- 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/crates/stackable-versioned/src/attrs/container.rs b/crates/stackable-versioned/src/attrs/container.rs index 56ef87045..a256c1b55 100644 --- a/crates/stackable-versioned/src/attrs/container.rs +++ b/crates/stackable-versioned/src/attrs/container.rs @@ -103,4 +103,5 @@ pub(crate) struct VersionAttributes { #[derive(Clone, Debug, Default, FromMeta)] pub(crate) struct ContainerOptions { pub(crate) allow_unsorted: Flag, + pub(crate) skip_from: Flag, } diff --git a/crates/stackable-versioned/src/attrs/field.rs b/crates/stackable-versioned/src/attrs/field.rs index 24f65a11e..5690f533a 100644 --- a/crates/stackable-versioned/src/attrs/field.rs +++ b/crates/stackable-versioned/src/attrs/field.rs @@ -1,6 +1,6 @@ use darling::{util::SpannedValue, Error, FromField, FromMeta}; use k8s_version::Version; -use syn::{Field, Ident}; +use syn::{Field, Ident, Path}; use crate::{attrs::container::ContainerAttributes, consts::DEPRECATED_PREFIX}; @@ -40,6 +40,7 @@ pub(crate) struct FieldAttributes { #[derive(Clone, Debug, FromMeta)] pub(crate) struct AddedAttributes { pub(crate) since: SpannedValue, + pub(crate) default: Option>, } #[derive(Clone, Debug, FromMeta)] diff --git a/crates/stackable-versioned/src/gen/field.rs b/crates/stackable-versioned/src/gen/field.rs index e6948310b..fbbefa450 100644 --- a/crates/stackable-versioned/src/gen/field.rs +++ b/crates/stackable-versioned/src/gen/field.rs @@ -24,8 +24,8 @@ pub(crate) struct VersionedField { inner: Field, } -impl ToTokensExt for VersionedField { - fn to_tokens_for_version(&self, container_version: &ContainerVersion) -> Option { +impl ToTokensExt<&ContainerVersion> for VersionedField { + fn to_tokens(&self, container_version: &ContainerVersion) -> Option { match &self.chain { Some(chain) => { // Check if the provided container version is present in the map diff --git a/crates/stackable-versioned/src/gen/mod.rs b/crates/stackable-versioned/src/gen/mod.rs index 4c3729873..7d8526dab 100644 --- a/crates/stackable-versioned/src/gen/mod.rs +++ b/crates/stackable-versioned/src/gen/mod.rs @@ -5,7 +5,7 @@ use syn::{spanned::Spanned, Data, DeriveInput, Error, Result}; use crate::{ attrs::container::ContainerAttributes, - gen::{venum::VersionedEnum, version::ContainerVersion, vstruct::VersionedStruct}, + gen::{venum::VersionedEnum, vstruct::VersionedStruct}, }; pub(crate) mod field; @@ -32,9 +32,9 @@ pub(crate) fn expand(input: DeriveInput) -> Result { // Validate container shape and generate code let expanded = match input.data { - Data::Struct(data) => { - VersionedStruct::new(input.ident, data, attributes)?.to_token_stream() - } + Data::Struct(data) => VersionedStruct::new(input.ident, data, attributes)? + .to_tokens(true) + .expect("internal error: must produce tokens for versioned struct"), Data::Enum(data) => VersionedEnum::new(input.ident, data, attributes)?.to_token_stream(), Data::Union(_) => { return Err(Error::new( @@ -47,6 +47,9 @@ pub(crate) fn expand(input: DeriveInput) -> Result { Ok(expanded) } -pub(crate) trait ToTokensExt { - fn to_tokens_for_version(&self, version: &ContainerVersion) -> Option; +pub(crate) trait ToTokensExt +where + T: Copy, +{ + fn to_tokens(&self, state: T) -> Option; } diff --git a/crates/stackable-versioned/src/gen/version.rs b/crates/stackable-versioned/src/gen/version.rs index ca5d7f0f5..141a49d50 100644 --- a/crates/stackable-versioned/src/gen/version.rs +++ b/crates/stackable-versioned/src/gen/version.rs @@ -1,6 +1,6 @@ use k8s_version::Version; -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct ContainerVersion { pub(crate) deprecated: bool, pub(crate) inner: Version, diff --git a/crates/stackable-versioned/src/gen/vstruct.rs b/crates/stackable-versioned/src/gen/vstruct.rs index f92587419..0486b556e 100644 --- a/crates/stackable-versioned/src/gen/vstruct.rs +++ b/crates/stackable-versioned/src/gen/vstruct.rs @@ -1,6 +1,6 @@ use darling::FromField; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote}; use syn::{DataStruct, Ident, Result}; use crate::{ @@ -26,41 +26,82 @@ pub(crate) struct VersionedStruct { pub(crate) fields: Vec, } -impl ToTokens for VersionedStruct { - fn to_tokens(&self, _tokens: &mut TokenStream) { - let mut versions = self.versions.iter().peekable(); +impl ToTokensExt for VersionedStruct { + fn to_tokens(&self, generate_modules: bool) -> Option { + // TODO (@Techassi): This unwrap should be fine, should we expect here? + let mut versions = self.versions.clone(); + versions.pop().unwrap(); + let mut versions = versions.iter().peekable(); + let mut tokens = TokenStream::new(); + + // TODO (@Techassi): Move this into own functions while let Some(version) = versions.next() { - let mut fields = TokenStream::new(); + let mut field_tokens = TokenStream::new(); for field in &self.fields { - fields.extend(field.to_tokens_for_version(version)); + field_tokens.extend(field.to_tokens(version)); } - // TODO (@Techassi): Make the generation of the module optional to - // enable the attribute macro to be applied to a module which - // generates versioned versions of all contained containers. - - let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); let module_name = format_ident!("{version}", version = version.inner.to_string()); + let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); let struct_name = &self.ident; - // Only generate a module when there is at least one more version. - // This skips generating a module for the latest version, because - // the base struct always represents the latest version. - if versions.peek().is_some() { - _tokens.extend(quote! { + let struct_tokens = quote! { + pub struct #struct_name { + #field_tokens + } + }; + + // Only generate modules when asked to do so by the caller. This + // enables us the support attribute macros to generate code for + // multiple versioned containers in a single file (no module name + // collition). + if generate_modules { + // Only generate a module when there is at least one more + // version. This skips generating a module for the latest + // version, because the base struct always represents the + // latest version. + tokens.extend(quote! { #[automatically_derived] #deprecated_attr pub mod #module_name { - - pub struct #struct_name { - #fields - } + #struct_tokens } }); + + if let Some(next) = versions.peek() { + // Generate From for NEXT impls + let next_module = format_ident!("{}", next.inner.to_string()); + + let from_impl_tokens = quote! { + #[automatically_derived] + impl From<#module_name::#struct_name> for #next_module::#struct_name { + fn from(from: #module_name::#struct_name) -> Self { + todo!(); + } + } + }; + + tokens.extend(from_impl_tokens); + } else { + let from_impl_tokens = quote! { + #[automatically_derived] + impl From<#module_name::#struct_name> for #struct_name { + fn from(from: #module_name::#struct_name) -> Self { + todo!(); + } + } + }; + + tokens.extend(from_impl_tokens); + } + } else { + tokens.extend(struct_tokens) } } + + Some(tokens) } } From 7e16ec4b4e4d7e5b77121da4dd88ba6285f26272 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 23 May 2024 16:45:21 +0200 Subject: [PATCH 02/15] Merge branch 'main' into feat/crd-versioning-from-impls --- Cargo.lock | 4 ++-- crates/stackable-versioned/CHANGELOG.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42e557581..757dd722d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2403,9 +2403,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.7" +version = "0.23.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b" +checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" dependencies = [ "aws-lc-rs", "log", diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index fa261cf80..1708315dd 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. [#784](ttps://github.com/stackabletech/operator-rs/pull/784) +- Improve action chain generation ([#784]). + +[#784](ttps://github.com/stackabletech/operator-rs/pull/784) + ## [0.1.0] - 2024-05-08 ### Changed From 33f966d0febb748159e5649fa77123677da803be Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 28 May 2024 14:26:49 +0200 Subject: [PATCH 03/15] Separate code, re-add basic From impl, separate generation code This commit separate the code into two crates: the core stackable-versioned crate, which provides a few utility traits and types and also re-exports the macros from the stackable-versioned-macros crate. This is because, crates (or libraries) marked as proc-macro libraries don't support export anything other than macro items. It also re-adds the basic From impl which was hard to keep across the previous merge conflict. This basic impl will currently panic, as the inner function is not yet implemented. This will follow. It additionally separates the generation code into multiple functions to more easily generate the different parts of code, like modules, struct fields and From impls. --- Cargo.lock | 7 + crates/stackable-versioned-macros/Cargo.toml | 21 +++ .../src/attrs/container.rs | 0 .../src/attrs/field.rs | 0 .../src/attrs/mod.rs | 0 .../src/consts.rs | 0 .../src/gen/field.rs | 0 .../src/gen/mod.rs | 8 +- .../src/gen/neighbors.rs | 0 .../src/gen/version.rs | 2 + .../src/gen/vstruct.rs | 151 ++++++++++++++++++ crates/stackable-versioned-macros/src/lib.rs | 32 ++++ .../tests/basic.rs | 4 +- crates/stackable-versioned/CHANGELOG.md | 10 +- crates/stackable-versioned/Cargo.toml | 13 +- crates/stackable-versioned/src/gen/vstruct.rs | 107 ------------- crates/stackable-versioned/src/lib.rs | 35 +--- 17 files changed, 231 insertions(+), 159 deletions(-) create mode 100644 crates/stackable-versioned-macros/Cargo.toml rename crates/{stackable-versioned => stackable-versioned-macros}/src/attrs/container.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/attrs/field.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/attrs/mod.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/consts.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/gen/field.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/gen/mod.rs (88%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/gen/neighbors.rs (100%) rename crates/{stackable-versioned => stackable-versioned-macros}/src/gen/version.rs (77%) create mode 100644 crates/stackable-versioned-macros/src/gen/vstruct.rs create mode 100644 crates/stackable-versioned-macros/src/lib.rs rename crates/{stackable-versioned => stackable-versioned-macros}/tests/basic.rs (93%) delete mode 100644 crates/stackable-versioned/src/gen/vstruct.rs diff --git a/Cargo.lock b/Cargo.lock index 757dd722d..f358875e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2913,6 +2913,13 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.1.0" +dependencies = [ + "stackable-versioned-macros", +] + +[[package]] +name = "stackable-versioned-macros" +version = "0.1.0" dependencies = [ "darling", "k8s-version", diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml new file mode 100644 index 000000000..2c8f98291 --- /dev/null +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "stackable-versioned-macros" +version = "0.1.0" +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +k8s-version = { path = "../k8s-version", features = ["darling"] } + +darling.workspace = true +proc-macro2.workspace = true +syn.workspace = true +quote.workspace = true + +[dev-dependencies] +rstest.workspace = true diff --git a/crates/stackable-versioned/src/attrs/container.rs b/crates/stackable-versioned-macros/src/attrs/container.rs similarity index 100% rename from crates/stackable-versioned/src/attrs/container.rs rename to crates/stackable-versioned-macros/src/attrs/container.rs diff --git a/crates/stackable-versioned/src/attrs/field.rs b/crates/stackable-versioned-macros/src/attrs/field.rs similarity index 100% rename from crates/stackable-versioned/src/attrs/field.rs rename to crates/stackable-versioned-macros/src/attrs/field.rs diff --git a/crates/stackable-versioned/src/attrs/mod.rs b/crates/stackable-versioned-macros/src/attrs/mod.rs similarity index 100% rename from crates/stackable-versioned/src/attrs/mod.rs rename to crates/stackable-versioned-macros/src/attrs/mod.rs diff --git a/crates/stackable-versioned/src/consts.rs b/crates/stackable-versioned-macros/src/consts.rs similarity index 100% rename from crates/stackable-versioned/src/consts.rs rename to crates/stackable-versioned-macros/src/consts.rs diff --git a/crates/stackable-versioned/src/gen/field.rs b/crates/stackable-versioned-macros/src/gen/field.rs similarity index 100% rename from crates/stackable-versioned/src/gen/field.rs rename to crates/stackable-versioned-macros/src/gen/field.rs diff --git a/crates/stackable-versioned/src/gen/mod.rs b/crates/stackable-versioned-macros/src/gen/mod.rs similarity index 88% rename from crates/stackable-versioned/src/gen/mod.rs rename to crates/stackable-versioned-macros/src/gen/mod.rs index 2dee123ec..c24704c10 100644 --- a/crates/stackable-versioned/src/gen/mod.rs +++ b/crates/stackable-versioned-macros/src/gen/mod.rs @@ -1,11 +1,7 @@ use proc_macro2::TokenStream; -use quote::ToTokens; use syn::{spanned::Spanned, Data, DeriveInput, Error, Result}; -use crate::{ - attrs::container::ContainerAttributes, - gen::{version::ContainerVersion, vstruct::VersionedStruct}, -}; +use crate::{attrs::container::ContainerAttributes, gen::vstruct::VersionedStruct}; pub(crate) mod field; pub(crate) mod neighbors; @@ -26,7 +22,7 @@ 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)?.to_token_stream(), + Data::Struct(data) => VersionedStruct::new(input.ident, data, attrs)?.generate_tokens(), _ => { return Err(Error::new( input.span(), diff --git a/crates/stackable-versioned/src/gen/neighbors.rs b/crates/stackable-versioned-macros/src/gen/neighbors.rs similarity index 100% rename from crates/stackable-versioned/src/gen/neighbors.rs rename to crates/stackable-versioned-macros/src/gen/neighbors.rs diff --git a/crates/stackable-versioned/src/gen/version.rs b/crates/stackable-versioned-macros/src/gen/version.rs similarity index 77% rename from crates/stackable-versioned/src/gen/version.rs rename to crates/stackable-versioned-macros/src/gen/version.rs index 141a49d50..159518890 100644 --- a/crates/stackable-versioned/src/gen/version.rs +++ b/crates/stackable-versioned-macros/src/gen/version.rs @@ -1,7 +1,9 @@ use k8s_version::Version; +use syn::Ident; #[derive(Debug, Clone)] pub(crate) struct ContainerVersion { pub(crate) deprecated: bool, pub(crate) inner: Version, + pub(crate) ident: Ident, } diff --git a/crates/stackable-versioned-macros/src/gen/vstruct.rs b/crates/stackable-versioned-macros/src/gen/vstruct.rs new file mode 100644 index 000000000..18b21428e --- /dev/null +++ b/crates/stackable-versioned-macros/src/gen/vstruct.rs @@ -0,0 +1,151 @@ +use darling::FromField; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{DataStruct, Ident, Result}; + +use crate::{ + attrs::{container::ContainerAttributes, field::FieldAttributes}, + gen::{field::VersionedField, version::ContainerVersion, ToTokensExt}, +}; + +/// Stores individual versions of a single struct. Each version tracks field +/// actions, which describe if the field was added, renamed or deprecated in +/// that version. Fields which are not versioned, are included in every +/// version of the struct. +#[derive(Debug)] +pub(crate) struct VersionedStruct { + /// The ident, or name, of the versioned struct. + pub(crate) ident: Ident, + + /// List of declared versions for this struct. Each version, except the + /// latest, generates a definition with appropriate fields. + pub(crate) versions: Vec, + + /// List of fields defined in the base struct. How, and if, a field should + /// generate code, is decided by the currently generated version. + pub(crate) fields: Vec, +} + +impl VersionedStruct { + pub(crate) fn new( + ident: Ident, + data: DataStruct, + attributes: ContainerAttributes, + ) -> Result { + // Convert the raw version attributes into a container version. + let versions = attributes + .versions + .iter() + .map(|v| ContainerVersion { + deprecated: v.deprecated.is_present(), + ident: format_ident!("{version}", version = v.name.to_string()), + inner: v.name, + }) + .collect(); + + // Extract the field attributes for every field from the raw token + // stream and also validate that each field action version uses a + // version declared by the container attribute. + let mut fields = Vec::new(); + + for field in data.fields { + let attrs = FieldAttributes::from_field(&field)?; + attrs.validate_versions(&attributes, &field)?; + + let mut versioned_field = VersionedField::new(field, attrs)?; + versioned_field.insert_container_versions(&versions); + fields.push(versioned_field); + } + + Ok(Self { + ident, + versions, + fields, + }) + } + + /// This generates the complete code for a single versioned struct. + /// + /// Internally, it will create a module for each declared version which + /// contains the struct with the appropriate fields. Additionally, it + /// generated `From` implementations, which enable conversion from an older + /// to a newer version. + pub(crate) fn generate_tokens(&self) -> TokenStream { + let mut token_stream = TokenStream::new(); + let mut versions = self.versions.iter().peekable(); + + while let Some(version) = versions.next() { + token_stream.extend(self.generate_version(version, versions.peek().copied())); + } + + token_stream + } + + pub(crate) fn generate_version( + &self, + version: &ContainerVersion, + next_version: Option<&ContainerVersion>, + ) -> TokenStream { + let mut token_stream = TokenStream::new(); + let struct_name = &self.ident; + + // Generate fields of the struct for `version`. + let fields = self.generate_struct_fields(version); + + // TODO (@Techassi): Make the generation of the module optional to + // enable the attribute macro to be applied to a module which + // generates versioned versions of all contained containers. + + let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); + let module_name = &version.ident; + + // Generate tokens for the module and the contained struct + token_stream.extend(quote! { + #[automatically_derived] + #deprecated_attr + pub mod #module_name { + pub struct #struct_name { + #fields + } + } + }); + + // Generate the From impl between this `version` and the next one. + token_stream.extend(self.generate_from_impl(version, next_version)); + + token_stream + } + + pub(crate) fn generate_struct_fields(&self, version: &ContainerVersion) -> TokenStream { + let mut token_stream = TokenStream::new(); + + for field in &self.fields { + token_stream.extend(field.to_tokens(version)); + } + + token_stream + } + + pub(crate) fn generate_from_impl( + &self, + version: &ContainerVersion, + next_version: Option<&ContainerVersion>, + ) -> TokenStream { + if let Some(next_version) = next_version { + let next_module_name = &next_version.ident; + let module_name = &version.ident; + let struct_name = &self.ident; + + return quote! { + #[automatically_derived] + impl From<#module_name::#struct_name> for #next_module_name::#struct_name { + fn from(from: #module_name::#struct_name) -> Self { + todo!(); + } + } + }; + } + + quote! {} + } +} diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs new file mode 100644 index 000000000..34d79cf2c --- /dev/null +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -0,0 +1,32 @@ +use darling::{ast::NestedMeta, FromMeta}; +use proc_macro::TokenStream; +use syn::{DeriveInput, Error}; + +use crate::attrs::container::ContainerAttributes; + +mod attrs; +mod consts; +mod gen; + +#[proc_macro_attribute] +pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { + let attrs = match NestedMeta::parse_meta_list(attrs.into()) { + Ok(attrs) => match ContainerAttributes::from_list(&attrs) { + Ok(attrs) => attrs, + Err(err) => return err.write_errors().into(), + }, + Err(err) => return darling::Error::from(err).write_errors().into(), + }; + + // NOTE (@Techassi): For now, we can just use the DeriveInput type here, + // because we only support structs (and eventually enums) to be versioned. + // In the future - if we decide to support modules - this requires + // adjustments to also support modules. One possible solution might be to + // use an enum with two variants: Container(DeriveInput) and + // Module(ItemMod). + let input = syn::parse_macro_input!(input as DeriveInput); + + gen::expand(attrs, input) + .unwrap_or_else(Error::into_compile_error) + .into() +} diff --git a/crates/stackable-versioned/tests/basic.rs b/crates/stackable-versioned-macros/tests/basic.rs similarity index 93% rename from crates/stackable-versioned/tests/basic.rs rename to crates/stackable-versioned-macros/tests/basic.rs index b3dd86db8..ef8a1c55b 100644 --- a/crates/stackable-versioned/tests/basic.rs +++ b/crates/stackable-versioned-macros/tests/basic.rs @@ -1,4 +1,4 @@ -use stackable_versioned::versioned; +use stackable_versioned_macros::versioned; // To expand the generated code (for debugging and testing), it is recommended // to first change directory via `cd crates/stackable-versioned` and to then @@ -36,7 +36,7 @@ fn basic() { // The latest version (v3) #[allow(deprecated)] - let _ = Foo { + let _ = v3::Foo { deprecated_bar: 0, baz: false, }; diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 6a4f69c3c..1981c04df 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -4,12 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- Add auto-generated `From for NEW` implementations ([#790]). - Change from derive macro to attribute macro to be able to generate code - _in place_ instead of _appending_ new code ([#CHANGEME]). + _in place_ instead of _appending_ new code ([#793]). - Improve action chain generation ([#784]). -[#784](https://github.com/stackabletech/operator-rs/pull/784) -[#CHANGEME](https://github.com/stackabletech/operator-rs/pull/CHANGEME) +[#784]: https://github.com/stackabletech/operator-rs/pull/784 +[#790]: https://github.com/stackabletech/operator-rs/pull/790 +[#793]: https://github.com/stackabletech/operator-rs/pull/793 - Improve action chain generation ([#784]). diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index d2ff81b73..b1dd38b5c 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -6,16 +6,5 @@ license.workspace = true edition.workspace = true repository.workspace = true -[lib] -proc-macro = true - [dependencies] -k8s-version = { path = "../k8s-version", features = ["darling"] } - -darling.workspace = true -proc-macro2.workspace = true -syn.workspace = true -quote.workspace = true - -[dev-dependencies] -rstest.workspace = true +stackable-versioned-macros = { path = "../stackable-versioned-macros" } diff --git a/crates/stackable-versioned/src/gen/vstruct.rs b/crates/stackable-versioned/src/gen/vstruct.rs deleted file mode 100644 index f115df115..000000000 --- a/crates/stackable-versioned/src/gen/vstruct.rs +++ /dev/null @@ -1,107 +0,0 @@ -use darling::FromField; -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::{DataStruct, Ident, Result}; - -use crate::{ - attrs::{container::ContainerAttributes, field::FieldAttributes}, - gen::{field::VersionedField, version::ContainerVersion, ToTokensExt}, -}; - -/// Stores individual versions of a single struct. Each version tracks field -/// actions, which describe if the field was added, renamed or deprecated in -/// that version. Fields which are not versioned, are included in every -/// version of the struct. -#[derive(Debug)] -pub(crate) struct VersionedStruct { - /// The ident, or name, of the versioned struct. - pub(crate) ident: Ident, - - /// List of declared versions for this struct. Each version, except the - /// latest, generates a definition with appropriate fields. - pub(crate) versions: Vec, - - /// List of fields defined in the base struct. How, and if, a field should - /// generate code, is decided by the currently generated version. - pub(crate) fields: Vec, -} - -impl ToTokens for VersionedStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let versions = self.versions.iter().peekable(); - let struct_name = &self.ident; - - for version in versions { - let mut fields = TokenStream::new(); - - for field in &self.fields { - fields.extend(field.to_tokens(version)); - } - - // TODO (@Techassi): Make the generation of the module optional to - // enable the attribute macro to be applied to a module which - // generates versioned versions of all contained containers. - - let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); - let module_name = format_ident!("{version}", version = version.inner.to_string()); - - tokens.extend(quote! { - #[automatically_derived] - #deprecated_attr - pub mod #module_name { - - pub struct #struct_name { - #fields - } - } - }); - } - - // Special handling for the last (and thus latest) version - let module_name = format_ident!( - "{version}", - version = self.versions.last().unwrap().inner.to_string() - ); - tokens.extend(quote! { - pub type #struct_name = #module_name::#struct_name; - }) - } -} - -impl VersionedStruct { - pub(crate) fn new( - ident: Ident, - data: DataStruct, - attributes: ContainerAttributes, - ) -> Result { - // Convert the raw version attributes into a container version. - let versions = attributes - .versions - .iter() - .map(|v| ContainerVersion { - deprecated: v.deprecated.is_present(), - inner: v.name, - }) - .collect(); - - // Extract the field attributes for every field from the raw token - // stream and also validate that each field action version uses a - // version declared by the container attribute. - let mut fields = Vec::new(); - - for field in data.fields { - let attrs = FieldAttributes::from_field(&field)?; - attrs.validate_versions(&attributes, &field)?; - - let mut versioned_field = VersionedField::new(field, attrs)?; - versioned_field.insert_container_versions(&versions); - fields.push(versioned_field); - } - - Ok(Self { - ident, - versions, - fields, - }) - } -} diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 34d79cf2c..dd7d29b9b 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -1,32 +1,9 @@ -use darling::{ast::NestedMeta, FromMeta}; -use proc_macro::TokenStream; -use syn::{DeriveInput, Error}; +pub use stackable_versioned_macros::*; -use crate::attrs::container::ContainerAttributes; +pub trait AsVersionStr { + const VERSION: &'static str; -mod attrs; -mod consts; -mod gen; - -#[proc_macro_attribute] -pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { - let attrs = match NestedMeta::parse_meta_list(attrs.into()) { - Ok(attrs) => match ContainerAttributes::from_list(&attrs) { - Ok(attrs) => attrs, - Err(err) => return err.write_errors().into(), - }, - Err(err) => return darling::Error::from(err).write_errors().into(), - }; - - // NOTE (@Techassi): For now, we can just use the DeriveInput type here, - // because we only support structs (and eventually enums) to be versioned. - // In the future - if we decide to support modules - this requires - // adjustments to also support modules. One possible solution might be to - // use an enum with two variants: Container(DeriveInput) and - // Module(ItemMod). - let input = syn::parse_macro_input!(input as DeriveInput); - - gen::expand(attrs, input) - .unwrap_or_else(Error::into_compile_error) - .into() + fn as_version_str(&self) -> &'static str { + Self::VERSION + } } From f113dfd3ed562a54d4af5609c722e818300478e2 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 28 May 2024 16:27:00 +0200 Subject: [PATCH 04/15] Generate working inner From trait function There is at least one known rough edge: The #[allow(deprecated)] attribute is always included in the generated output, regardless if the individual From implementation actually needs it. The attribute is only required when the next version of the struct includes a field which is marked as deprecated. --- .../src/gen/field.rs | 161 +++++++++++------- .../stackable-versioned-macros/src/gen/mod.rs | 7 - .../src/gen/vstruct.rs | 43 ++++- 3 files changed, 136 insertions(+), 75 deletions(-) diff --git a/crates/stackable-versioned-macros/src/gen/field.rs b/crates/stackable-versioned-macros/src/gen/field.rs index fbbefa450..5017b427f 100644 --- a/crates/stackable-versioned-macros/src/gen/field.rs +++ b/crates/stackable-versioned-macros/src/gen/field.rs @@ -9,7 +9,7 @@ use syn::{Field, Ident}; use crate::{ attrs::field::FieldAttributes, consts::DEPRECATED_PREFIX, - gen::{neighbors::Neighbors, version::ContainerVersion, ToTokensExt}, + gen::{neighbors::Neighbors, version::ContainerVersion}, }; /// A versioned field, which contains contains common [`Field`] data and a chain @@ -17,65 +17,13 @@ use crate::{ /// /// The chain of action maps versions to an action and the appropriate field /// name. Additionally, the [`Field`] data can be used to forward attributes, -/// generate documention, etc. +/// generate documentation, etc. #[derive(Debug)] pub(crate) struct VersionedField { chain: Option>, inner: Field, } -impl ToTokensExt<&ContainerVersion> for VersionedField { - fn to_tokens(&self, container_version: &ContainerVersion) -> Option { - match &self.chain { - Some(chain) => { - // Check if the provided container version is present in the map - // of actions. If it is, some action occured in exactly that - // version and thus code is generated for that field based on - // the type of action. - // If not, the provided version has no action attached to it. - // The code generation then depends on the relation to other - // versions (with actions). - - let field_type = &self.inner.ty; - - match chain - .get(&container_version.inner) - .expect("internal error: chain must contain container version") - { - FieldStatus::Added(field_ident) => Some(quote! { - pub #field_ident: #field_type, - }), - FieldStatus::Renamed { _from: _, to } => Some(quote! { - pub #to: #field_type, - }), - FieldStatus::Deprecated { - ident: field_ident, - note, - } => Some(quote! { - #[deprecated = #note] - pub #field_ident: #field_type, - }), - FieldStatus::NotPresent => None, - FieldStatus::NoChange(field_ident) => Some(quote! { - pub #field_ident: #field_type, - }), - } - } - None => { - // If there is no chain of field actions, the field is not - // versioned and code generation is straight forward. - // Unversioned fields are always included in versioned structs. - let field_ident = &self.inner.ident; - let field_type = &self.inner.ty; - - Some(quote! { - pub #field_ident: #field_type, - }) - } - } - } -} - impl VersionedField { /// Create a new versioned field by creating a status chain for each version /// defined in an action in the field attribute. @@ -95,9 +43,9 @@ impl VersionedField { // The ident of the deprecated field is guaranteed to include the // 'deprecated_' prefix. The ident can thus be used as is. if let Some(deprecated) = attrs.deprecated { + let ident = field.ident.as_ref().unwrap(); let mut actions = BTreeMap::new(); - let ident = field.ident.as_ref().unwrap(); actions.insert( *deprecated.since, FieldStatus::Deprecated { @@ -106,7 +54,7 @@ impl VersionedField { }, ); - // When the field is deprecated, any rename which occured beforehand + // When the field is deprecated, any rename which occurred beforehand // requires access to the field ident to infer the field ident for // the latest rename. let mut ident = format_ident!( @@ -158,8 +106,6 @@ impl VersionedField { actions.insert(*added.since, FieldStatus::Added(ident)); } - dbg!(&actions); - Ok(Self { chain: Some(actions), inner: field, @@ -188,12 +134,12 @@ impl VersionedField { /// Inserts container versions not yet present in the status chain. /// - /// When intially creating a new [`VersionedField`], the code doesn't have + /// When initially creating a new [`VersionedField`], the code doesn't have /// access to the versions defined on the container. This function inserts /// all non-present container versions and decides which status and ident /// is the right fit based on the status neighbors. /// - /// This continous chain ensures that when generating code (tokens), each + /// This continuous chain ensures that when generating code (tokens), each /// field can lookup the status for a requested version. pub(crate) fn insert_container_versions(&mut self, versions: &Vec) { if let Some(chain) = &mut self.chain { @@ -230,6 +176,89 @@ impl VersionedField { } } } + + pub(crate) fn generate_for_struct( + &self, + container_version: &ContainerVersion, + ) -> Option { + match &self.chain { + Some(chain) => { + // Check if the provided container version is present in the map + // of actions. If it is, some action occurred in exactly that + // version and thus code is generated for that field based on + // the type of action. + // If not, the provided version has no action attached to it. + // The code generation then depends on the relation to other + // versions (with actions). + + let field_type = &self.inner.ty; + + match chain + .get(&container_version.inner) + .expect("internal error: chain must contain container version") + { + FieldStatus::Added(field_ident) => Some(quote! { + pub #field_ident: #field_type, + }), + FieldStatus::Renamed { _from: _, to } => Some(quote! { + pub #to: #field_type, + }), + FieldStatus::Deprecated { + ident: field_ident, + note, + } => Some(quote! { + #[deprecated = #note] + pub #field_ident: #field_type, + }), + FieldStatus::NotPresent => None, + FieldStatus::NoChange(field_ident) => Some(quote! { + pub #field_ident: #field_type, + }), + } + } + None => { + // If there is no chain of field actions, the field is not + // versioned and code generation is straight forward. + // Unversioned fields are always included in versioned structs. + let field_ident = &self.inner.ident; + let field_type = &self.inner.ty; + + Some(quote! { + pub #field_ident: #field_type, + }) + } + } + } + + pub(crate) fn generate_for_from_impl( + &self, + version: &ContainerVersion, + next_version: &ContainerVersion, + from_ident: &Ident, + ) -> TokenStream { + match &self.chain { + Some(chain) => { + match ( + chain.get(&version.inner).expect("msg").get_ident(), + chain.get(&next_version.inner).expect("msg").get_ident(), + ) { + (None, Some(next_field_ident)) => quote! { + #next_field_ident: Default::default(), + }, + (Some(old_field_ident), Some(next_field_ident)) => quote! { + #next_field_ident: #from_ident.#old_field_ident, + }, + _ => unreachable!(), + } + } + None => { + let field_ident = &self.inner.ident; + quote! { + #field_ident: #from_ident.#field_ident, + } + } + } + } } #[derive(Debug)] @@ -240,3 +269,15 @@ pub(crate) enum FieldStatus { NoChange(Ident), NotPresent, } + +impl FieldStatus { + pub(crate) fn get_ident(&self) -> Option<&Ident> { + match &self { + FieldStatus::Added(ident) => Some(ident), + FieldStatus::Renamed { _from, to } => Some(to), + FieldStatus::Deprecated { ident, note: _ } => Some(ident), + FieldStatus::NoChange(ident) => Some(ident), + FieldStatus::NotPresent => None, + } + } +} diff --git a/crates/stackable-versioned-macros/src/gen/mod.rs b/crates/stackable-versioned-macros/src/gen/mod.rs index c24704c10..bf64fa495 100644 --- a/crates/stackable-versioned-macros/src/gen/mod.rs +++ b/crates/stackable-versioned-macros/src/gen/mod.rs @@ -33,10 +33,3 @@ pub(crate) fn expand(attrs: ContainerAttributes, input: DeriveInput) -> Result -where - T: Copy, -{ - fn to_tokens(&self, state: T) -> Option; -} diff --git a/crates/stackable-versioned-macros/src/gen/vstruct.rs b/crates/stackable-versioned-macros/src/gen/vstruct.rs index 18b21428e..41909049a 100644 --- a/crates/stackable-versioned-macros/src/gen/vstruct.rs +++ b/crates/stackable-versioned-macros/src/gen/vstruct.rs @@ -5,7 +5,7 @@ use syn::{DataStruct, Ident, Result}; use crate::{ attrs::{container::ContainerAttributes, field::FieldAttributes}, - gen::{field::VersionedField, version::ContainerVersion, ToTokensExt}, + gen::{field::VersionedField, version::ContainerVersion}, }; /// Stores individual versions of a single struct. Each version tracks field @@ -17,6 +17,9 @@ pub(crate) struct VersionedStruct { /// The ident, or name, of the versioned struct. pub(crate) ident: Ident, + /// The name of the struct used in `From` implementations. + pub(crate) from_ident: Ident, + /// List of declared versions for this struct. Each version, except the /// latest, generates a definition with appropriate fields. pub(crate) versions: Vec, @@ -57,10 +60,13 @@ impl VersionedStruct { fields.push(versioned_field); } + let from_ident = format_ident!("__sv_{ident}", ident = ident.to_string().to_lowercase()); + Ok(Self { - ident, + from_ident, versions, fields, + ident, }) } @@ -81,7 +87,7 @@ impl VersionedStruct { token_stream } - pub(crate) fn generate_version( + fn generate_version( &self, version: &ContainerVersion, next_version: Option<&ContainerVersion>, @@ -116,31 +122,37 @@ impl VersionedStruct { token_stream } - pub(crate) fn generate_struct_fields(&self, version: &ContainerVersion) -> TokenStream { + fn generate_struct_fields(&self, version: &ContainerVersion) -> TokenStream { let mut token_stream = TokenStream::new(); for field in &self.fields { - token_stream.extend(field.to_tokens(version)); + token_stream.extend(field.generate_for_struct(version)); } token_stream } - pub(crate) fn generate_from_impl( + fn generate_from_impl( &self, version: &ContainerVersion, next_version: Option<&ContainerVersion>, ) -> TokenStream { if let Some(next_version) = next_version { let next_module_name = &next_version.ident; + let from_ident = &self.from_ident; let module_name = &version.ident; let struct_name = &self.ident; + let fields = self.generate_from_fields(version, next_version, from_ident); + return quote! { #[automatically_derived] + #[allow(deprecated)] impl From<#module_name::#struct_name> for #next_module_name::#struct_name { - fn from(from: #module_name::#struct_name) -> Self { - todo!(); + fn from(#from_ident: #module_name::#struct_name) -> Self { + Self { + #fields + } } } }; @@ -148,4 +160,19 @@ impl VersionedStruct { quote! {} } + + fn generate_from_fields( + &self, + version: &ContainerVersion, + next_version: &ContainerVersion, + from_ident: &Ident, + ) -> TokenStream { + let mut token_stream = TokenStream::new(); + + for field in &self.fields { + token_stream.extend(field.generate_for_from_impl(version, next_version, from_ident)) + } + + token_stream + } } From d06f2e268b4328cbd2437ccdd56533d530a6a935 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 3 Jun 2024 13:07:19 +0200 Subject: [PATCH 05/15] Add option to skip From trait implementation generation Can be skipped both on the container level (no impls are generated) or at the version level (no impls are generated for that version). --- .../src/attrs/container.rs | 15 ++++- .../src/gen/version.rs | 1 + .../src/gen/vstruct.rs | 13 +++- .../stackable-versioned-macros/tests/from.rs | 61 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 crates/stackable-versioned-macros/tests/from.rs diff --git a/crates/stackable-versioned-macros/src/attrs/container.rs b/crates/stackable-versioned-macros/src/attrs/container.rs index b3f4a59e9..6c1b9fd63 100644 --- a/crates/stackable-versioned-macros/src/attrs/container.rs +++ b/crates/stackable-versioned-macros/src/attrs/container.rs @@ -83,10 +83,12 @@ impl ContainerAttributes { /// /// - `name` of the version, like `v1alpha1`. /// - `deprecated` flag to mark that version as deprecated. +/// - `skip` option to skip generating various pieces of code. #[derive(Clone, Debug, FromMeta)] pub(crate) struct VersionAttributes { pub(crate) deprecated: Flag, pub(crate) name: Version, + pub(crate) skip: Option, } /// This struct contains supported container options. @@ -95,8 +97,19 @@ pub(crate) struct VersionAttributes { /// /// - `allow_unsorted`, which allows declaring versions in unsorted order, /// instead of enforcing ascending order. +/// - `skip` option to skip generating various pieces of code. #[derive(Clone, Debug, Default, FromMeta)] pub(crate) struct ContainerOptions { pub(crate) allow_unsorted: Flag, - pub(crate) skip_from: Flag, + pub(crate) skip: Option, +} + +/// This struct contains supported skip options. +/// +/// Supported options are: +/// +/// - `from` flag, which skips generating [`From`] implementations when provided. +#[derive(Clone, Debug, Default, FromMeta)] +pub(crate) struct SkipOptions { + pub(crate) from: Flag, } diff --git a/crates/stackable-versioned-macros/src/gen/version.rs b/crates/stackable-versioned-macros/src/gen/version.rs index 159518890..e4375090e 100644 --- a/crates/stackable-versioned-macros/src/gen/version.rs +++ b/crates/stackable-versioned-macros/src/gen/version.rs @@ -4,6 +4,7 @@ use syn::Ident; #[derive(Debug, Clone)] pub(crate) struct ContainerVersion { pub(crate) deprecated: bool, + pub(crate) skip_from: bool, pub(crate) inner: Version, pub(crate) ident: Ident, } diff --git a/crates/stackable-versioned-macros/src/gen/vstruct.rs b/crates/stackable-versioned-macros/src/gen/vstruct.rs index 41909049a..023678d38 100644 --- a/crates/stackable-versioned-macros/src/gen/vstruct.rs +++ b/crates/stackable-versioned-macros/src/gen/vstruct.rs @@ -27,6 +27,8 @@ pub(crate) struct VersionedStruct { /// List of fields defined in the base struct. How, and if, a field should /// generate code, is decided by the currently generated version. pub(crate) fields: Vec, + + pub(crate) skip_from: bool, } impl VersionedStruct { @@ -40,8 +42,9 @@ impl VersionedStruct { .versions .iter() .map(|v| ContainerVersion { - deprecated: v.deprecated.is_present(), + 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(), inner: v.name, }) .collect(); @@ -63,6 +66,10 @@ impl VersionedStruct { let from_ident = format_ident!("__sv_{ident}", ident = ident.to_string().to_lowercase()); Ok(Self { + skip_from: attributes + .options + .skip + .map_or(false, |s| s.from.is_present()), from_ident, versions, fields, @@ -117,7 +124,9 @@ impl VersionedStruct { }); // Generate the From impl between this `version` and the next one. - token_stream.extend(self.generate_from_impl(version, next_version)); + if !self.skip_from && !version.skip_from { + token_stream.extend(self.generate_from_impl(version, next_version)); + } token_stream } diff --git a/crates/stackable-versioned-macros/tests/from.rs b/crates/stackable-versioned-macros/tests/from.rs new file mode 100644 index 000000000..b341c650c --- /dev/null +++ b/crates/stackable-versioned-macros/tests/from.rs @@ -0,0 +1,61 @@ +use stackable_versioned_macros::versioned; + +#[allow(deprecated)] +#[test] +fn from() { + #[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1") + )] + pub struct Foo { + #[versioned( + added(since = "v1beta1"), + deprecated(since = "v1", note = "not needed") + )] + deprecated_bar: usize, + baz: bool, + } + + let foo_v1alpha1 = v1alpha1::Foo { baz: true }; + let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1); + let foo_v1 = v1::Foo::from(foo_v1beta1); + + assert_eq!(foo_v1.deprecated_bar, 0); + assert!(foo_v1.baz); +} + +#[test] +fn skip_from_all() { + #[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + options(skip(from)) + )] + pub struct Foo { + #[versioned( + added(since = "v1beta1"), + deprecated(since = "v1", note = "not needed") + )] + deprecated_bar: usize, + baz: bool, + } +} + +#[test] +fn skip_from_version() { + #[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1", skip(from)), + version(name = "v1") + )] + pub struct Foo { + #[versioned( + added(since = "v1beta1"), + deprecated(since = "v1", note = "not needed") + )] + deprecated_bar: usize, + baz: bool, + } +} From 62f61fc478fc7352bf39fe4b1162131b919509b8 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 3 Jun 2024 15:12:22 +0200 Subject: [PATCH 06/15] Add README --- crates/stackable-versioned/README.md | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 crates/stackable-versioned/README.md diff --git a/crates/stackable-versioned/README.md b/crates/stackable-versioned/README.md new file mode 100644 index 000000000..f603d335a --- /dev/null +++ b/crates/stackable-versioned/README.md @@ -0,0 +1,39 @@ + + +

+ Stackable Logo +

+ +

stackable-versioned

+ +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://docs.stackable.tech/home/stable/contributor/index.html) +[![Apache License 2.0](https://img.shields.io/badge/license-Apache--2.0-green)](./LICENSE) + +[Stackable Data Platform](https://stackable.tech/) | [Platform Docs](https://docs.stackable.tech/) | [Discussions](https://github.com/orgs/stackabletech/discussions) | [Discord](https://discord.gg/7kZ3BNnCAF) + +This crate enables versioning of structs (and enums in the future). It currently +supports Kubernetes API versions while declaring versions on a data type. This +will be extended to support SemVer versions, as well as custom version formats +in the future. + +```rust +use stackable_versioned::versioned; + +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + version(name = "v2"), + version(name = "v3") +)] +struct Foo { + /// My docs + #[versioned( + added(since = "v1alpha1"), + renamed(since = "v1beta1", from = "gau"), + deprecated(since = "v2", note = "not required anymore") + )] + deprecated_bar: usize, + baz: bool, +} +``` From 7d082db6f823ece348a066489343243ca421ae79 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 3 Jun 2024 16:47:05 +0200 Subject: [PATCH 07/15] Add basic support for custom default function --- .../src/attrs/field.rs | 12 +++- .../src/gen/field.rs | 63 +++++++++++++------ .../stackable-versioned-macros/tests/from.rs | 27 ++++++++ crates/stackable-versioned/src/lib.rs | 27 ++++++++ 4 files changed, 108 insertions(+), 21 deletions(-) diff --git a/crates/stackable-versioned-macros/src/attrs/field.rs b/crates/stackable-versioned-macros/src/attrs/field.rs index 5690f533a..8c4d8ad86 100644 --- a/crates/stackable-versioned-macros/src/attrs/field.rs +++ b/crates/stackable-versioned-macros/src/attrs/field.rs @@ -1,5 +1,6 @@ use darling::{util::SpannedValue, Error, FromField, FromMeta}; use k8s_version::Version; +use proc_macro2::Span; use syn::{Field, Ident, Path}; use crate::{attrs::container::ContainerAttributes, consts::DEPRECATED_PREFIX}; @@ -40,7 +41,16 @@ pub(crate) struct FieldAttributes { #[derive(Clone, Debug, FromMeta)] pub(crate) struct AddedAttributes { pub(crate) since: SpannedValue, - pub(crate) default: Option>, + + #[darling(rename = "default", default = "default_default_fn")] + pub(crate) default_fn: SpannedValue, +} + +fn default_default_fn() -> SpannedValue { + SpannedValue::new( + syn::parse_str("std::default::Default::default").unwrap(), + Span::call_site(), + ) } #[derive(Clone, Debug, FromMeta)] diff --git a/crates/stackable-versioned-macros/src/gen/field.rs b/crates/stackable-versioned-macros/src/gen/field.rs index 5017b427f..d5748c8dc 100644 --- a/crates/stackable-versioned-macros/src/gen/field.rs +++ b/crates/stackable-versioned-macros/src/gen/field.rs @@ -1,10 +1,10 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, ops::Deref}; use darling::Error; use k8s_version::Version; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{Field, Ident}; +use syn::{Field, Ident, Path}; use crate::{ attrs::field::FieldAttributes, @@ -77,7 +77,13 @@ impl VersionedField { // After the last iteration above (if any) we use the ident for the // added action if there is any. if let Some(added) = attrs.added { - actions.insert(*added.since, FieldStatus::Added(ident)); + actions.insert( + *added.since, + FieldStatus::Added { + default_fn: added.default_fn.deref().clone(), + ident, + }, + ); } Ok(Self { @@ -103,7 +109,13 @@ impl VersionedField { // After the last iteration above (if any) we use the ident for the // added action if there is any. if let Some(added) = attrs.added { - actions.insert(*added.since, FieldStatus::Added(ident)); + actions.insert( + *added.since, + FieldStatus::Added { + default_fn: added.default_fn.deref().clone(), + ident, + }, + ); } Ok(Self { @@ -116,7 +128,10 @@ impl VersionedField { actions.insert( *added.since, - FieldStatus::Added(field.ident.clone().unwrap()), + FieldStatus::Added { + default_fn: added.default_fn.deref().clone(), + ident: field.ident.clone().unwrap(), + }, ); return Ok(Self { @@ -152,7 +167,7 @@ impl VersionedField { (None, Some(_)) => chain.insert(version.inner, FieldStatus::NotPresent), (Some(status), None) => { let ident = match status { - FieldStatus::Added(ident) => ident, + FieldStatus::Added { ident, .. } => ident, FieldStatus::Renamed { _from: _, to } => to, FieldStatus::Deprecated { ident, note: _ } => ident, FieldStatus::NoChange(ident) => ident, @@ -163,7 +178,7 @@ impl VersionedField { } (Some(status), Some(_)) => { let ident = match status { - FieldStatus::Added(ident) => ident, + FieldStatus::Added { ident, .. } => ident, FieldStatus::Renamed { _from: _, to } => to, FieldStatus::NoChange(ident) => ident, _ => unreachable!(), @@ -197,8 +212,8 @@ impl VersionedField { .get(&container_version.inner) .expect("internal error: chain must contain container version") { - FieldStatus::Added(field_ident) => Some(quote! { - pub #field_ident: #field_type, + FieldStatus::Added { ident, .. } => Some(quote! { + pub #ident: #field_type, }), FieldStatus::Renamed { _from: _, to } => Some(quote! { pub #to: #field_type, @@ -239,16 +254,24 @@ impl VersionedField { match &self.chain { Some(chain) => { match ( - chain.get(&version.inner).expect("msg").get_ident(), - chain.get(&next_version.inner).expect("msg").get_ident(), + chain + .get(&version.inner) + .expect("internal error: chain must contain container version"), + chain + .get(&next_version.inner) + .expect("internal error: chain must contain container version"), ) { - (None, Some(next_field_ident)) => quote! { - #next_field_ident: Default::default(), - }, - (Some(old_field_ident), Some(next_field_ident)) => quote! { - #next_field_ident: #from_ident.#old_field_ident, + (_, FieldStatus::Added { ident, default_fn }) => quote! { + #ident: #default_fn(), }, - _ => unreachable!(), + (old, next) => { + let old_field_ident = old.get_ident().unwrap(); + let next_field_ident = next.get_ident().unwrap(); + + quote! { + #next_field_ident: #from_ident.#old_field_ident, + } + } } } None => { @@ -263,7 +286,7 @@ impl VersionedField { #[derive(Debug)] pub(crate) enum FieldStatus { - Added(Ident), + Added { ident: Ident, default_fn: Path }, Renamed { _from: Ident, to: Ident }, Deprecated { ident: Ident, note: String }, NoChange(Ident), @@ -273,9 +296,9 @@ pub(crate) enum FieldStatus { impl FieldStatus { pub(crate) fn get_ident(&self) -> Option<&Ident> { match &self { - FieldStatus::Added(ident) => Some(ident), + FieldStatus::Added { ident, .. } => Some(ident), FieldStatus::Renamed { _from, to } => Some(to), - FieldStatus::Deprecated { ident, note: _ } => Some(ident), + FieldStatus::Deprecated { ident, .. } => Some(ident), FieldStatus::NoChange(ident) => Some(ident), FieldStatus::NotPresent => None, } diff --git a/crates/stackable-versioned-macros/tests/from.rs b/crates/stackable-versioned-macros/tests/from.rs index b341c650c..e644e00d6 100644 --- a/crates/stackable-versioned-macros/tests/from.rs +++ b/crates/stackable-versioned-macros/tests/from.rs @@ -25,6 +25,33 @@ fn from() { assert!(foo_v1.baz); } +#[test] +fn from_custom_default_fn() { + #[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1") + )] + pub struct Foo { + #[versioned( + added(since = "v1beta1", default = "default_bar"), + deprecated(since = "v1", note = "not needed") + )] + deprecated_bar: usize, + baz: bool, + } + + fn default_bar() -> usize { + 42 + } + + let foo_v1alpha1 = v1alpha1::Foo { baz: true }; + let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1); + + assert_eq!(foo_v1beta1.bar, 42); + assert!(foo_v1beta1.baz); +} + #[test] fn skip_from_all() { #[versioned( diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index dd7d29b9b..1d46847b2 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -1,3 +1,30 @@ +//! This crate enables versioning of structs (and enums in the future). It +//! currently supports Kubernetes API versions while declaring versions on a +//! data type. This will be extended to support SemVer versions, as well as +//! custom version formats in the future. +//! +//! ## Basic Usage +//! +//! ``` +//! #[versioned( +//! version(name = "v1alpha1"), +//! version(name = "v1beta1"), +//! version(name = "v1"), +//! version(name = "v2"), +//! version(name = "v3") +//! )] +//! struct Foo { +//! /// My docs +//! #[versioned( +//! added(since = "v1alpha1"), +//! renamed(since = "v1beta1", from = "gau"), +//! deprecated(since = "v2", note = "not empty") +//! )] +//! deprecated_bar: usize, +//! baz: bool, +//! } +//! ``` + pub use stackable_versioned_macros::*; pub trait AsVersionStr { From d9cbb5e48e1dfc7802338f8846dd15da4fb273b3 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 4 Jun 2024 11:36:16 +0200 Subject: [PATCH 08/15] Fix doc test --- crates/stackable-versioned/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 1d46847b2..c7b60e6c4 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -6,6 +6,8 @@ //! ## Basic Usage //! //! ``` +//! use stackable_versioned::versioned; +//! //! #[versioned( //! version(name = "v1alpha1"), //! version(name = "v1beta1"), From 56aae6308a86bd5be7d1ed3a26ba486e6236eb1c Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 4 Jun 2024 13:52:36 +0200 Subject: [PATCH 09/15] Add TODOs --- crates/stackable-versioned-macros/src/attrs/container.rs | 4 ++++ crates/stackable-versioned-macros/src/gen/vstruct.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/crates/stackable-versioned-macros/src/attrs/container.rs b/crates/stackable-versioned-macros/src/attrs/container.rs index 6c1b9fd63..fe7191e7c 100644 --- a/crates/stackable-versioned-macros/src/attrs/container.rs +++ b/crates/stackable-versioned-macros/src/attrs/container.rs @@ -59,6 +59,10 @@ impl ContainerAttributes { } } + // TODO (@Techassi): Add validation for skip(from) for last version, + // which will skip nothing, because nothing is generated in the first + // place. + // Ensure every version is unique and isn't declared multiple times. This // is inspired by the itertools all_unique function. let mut unique = HashSet::new(); diff --git a/crates/stackable-versioned-macros/src/gen/vstruct.rs b/crates/stackable-versioned-macros/src/gen/vstruct.rs index 023678d38..812a55b06 100644 --- a/crates/stackable-versioned-macros/src/gen/vstruct.rs +++ b/crates/stackable-versioned-macros/src/gen/vstruct.rs @@ -154,6 +154,8 @@ impl VersionedStruct { let fields = self.generate_from_fields(version, next_version, from_ident); + // TODO (@Techassi): Be a little bit more clever about when to include + // the #[allow(deprecated)] attribute. return quote! { #[automatically_derived] #[allow(deprecated)] From d3b5c28ab677ca9f9b15d5d8eee497f88e98bbba Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 4 Jun 2024 15:45:34 +0200 Subject: [PATCH 10/15] Start adding doc comments --- .../src/attrs/field.rs | 1 + crates/stackable-versioned-macros/src/lib.rs | 189 ++++++++++++++++++ crates/stackable-versioned/src/lib.rs | 9 +- 3 files changed, 196 insertions(+), 3 deletions(-) diff --git a/crates/stackable-versioned-macros/src/attrs/field.rs b/crates/stackable-versioned-macros/src/attrs/field.rs index 8c4d8ad86..03d0c6045 100644 --- a/crates/stackable-versioned-macros/src/attrs/field.rs +++ b/crates/stackable-versioned-macros/src/attrs/field.rs @@ -150,6 +150,7 @@ impl FieldAttributes { // First, validate that the added version is less than the deprecated // version. + // NOTE (@Techassi): Is this already covered by the code below? if let (Some(added_version), Some(deprecated_version)) = (added_version, deprecated_version) { if added_version >= deprecated_version { diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 34d79cf2c..8489c660d 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -8,6 +8,195 @@ mod attrs; mod consts; mod gen; +/// This macro enables generating versioned structs. +/// +/// ## Usage Guide +/// +/// ### Quickstart +/// +/// ``` +/// use stackable_versioned::versioned; +/// +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1"), +/// version(name = "v1"), +/// version(name = "v2"), +/// version(name = "v3") +/// )] +/// struct Foo { +/// /// My docs +/// #[versioned( +/// added(since = "v1beta1"), +/// renamed(since = "v1", from = "gau"), +/// deprecated(since = "v2", note = "not empty") +/// )] +/// deprecated_bar: usize, +/// baz: bool, +/// } +/// ``` +/// +/// ### Declaring Versions +/// +/// Before any of the fields can be versioned, versions need to be declared at +/// the container level. Each version currently supports two parameters: `name` +/// and the `deprecated` flag. The `name` must be a valid (and supported) +/// format. The macro checks each declared version and reports any error +/// encountered during parsing. +/// The `deprecated` flag marks the version as deprecated. This currently adds +/// the `#[deprecated]` attribute to the appropriate piece of code. +/// +/// ``` +/// # use stackable_versioned::versioned; +/// #[versioned( +/// version(name = "v1alpha1", deprecated) +/// )] +/// struct Foo {} +/// ``` +/// +/// Additionally, it is ensured that each version is unique. Declaring the same +/// version multiple times will result in an error. Furthermore, declaring the +/// versions out-of-order ist prohibited by default. It is possible to opt-out +/// of this check by setting `options(allow_unsorted)`: +/// +/// ``` +/// # use stackable_versioned::versioned; +/// #[versioned( +/// version(name = "v1beta1"), +/// version(name = "v1alpha1"), +/// options(allow_unsorted) +/// )] +/// struct Foo {} +/// ``` +/// +/// ### Field Actions +/// +/// This library currently supports three different field actions. Fields can +/// be added, renamed and deprecated. The macro ensures that these actions +/// adhere to the following set of rules: +/// +/// - Fields cannot be added and deprecated in the same version. +/// - Fields cannot be added and renamed in the same version. +/// - Fields cannot be renamed and deprecated in the same version. +/// - Fields added in version _a_, renamed _0...n_ times in versions +/// b1, b2, ..., bn and deprecated in +/// version _c_ must ensure _a < b1, b2, ..., +/// bn < c_. +/// - All field actions must use previously declared versions. Using versions +/// not present at the container level will result in an error. +/// +/// ``` +/// # use stackable_versioned::versioned; +/// +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1"), +/// version(name = "v1"), +/// version(name = "v2"), +/// )] +/// struct Foo { +/// #[versioned( +/// added(since = "v1beta1"), +/// renamed(since = "v1", from = "gau"), +/// deprecated(since = "v2", note = "not empty") +/// )] +/// deprecated_bar: usize, +/// baz: bool, +/// } +/// ``` +/// +/// For fields marked as deprecated, two additional rules apply: +/// +/// - Fields must start with the `deprecated_` prefix. +/// - The deprecation note cannot be empty. +/// +/// ### Auto-generated [`From`] Implementations +/// +/// To enable smooth conversions between different versions of the same struct, +/// the macro automatically generates [`From`] implementations. On a high level, +/// code generated for two versions _a_ and _b_, with _a < b_ looks like this: +/// `impl From for b`. +/// +/// ```ignore +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1"), +/// version(name = "v1") +/// )] +/// pub struct Foo { +/// #[versioned( +/// added(since = "v1beta1"), +/// deprecated(since = "v1", note = "not needed") +/// )] +/// deprecated_bar: usize, +/// baz: bool, +/// } +/// +/// // Produces ... +/// +/// #[automatically_derived] +/// pub mod v1alpha1 { +/// pub struct Foo { +/// pub baz: bool, +/// } +/// } +/// #[automatically_derived] +/// #[allow(deprecated)] +/// impl From for v1beta1::Foo { +/// fn from(__sv_foo: v1alpha1::Foo) -> Self { +/// Self { +/// bar: std::default::Default::default(), +/// baz: __sv_foo.baz, +/// } +/// } +/// } +/// #[automatically_derived] +/// pub mod v1beta1 { +/// pub struct Foo { +/// pub bar: usize, +/// pub baz: bool, +/// } +/// } +/// #[automatically_derived] +/// #[allow(deprecated)] +/// impl From for v1::Foo { +/// fn from(__sv_foo: v1beta1::Foo) -> Self { +/// Self { +/// deprecated_bar: __sv_foo.bar, +/// baz: __sv_foo.baz, +/// } +/// } +/// } +/// #[automatically_derived] +/// pub mod v1 { +/// pub struct Foo { +/// #[deprecated = "not needed"] +/// pub deprecated_bar: usize, +/// pub baz: bool, +/// } +/// } +/// ``` +/// +/// Generation of these [`From`] implementations can be skipped at the container +/// and version level. This enables customization of the implementations if the +/// default implementation is not sufficient. +/// +/// ``` +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1"), +/// version(name = "v1"), +/// options(skip(from)) +/// )] +/// pub struct Foo { +/// #[versioned( +/// added(since = "v1beta1"), +/// deprecated(since = "v1", note = "not needed") +/// )] +/// deprecated_bar: usize, +/// baz: bool, +/// } +/// ``` #[proc_macro_attribute] pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { let attrs = match NestedMeta::parse_meta_list(attrs.into()) { diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index c7b60e6c4..22925d547 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -3,7 +3,7 @@ //! data type. This will be extended to support SemVer versions, as well as //! custom version formats in the future. //! -//! ## Basic Usage +//! ## Usage Guide //! //! ``` //! use stackable_versioned::versioned; @@ -18,14 +18,17 @@ //! struct Foo { //! /// My docs //! #[versioned( -//! added(since = "v1alpha1"), -//! renamed(since = "v1beta1", from = "gau"), +//! added(since = "v1beta1"), +//! renamed(since = "v1", from = "gau"), //! deprecated(since = "v2", note = "not empty") //! )] //! deprecated_bar: usize, //! baz: bool, //! } //! ``` +//! +//! See [`versioned`] for an in-depth usage guide and a list of supported +//! parameters. pub use stackable_versioned_macros::*; From 26fdba568c759dd0222d190f551c0a3fd2d8eb2b Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 5 Jun 2024 13:01:43 +0200 Subject: [PATCH 11/15] Finish doc comment --- crates/stackable-versioned-macros/src/lib.rs | 38 +++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 8489c660d..2a8092588 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -87,7 +87,6 @@ mod gen; /// /// ``` /// # use stackable_versioned::versioned; -/// /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), @@ -112,10 +111,9 @@ mod gen; /// /// ### Auto-generated [`From`] Implementations /// -/// To enable smooth conversions between different versions of the same struct, -/// the macro automatically generates [`From`] implementations. On a high level, -/// code generated for two versions _a_ and _b_, with _a < b_ looks like this: -/// `impl From for b`. +/// To enable smooth version upgrades of the same struct, the macro automatically +/// generates [`From`] implementations. On a high level, code generated for two +/// versions _a_ and _b_, with _a < b_ looks like this: `impl From for b`. /// /// ```ignore /// #[versioned( @@ -177,11 +175,14 @@ mod gen; /// } /// ``` /// +/// #### Skip [`From`] generation +/// /// Generation of these [`From`] implementations can be skipped at the container /// and version level. This enables customization of the implementations if the /// default implementation is not sufficient. /// /// ``` +/// # use stackable_versioned::versioned; /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), @@ -197,6 +198,33 @@ mod gen; /// baz: bool, /// } /// ``` +/// +/// #### Customize Default Function for Added Fields +/// +/// It is possible to customize the default function used in the generated +/// [`From`] implementation for populating added fields. By default, +/// [`Default::default()`] is used. +/// +/// ``` +/// # use stackable_versioned::versioned; +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1"), +/// version(name = "v1") +/// )] +/// pub struct Foo { +/// #[versioned( +/// added(since = "v1beta1", default = "default_bar"), +/// deprecated(since = "v1", note = "not needed") +/// )] +/// deprecated_bar: usize, +/// baz: bool, +/// } +/// +/// fn default_bar() -> usize { +/// 42 +/// } +/// ``` #[proc_macro_attribute] pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { let attrs = match NestedMeta::parse_meta_list(attrs.into()) { From e8cc4d1c4707c1fa9db0e829ea38306915a8c43d Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 5 Jun 2024 13:02:51 +0200 Subject: [PATCH 12/15] Fix changelog --- crates/stackable-versioned/CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 1981c04df..869ae6ece 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -15,10 +15,6 @@ All notable changes to this project will be documented in this file. [#790]: https://github.com/stackabletech/operator-rs/pull/790 [#793]: https://github.com/stackabletech/operator-rs/pull/793 -- Improve action chain generation ([#784]). - -[#784](ttps://github.com/stackabletech/operator-rs/pull/784) - ## [0.1.0] - 2024-05-08 ### Changed From e0db6e5e74908cc278d559966dc3aa938ad70ff9 Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 5 Jun 2024 13:11:51 +0200 Subject: [PATCH 13/15] Fix doc tests --- crates/stackable-versioned-macros/src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 2a8092588..ff5173f0c 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -15,8 +15,7 @@ mod gen; /// ### Quickstart /// /// ``` -/// use stackable_versioned::versioned; -/// +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), @@ -47,7 +46,7 @@ mod gen; /// the `#[deprecated]` attribute to the appropriate piece of code. /// /// ``` -/// # use stackable_versioned::versioned; +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1alpha1", deprecated) /// )] @@ -60,7 +59,7 @@ mod gen; /// of this check by setting `options(allow_unsorted)`: /// /// ``` -/// # use stackable_versioned::versioned; +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1beta1"), /// version(name = "v1alpha1"), @@ -86,7 +85,7 @@ mod gen; /// not present at the container level will result in an error. /// /// ``` -/// # use stackable_versioned::versioned; +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), @@ -182,7 +181,7 @@ mod gen; /// default implementation is not sufficient. /// /// ``` -/// # use stackable_versioned::versioned; +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), @@ -206,7 +205,7 @@ mod gen; /// [`Default::default()`] is used. /// /// ``` -/// # use stackable_versioned::versioned; +/// # use stackable_versioned_macros::versioned; /// #[versioned( /// version(name = "v1alpha1"), /// version(name = "v1beta1"), From 19caa73b06d5d3e6f38b313b04dad4418fb32faa Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 5 Jun 2024 13:58:25 +0200 Subject: [PATCH 14/15] Update crates/stackable-versioned-macros/src/lib.rs Co-authored-by: Nick --- crates/stackable-versioned-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index ff5173f0c..29d451467 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -55,7 +55,7 @@ mod gen; /// /// Additionally, it is ensured that each version is unique. Declaring the same /// version multiple times will result in an error. Furthermore, declaring the -/// versions out-of-order ist prohibited by default. It is possible to opt-out +/// versions out-of-order is prohibited by default. It is possible to opt-out /// of this check by setting `options(allow_unsorted)`: /// /// ``` From 3be5393ad00a699c9747f966a5da165f03cd8bd1 Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 5 Jun 2024 14:00:52 +0200 Subject: [PATCH 15/15] Remove redundant code block in doc comment --- crates/stackable-versioned-macros/src/lib.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 29d451467..9c8ce122d 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -84,25 +84,6 @@ mod gen; /// - All field actions must use previously declared versions. Using versions /// not present at the container level will result in an error. /// -/// ``` -/// # use stackable_versioned_macros::versioned; -/// #[versioned( -/// version(name = "v1alpha1"), -/// version(name = "v1beta1"), -/// version(name = "v1"), -/// version(name = "v2"), -/// )] -/// struct Foo { -/// #[versioned( -/// added(since = "v1beta1"), -/// renamed(since = "v1", from = "gau"), -/// deprecated(since = "v2", note = "not empty") -/// )] -/// deprecated_bar: usize, -/// baz: bool, -/// } -/// ``` -/// /// For fields marked as deprecated, two additional rules apply: /// /// - Fields must start with the `deprecated_` prefix.