diff --git a/Cargo.lock b/Cargo.lock index 7d4052e05..5e209f4d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3043,6 +3043,11 @@ dependencies = [ name = "stackable-versioned" version = "0.7.1" dependencies = [ + "k8s-version", + "schemars", + "serde", + "serde_json", + "serde_yaml", "stackable-versioned-macros", ] diff --git a/crates/k8s-version/src/api_version/serde.rs b/crates/k8s-version/src/api_version/serde.rs index f9754c842..2ef14822b 100644 --- a/crates/k8s-version/src/api_version/serde.rs +++ b/crates/k8s-version/src/api_version/serde.rs @@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for ApiVersion { { struct ApiVersionVisitor; - impl<'de> Visitor<'de> for ApiVersionVisitor { + impl Visitor<'_> for ApiVersionVisitor { type Value = ApiVersion; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/crates/k8s-version/src/level/serde.rs b/crates/k8s-version/src/level/serde.rs index 59bda5702..4d1fd8075 100644 --- a/crates/k8s-version/src/level/serde.rs +++ b/crates/k8s-version/src/level/serde.rs @@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for Level { { struct LevelVisitor; - impl<'de> Visitor<'de> for LevelVisitor { + impl Visitor<'_> for LevelVisitor { type Value = Level; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/crates/k8s-version/src/version/serde.rs b/crates/k8s-version/src/version/serde.rs index 88c7d98c3..c0f3b3c6c 100644 --- a/crates/k8s-version/src/version/serde.rs +++ b/crates/k8s-version/src/version/serde.rs @@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for Version { { struct VersionVisitor; - impl<'de> Visitor<'de> for VersionVisitor { + impl Visitor<'_> for VersionVisitor { type Value = Version; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index 123679118..9739c4427 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -43,7 +43,6 @@ quote.workspace = true [dev-dependencies] # Only needed for doc tests / examples stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] } -k8s-openapi.workspace = true insta.workspace = true prettyplease.workspace = true diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index 34dbb3986..fff4396fb 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -238,15 +238,26 @@ impl CommonItemAttributes { } } - // The convert_with argument only makes sense to use when the - // type changed - if let Some(convert_func) = change.convert_with.as_ref() { - if change.from_type.is_none() { + if change.from_type.is_none() { + // The upgrade_with argument only makes sense to use when the + // type changed + if let Some(upgrade_func) = change.upgrade_with.as_ref() { errors.push( Error::custom( - "the `convert_with` argument must be used in combination with `from_type`", + "the `upgrade_with` argument must be used in combination with `from_type`", ) - .with_span(&convert_func.span()), + .with_span(&upgrade_func.span()), + ); + } + + // The downgrade_with argument only makes sense to use when the + // type changed + if let Some(downgrade_func) = change.downgrade_with.as_ref() { + errors.push( + Error::custom( + "the `downgrade_with` argument must be used in combination with `from_type`", + ) + .with_span(&downgrade_func.span()), ); } } @@ -315,7 +326,8 @@ impl CommonItemAttributes { .unwrap_or(ty.clone()); actions.insert(*change.since, ItemStatus::Change { - convert_with: change.convert_with.as_deref().cloned(), + downgrade_with: change.downgrade_with.as_deref().cloned(), + upgrade_with: change.upgrade_with.as_deref().cloned(), from_ident: from_ident.clone(), from_type: from_ty.clone(), to_ident: ident, @@ -359,7 +371,8 @@ impl CommonItemAttributes { .unwrap_or(ty.clone()); actions.insert(*change.since, ItemStatus::Change { - convert_with: change.convert_with.as_deref().cloned(), + downgrade_with: change.downgrade_with.as_deref().cloned(), + upgrade_with: change.upgrade_with.as_deref().cloned(), from_ident: from_ident.clone(), from_type: from_ty.clone(), to_ident: ident, @@ -429,13 +442,15 @@ fn default_default_fn() -> SpannedValue { /// Example usage: /// - `changed(since = "...", from_name = "...")` /// - `changed(since = "...", from_name = "...", from_type="...")` -/// - `changed(since = "...", from_name = "...", from_type="...", convert_with = "...")` +/// - `changed(since = "...", from_name = "...", from_type="...", upgrade_with = "...")` +/// - `changed(since = "...", from_name = "...", from_type="...", downgrade_with = "...")` #[derive(Clone, Debug, FromMeta)] pub struct ChangedAttributes { pub since: SpannedValue, pub from_name: Option>, pub from_type: Option>, - pub convert_with: Option>, + pub upgrade_with: Option>, + pub downgrade_with: Option>, } /// For the deprecated() action diff --git a/crates/stackable-versioned-macros/src/attrs/k8s.rs b/crates/stackable-versioned-macros/src/attrs/k8s.rs index 60fc4521c..06fcbbcb3 100644 --- a/crates/stackable-versioned-macros/src/attrs/k8s.rs +++ b/crates/stackable-versioned-macros/src/attrs/k8s.rs @@ -22,27 +22,30 @@ use syn::Path; /// times. /// - `skip`: Controls skipping parts of the generation. #[derive(Clone, Debug, FromMeta)] -pub(crate) struct KubernetesArguments { - pub(crate) group: String, - pub(crate) kind: Option, - pub(crate) singular: Option, - pub(crate) plural: Option, - pub(crate) namespaced: Flag, +pub struct KubernetesArguments { + pub group: String, + pub kind: Option, + pub singular: Option, + pub plural: Option, + pub namespaced: Flag, // root - pub(crate) crates: Option, - pub(crate) status: Option, + pub crates: Option, + pub status: Option, // derive // schema // scale // printcolumn #[darling(multiple, rename = "shortname")] - pub(crate) shortnames: Vec, + pub shortnames: Vec, // category // selectable // doc // annotation // label - pub(crate) skip: Option, + pub skip: Option, + + #[darling(default)] + pub options: RawKubernetesOptions, } /// This struct contains supported kubernetes skip arguments. @@ -52,19 +55,25 @@ pub(crate) struct KubernetesArguments { /// - `merged_crd` flag, which skips generating the `crd()` and `merged_crd()` functions are /// generated. #[derive(Clone, Debug, FromMeta)] -pub(crate) struct KubernetesSkipArguments { +pub struct KubernetesSkipArguments { /// Whether the `crd()` and `merged_crd()` generation should be skipped for /// this container. - pub(crate) merged_crd: Flag, + pub merged_crd: Flag, } /// This struct contains crate overrides to be passed to `#[kube]`. #[derive(Clone, Debug, FromMeta)] -pub(crate) struct KubernetesCrateArguments { - pub(crate) kube_core: Option, - pub(crate) kube_client: Option, - pub(crate) k8s_openapi: Option, - pub(crate) schemars: Option, - pub(crate) serde: Option, - pub(crate) serde_json: Option, +pub struct KubernetesCrateArguments { + pub kube_core: Option, + pub kube_client: Option, + pub k8s_openapi: Option, + pub schemars: Option, + pub serde: Option, + pub serde_json: Option, + pub versioned: Option, +} + +#[derive(Clone, Default, Debug, FromMeta)] +pub struct RawKubernetesOptions { + pub experimental_conversion_tracking: Flag, } diff --git a/crates/stackable-versioned-macros/src/codegen/container/enum.rs b/crates/stackable-versioned-macros/src/codegen/container/enum.rs index da28cc785..d8ee3d668 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/enum.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/enum.rs @@ -1,6 +1,6 @@ use std::ops::Not; -use darling::{FromAttributes, Result, util::IdentString}; +use darling::{FromAttributes, Result}; use proc_macro2::TokenStream; use quote::quote; use syn::{Generics, ItemEnum}; @@ -126,11 +126,11 @@ impl Enum { } /// Generates code for the `From for NextVersion` implementation. - pub(crate) fn generate_from_impl( + pub fn generate_upgrade_from_impl( &self, version: &VersionDefinition, next_version: Option<&VersionDefinition>, - is_nested: bool, + add_attributes: bool, ) -> Option { if version.skip_from || self.common.options.skip_from { return None; @@ -145,12 +145,18 @@ impl Enum { // later versions. let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); let enum_ident = &self.common.idents.original; - let from_ident = &self.common.idents.from; + let from_enum_ident = &self.common.idents.from; - let next_version_ident = &next_version.ident; - let version_ident = &version.ident; + let for_module_ident = &next_version.ident; + let from_module_ident = &version.ident; - let variants = self.generate_from_variants(version, next_version, enum_ident); + let variants: TokenStream = self + .variants + .iter() + .filter_map(|v| { + v.generate_for_upgrade_from_impl(version, next_version, enum_ident) + }) + .collect(); // Include allow(deprecated) only when this or the next version is // deprecated. Also include it, when a variant in this or the next @@ -163,17 +169,18 @@ impl Enum { // Only add the #[automatically_derived] attribute only if this impl is used // outside of a module (in standalone mode). - let automatically_derived = - is_nested.not().then(|| quote! {#[automatically_derived]}); + let automatically_derived = add_attributes + .not() + .then(|| quote! {#[automatically_derived]}); Some(quote! { #automatically_derived #allow_attribute - impl #impl_generics ::std::convert::From<#version_ident::#enum_ident #type_generics> for #next_version_ident::#enum_ident #type_generics + impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics #where_clause { - fn from(#from_ident: #version_ident::#enum_ident #type_generics) -> Self { - match #from_ident { + fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self { + match #from_enum_ident { #variants } } @@ -184,20 +191,64 @@ impl Enum { } } - /// Generates code for enum variants used in `From` implementations. - fn generate_from_variants( + pub fn generate_downgrade_from_impl( &self, version: &VersionDefinition, - next_version: &VersionDefinition, - enum_ident: &IdentString, - ) -> TokenStream { - let mut tokens = TokenStream::new(); - - for variant in &self.variants { - tokens.extend(variant.generate_for_from_impl(version, next_version, enum_ident)); + next_version: Option<&VersionDefinition>, + add_attributes: bool, + ) -> Option { + if version.skip_from || self.common.options.skip_from { + return None; } - tokens + match next_version { + Some(next_version) => { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let enum_ident = &self.common.idents.original; + let from_enum_ident = &self.common.idents.from; + + let for_module_ident = &version.ident; + let from_module_ident = &next_version.ident; + + let variants: TokenStream = self + .variants + .iter() + .filter_map(|v| { + v.generate_for_downgrade_from_impl(version, next_version, enum_ident) + }) + .collect(); + + // Include allow(deprecated) only when this or the next version is + // deprecated. Also include it, when a variant in this or the next + // version is deprecated. + let allow_attribute = (version.deprecated.is_some() + || next_version.deprecated.is_some() + || self.is_any_variant_deprecated(version) + || self.is_any_variant_deprecated(next_version)) + .then_some(quote! { #[allow(deprecated)] }); + + // Only add the #[automatically_derived] attribute only if this impl is used + // outside of a module (in standalone mode). + let automatically_derived = add_attributes + .not() + .then(|| quote! {#[automatically_derived]}); + + Some(quote! { + #automatically_derived + #allow_attribute + impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics + #where_clause + { + fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self { + match #from_enum_ident { + #variants + } + } + } + }) + } + None => None, + } } /// Returns whether any variant is deprecated in the provided `version`. diff --git a/crates/stackable-versioned-macros/src/codegen/container/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/mod.rs index df147c88a..7ac654ade 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/mod.rs @@ -8,7 +8,7 @@ use syn::{Attribute, Ident, ItemEnum, ItemStruct, Path, Visibility, parse_quote} use crate::{ attrs::{ container::StandaloneContainerAttributes, - k8s::{KubernetesArguments, KubernetesCrateArguments}, + k8s::{KubernetesArguments, KubernetesCrateArguments, RawKubernetesOptions}, }, codegen::{ VersionDefinition, @@ -21,7 +21,7 @@ mod r#enum; mod r#struct; /// Contains common container data shared between structs and enums. -pub(crate) struct CommonContainerData { +pub struct CommonContainerData { /// Original attributes placed on the container, like `#[derive()]` or `#[cfg()]`. pub(crate) original_attributes: Vec, @@ -37,7 +37,7 @@ pub(crate) struct CommonContainerData { /// Abstracting away with kind of container is generated makes it possible to create a list of /// containers when the macro is used on modules. This enum provides functions to generate code /// which then internally call the appropriate function based on the variant. -pub(crate) enum Container { +pub enum Container { Struct(Struct), Enum(Enum), } @@ -51,16 +51,37 @@ impl Container { } } - /// Generates the container `From for NextVersion` implementation. - pub(crate) fn generate_from_impl( + /// Generates the `From for NextVersion` implementation for the container. + pub fn generate_upgrade_from_impl( &self, version: &VersionDefinition, next_version: Option<&VersionDefinition>, add_attributes: bool, ) -> Option { match self { - Container::Struct(s) => s.generate_from_impl(version, next_version, add_attributes), - Container::Enum(e) => e.generate_from_impl(version, next_version, add_attributes), + Container::Struct(s) => { + s.generate_upgrade_from_impl(version, next_version, add_attributes) + } + Container::Enum(e) => { + e.generate_upgrade_from_impl(version, next_version, add_attributes) + } + } + } + + /// Generates the `From for Version` implementation for the container. + pub fn generate_downgrade_from_impl( + &self, + version: &VersionDefinition, + next_version: Option<&VersionDefinition>, + add_attributes: bool, + ) -> Option { + match self { + Container::Struct(s) => { + s.generate_downgrade_from_impl(version, next_version, add_attributes) + } + Container::Enum(e) => { + e.generate_downgrade_from_impl(version, next_version, add_attributes) + } } } @@ -74,7 +95,7 @@ impl Container { /// /// This function only returns `Some` if it is a struct. Enums cannot be used to define /// Kubernetes custom resources. - pub(crate) fn generate_kubernetes_item( + pub fn generate_kubernetes_item( &self, version: &VersionDefinition, ) -> Option<(IdentString, String, TokenStream)> { @@ -88,7 +109,7 @@ impl Container { /// /// This function only returns `Some` if it is a struct. Enums cannot be used to define /// Kubernetes custom resources. - pub(crate) fn generate_kubernetes_merge_crds( + pub fn generate_kubernetes_merge_crds( &self, enum_variant_idents: &[IdentString], enum_variant_strings: &[String], @@ -108,7 +129,14 @@ impl Container { } } - pub(crate) fn get_original_ident(&self) -> &Ident { + pub fn generate_kubernetes_status_struct(&self) -> Option { + match self { + Container::Struct(s) => s.generate_kubernetes_status_struct(), + Container::Enum(_) => None, + } + } + + pub fn get_original_ident(&self) -> &Ident { match &self { Container::Struct(s) => s.common.idents.original.as_ident(), Container::Enum(e) => e.common.idents.original.as_ident(), @@ -180,9 +208,17 @@ impl StandaloneContainer { // NOTE (@Techassi): Using '.copied()' here does not copy or clone the data, but instead // removes one level of indirection of the double reference '&&'. - let from_impl = + let next_version = versions.peek().copied(); + + // Generate the From impl for upgrading the CRD. + let upgrade_from_impl = + self.container + .generate_upgrade_from_impl(version, next_version, false); + + // Generate the From impl for downgrading the CRD. + let downgrade_from_impl = self.container - .generate_from_impl(version, versions.peek().copied(), false); + .generate_downgrade_from_impl(version, next_version, false); // Add the #[deprecated] attribute when the version is marked as deprecated. let deprecated_attribute = version @@ -210,7 +246,8 @@ impl StandaloneContainer { #container_definition } - #from_impl + #upgrade_from_impl + #downgrade_from_impl }); } @@ -222,6 +259,8 @@ impl StandaloneContainer { false, )); + tokens.extend(self.container.generate_kubernetes_status_struct()); + tokens } } @@ -231,13 +270,13 @@ impl StandaloneContainer { pub(crate) struct ContainerIdents { /// The ident used in the context of Kubernetes specific code. This ident /// removes the 'Spec' suffix present in the definition container. - pub(crate) kubernetes: IdentString, + pub kubernetes: IdentString, /// The original ident, or name, of the versioned container. - pub(crate) original: IdentString, + pub original: IdentString, /// The ident used in the [`From`] impl. - pub(crate) from: IdentString, + pub from: IdentString, } impl ContainerIdents { @@ -261,32 +300,35 @@ impl ContainerIdents { } #[derive(Debug)] -pub(crate) struct ContainerOptions { - pub(crate) kubernetes_options: Option, - pub(crate) skip_from: bool, +pub struct ContainerOptions { + pub kubernetes_options: Option, + pub skip_from: bool, } +// TODO (@Techassi): Get rid of this whole mess. There should be an elegant way of using the +// attributes directly (with all defaults set and validation done). #[derive(Debug)] -pub(crate) struct KubernetesOptions { - pub(crate) group: String, - pub(crate) kind: Option, - pub(crate) singular: Option, - pub(crate) plural: Option, - pub(crate) namespaced: bool, +pub struct KubernetesOptions { + pub group: String, + pub kind: Option, + pub singular: Option, + pub plural: Option, + pub namespaced: bool, // root - pub(crate) crates: KubernetesCrateOptions, - pub(crate) status: Option, + pub crates: KubernetesCrateOptions, + pub status: Option, // derive // schema // scale // printcolumn - pub(crate) shortnames: Vec, + pub shortnames: Vec, // category // selectable // doc // annotation // label - pub(crate) skip_merged_crd: bool, + pub skip_merged_crd: bool, + pub config_options: KubernetesConfigOptions, } impl From for KubernetesOptions { @@ -303,23 +345,26 @@ impl From for KubernetesOptions { status: args.status, shortnames: args.shortnames, skip_merged_crd: args.skip.is_some_and(|s| s.merged_crd.is_present()), + config_options: args.options.into(), } } } #[derive(Debug)] -pub(crate) struct KubernetesCrateOptions { - pub(crate) kube_client: Override, - pub(crate) kube_core: Override, - pub(crate) k8s_openapi: Override, - pub(crate) schemars: Override, - pub(crate) serde: Override, - pub(crate) serde_json: Override, +pub struct KubernetesCrateOptions { + pub kube_client: Override, + pub kube_core: Override, + pub k8s_openapi: Override, + pub schemars: Override, + pub serde: Override, + pub serde_json: Override, + pub versioned: Override, } impl Default for KubernetesCrateOptions { fn default() -> Self { Self { + versioned: Override::Default(parse_quote! { ::stackable_versioned }), kube_client: Override::Default(parse_quote! { ::kube::client }), k8s_openapi: Override::Default(parse_quote! { ::k8s_openapi }), serde_json: Override::Default(parse_quote! { ::serde_json }), @@ -358,6 +403,10 @@ impl From for KubernetesCrateOptions { crate_options.serde = Override::Overridden(serde); } + if let Some(versioned) = args.versioned { + crate_options.versioned = Override::Overridden(versioned); + } + crate_options } } @@ -373,6 +422,7 @@ impl ToTokens for KubernetesCrateOptions { kube_core, schemars, serde, + .. } = self; if let Override::Overridden(k8s_openapi) = k8s_openapi { @@ -403,7 +453,7 @@ impl ToTokens for KubernetesCrateOptions { /// Wraps a value to indicate whether it is original or has been overridden. #[derive(Debug)] -pub(crate) enum Override { +pub enum Override { Default(T), Overridden(T), } @@ -418,3 +468,16 @@ impl Deref for Override { } } } + +#[derive(Debug)] +pub struct KubernetesConfigOptions { + experimental_conversion_tracking: bool, +} + +impl From for KubernetesConfigOptions { + fn from(options: RawKubernetesOptions) -> Self { + Self { + experimental_conversion_tracking: options.experimental_conversion_tracking.is_present(), + } + } +} diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct.rs b/crates/stackable-versioned-macros/src/codegen/container/struct.rs index 584a293b1..e96f99125 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct.rs @@ -2,7 +2,7 @@ use std::ops::Not; use darling::{Error, FromAttributes, Result, util::IdentString}; use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; +use quote::{ToTokens, format_ident, quote}; use syn::{Generics, ItemStruct, Path, Visibility, parse_quote}; use crate::{ @@ -119,7 +119,7 @@ impl Container { } /// A versioned struct. -pub(crate) struct Struct { +pub struct Struct { /// List of fields defined in the original struct. How, and if, an item /// should generate code, is decided by the currently generated version. pub fields: Vec, @@ -134,7 +134,7 @@ pub(crate) struct Struct { // Common token generation impl Struct { /// Generates code for the struct definition. - pub(crate) fn generate_definition(&self, version: &VersionDefinition) -> TokenStream { + pub fn generate_definition(&self, version: &VersionDefinition) -> TokenStream { let where_clause = self.generics.where_clause.as_ref(); let type_generics = &self.generics; @@ -160,8 +160,13 @@ impl Struct { } } + // TODO (@Techassi): It looks like some of the stuff from the upgrade and downgrade functions + // can be combined into a single piece of code. Figure out a nice way to do that. /// Generates code for the `From for NextVersion` implementation. - pub(crate) fn generate_from_impl( + /// + /// The `add_attributes` parameter declares if attributes (macros) should be added to the + /// generated `From` impl block. + pub fn generate_upgrade_from_impl( &self, version: &VersionDefinition, next_version: Option<&VersionDefinition>, @@ -185,7 +190,13 @@ impl Struct { let for_module_ident = &next_version.ident; let from_module_ident = &version.ident; - let fields = self.generate_from_fields(version, next_version, from_struct_ident); + let fields: TokenStream = self + .fields + .iter() + .map(|f| { + f.generate_for_upgrade_from_impl(version, next_version, from_struct_ident) + }) + .collect(); // Include allow(deprecated) only when this or the next version is // deprecated. Also include it, when a field in this or the next @@ -220,20 +231,64 @@ impl Struct { } } - /// Generates code for struct fields used in `From` implementations. - fn generate_from_fields( + pub fn generate_downgrade_from_impl( &self, version: &VersionDefinition, - next_version: &VersionDefinition, - from_struct_ident: &IdentString, - ) -> TokenStream { - let mut tokens = TokenStream::new(); - - for field in &self.fields { - tokens.extend(field.generate_for_from_impl(version, next_version, from_struct_ident)); + next_version: Option<&VersionDefinition>, + add_attributes: bool, + ) -> Option { + if version.skip_from || self.common.options.skip_from { + return None; } - tokens + match next_version { + Some(next_version) => { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let struct_ident = &self.common.idents.original; + let from_struct_ident = &self.common.idents.from; + + let for_module_ident = &version.ident; + let from_module_ident = &next_version.ident; + + let fields: TokenStream = self + .fields + .iter() + .map(|f| { + f.generate_for_downgrade_from_impl(version, next_version, from_struct_ident) + }) + .collect(); + + // Include allow(deprecated) only when this or the next version is + // deprecated. Also include it, when a field in this or the next + // version is deprecated. + let allow_attribute = (version.deprecated.is_some() + || next_version.deprecated.is_some() + || self.is_any_field_deprecated(version) + || self.is_any_field_deprecated(next_version)) + .then(|| quote! { #[allow(deprecated)] }); + + // Only add the #[automatically_derived] attribute only if this impl is used + // outside of a module (in standalone mode). + let automatically_derived = add_attributes + .not() + .then(|| quote! {#[automatically_derived]}); + + Some(quote! { + #automatically_derived + #allow_attribute + impl #impl_generics ::std::convert::From<#from_module_ident::#struct_ident #type_generics> for #for_module_ident::#struct_ident #type_generics + #where_clause + { + fn from(#from_struct_ident: #from_module_ident::#struct_ident #type_generics) -> Self { + Self { + #fields + } + } + } + }) + } + None => None, + } } /// Returns whether any field is deprecated in the provided `version`. @@ -260,88 +315,97 @@ impl Struct { } } +// TODO (@Techassi): Somehow bundle this into one struct which can emit all K8s related code. This +// makes keeping track of interconnected parts easier. // Kubernetes-specific token generation impl Struct { - pub(crate) fn generate_kube_attribute( - &self, - version: &VersionDefinition, - ) -> Option { - match &self.common.options.kubernetes_options { - Some(kubernetes_options) => { - // Required arguments - let group = &kubernetes_options.group; - let version = version.inner.to_string(); - let kind = kubernetes_options - .kind - .as_ref() - .map_or(self.common.idents.kubernetes.to_string(), |kind| { - kind.clone() - }); - - // Optional arguments - let singular = kubernetes_options - .singular - .as_ref() - .map(|s| quote! { , singular = #s }); - let plural = kubernetes_options - .plural - .as_ref() - .map(|p| quote! { , plural = #p }); - let namespaced = kubernetes_options - .namespaced - .then_some(quote! { , namespaced }); - let crates = kubernetes_options.crates.to_token_stream(); - let status = kubernetes_options - .status - .as_ref() - .map(|s| quote! { , status = #s }); - let shortnames: TokenStream = kubernetes_options - .shortnames - .iter() - .map(|s| quote! { , shortname = #s }) - .collect(); + pub fn generate_kube_attribute(&self, version: &VersionDefinition) -> Option { + let kubernetes_options = self.common.options.kubernetes_options.as_ref()?; + + // Required arguments + let group = &kubernetes_options.group; + let version = version.inner.to_string(); + let kind = kubernetes_options + .kind + .as_ref() + .map_or(self.common.idents.kubernetes.to_string(), |kind| { + kind.clone() + }); + + // Optional arguments + let singular = kubernetes_options + .singular + .as_ref() + .map(|s| quote! { , singular = #s }); + + let plural = kubernetes_options + .plural + .as_ref() + .map(|p| quote! { , plural = #p }); + + let namespaced = kubernetes_options + .namespaced + .then_some(quote! { , namespaced }); + let crates = kubernetes_options.crates.to_token_stream(); + + // TODO (@Techassi): This struct name should be defined once in a single place instead + // of constructing it in two different places which can lead to de-synchronization. + let status = kubernetes_options + .config_options + .experimental_conversion_tracking + .then(|| { + let status_ident = format_ident!( + "{struct_ident}StatusWithChangedValues", + struct_ident = self.common.idents.kubernetes.as_ident() + ); + quote! { , status = #status_ident } + }); + + let shortnames: TokenStream = kubernetes_options + .shortnames + .iter() + .map(|s| quote! { , shortname = #s }) + .collect(); - Some(quote! { - // The end-developer needs to derive CustomResource and JsonSchema. - // This is because we don't know if they want to use a re-exported or renamed import. - #[kube( - // These must be comma separated (except the last) as they always exist: - group = #group, version = #version, kind = #kind - // These fields are optional, and therefore the token stream must prefix each with a comma: - #singular #plural #namespaced #crates #status #shortnames - )] - }) - } - None => None, - } + Some(quote! { + // The end-developer needs to derive CustomResource and JsonSchema. + // This is because we don't know if they want to use a re-exported or renamed import. + #[kube( + // These must be comma separated (except the last) as they always exist: + group = #group, version = #version, kind = #kind + // These fields are optional, and therefore the token stream must prefix each with a comma: + #singular #plural #namespaced #crates #status #shortnames + )] + }) } - pub(crate) fn generate_kubernetes_item( + pub fn generate_kubernetes_item( &self, version: &VersionDefinition, ) -> Option<(IdentString, String, TokenStream)> { - match &self.common.options.kubernetes_options { - Some(options) if !options.skip_merged_crd => { - let kube_core_path = &*options.crates.kube_core; + let kubernetes_options = self.common.options.kubernetes_options.as_ref()?; - let enum_variant_ident = version.inner.as_variant_ident(); - let enum_variant_string = version.inner.to_string(); + if !kubernetes_options.skip_merged_crd { + let kube_core_crate = &*kubernetes_options.crates.kube_core; - let struct_ident = &self.common.idents.kubernetes; - let module_ident = &version.ident; - let qualified_path: Path = parse_quote!(#module_ident::#struct_ident); + let enum_variant_ident = version.inner.as_variant_ident(); + let enum_variant_string = version.inner.to_string(); - let merge_crds_fn_call = quote! { - <#qualified_path as #kube_core_path::CustomResourceExt>::crd() - }; + let struct_ident = &self.common.idents.kubernetes; + let module_ident = &version.ident; + let qualified_path: Path = parse_quote!(#module_ident::#struct_ident); - Some((enum_variant_ident, enum_variant_string, merge_crds_fn_call)) - } - _ => None, + let merge_crds_fn_call = quote! { + <#qualified_path as #kube_core_crate::CustomResourceExt>::crd() + }; + + Some((enum_variant_ident, enum_variant_string, merge_crds_fn_call)) + } else { + None } } - pub(crate) fn generate_kubernetes_merge_crds( + pub fn generate_kubernetes_merge_crds( &self, enum_variant_idents: &[IdentString], enum_variant_strings: &[String], @@ -349,46 +413,88 @@ impl Struct { vis: &Visibility, is_nested: bool, ) -> Option { - match &self.common.options.kubernetes_options { - Some(kubernetes_options) if !kubernetes_options.skip_merged_crd => { - let enum_ident = &self.common.idents.kubernetes; - - // Only add the #[automatically_derived] attribute if this impl is used outside of a - // module (in standalone mode). - let automatically_derived = - is_nested.not().then(|| quote! {#[automatically_derived]}); - - // Get the crate paths - let k8s_openapi_path = &*kubernetes_options.crates.k8s_openapi; - let kube_core_path = &*kubernetes_options.crates.kube_core; - - Some(quote! { - #automatically_derived - #vis enum #enum_ident { - #(#enum_variant_idents),* + let kubernetes_options = self.common.options.kubernetes_options.as_ref()?; + + if !kubernetes_options.skip_merged_crd { + let enum_ident = &self.common.idents.kubernetes; + + // Only add the #[automatically_derived] attribute if this impl is used outside of a + // module (in standalone mode). + let automatically_derived = is_nested.not().then(|| quote! {#[automatically_derived]}); + + // Get the crate paths + let k8s_openapi_path = &*kubernetes_options.crates.k8s_openapi; + let kube_core_path = &*kubernetes_options.crates.kube_core; + + Some(quote! { + #automatically_derived + #vis enum #enum_ident { + #(#enum_variant_idents),* + } + + #automatically_derived + impl ::std::fmt::Display for #enum_ident { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { + match self { + #(Self::#enum_variant_idents => f.write_str(#enum_variant_strings)),* + } + } + } + + #automatically_derived + impl #enum_ident { + /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. + pub fn merged_crd( + stored_apiversion: Self + ) -> ::std::result::Result<#k8s_openapi_path::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, #kube_core_path::crd::MergeError> { + #kube_core_path::crd::merge_crds(vec![#(#fn_calls),*], &stored_apiversion.to_string()) } + } + }) + } else { + None + } + } - #automatically_derived - impl ::std::fmt::Display for #enum_ident { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { - match self { - #(Self::#enum_variant_idents => f.write_str(#enum_variant_strings)),* - } - } + pub fn generate_kubernetes_status_struct(&self) -> Option { + let kubernetes_options = self.common.options.kubernetes_options.as_ref()?; + + kubernetes_options + .config_options + .experimental_conversion_tracking + .then(|| { + let status_ident = format_ident!( + "{struct_ident}StatusWithChangedValues", + struct_ident = self.common.idents.kubernetes.as_ident() + ); + + let versioned_crate = &*kubernetes_options.crates.versioned; + let schemars_crate = &*kubernetes_options.crates.schemars; + let serde_crate = &*kubernetes_options.crates.serde; + + // TODO (@Techassi): Validate that users don't specify the status we generate + let status = kubernetes_options.status.as_ref().map(|status| { + quote! { + #[serde(flatten)] + pub status: #status, } + }); + + quote! { + #[derive( + ::core::clone::Clone, + ::core::fmt::Debug, + #serde_crate::Deserialize, + #serde_crate::Serialize, + #schemars_crate::JsonSchema + )] + #[serde(rename_all = "camelCase")] + pub struct #status_ident { + pub changed_values: #versioned_crate::ChangedValues, - #automatically_derived - impl #enum_ident { - /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. - pub fn merged_crd( - stored_apiversion: Self - ) -> ::std::result::Result<#k8s_openapi_path::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, #kube_core_path::crd::MergeError> { - #kube_core_path::crd::merge_crds(vec![#(#fn_calls),*], &stored_apiversion.to_string()) - } + #status } - }) - } - _ => None, - } + } + }) } } diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index f55f774fa..c039e9873 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -144,7 +144,7 @@ impl VersionedField { } // TODO (@Techassi): This should probably return an optional token stream. - pub(crate) fn generate_for_from_impl( + pub fn generate_for_upgrade_from_impl( &self, version: &VersionDefinition, next_version: &VersionDefinition, @@ -174,16 +174,16 @@ impl VersionedField { _, ItemStatus::Change { from_ident: old_field_ident, - convert_with, + upgrade_with, to_ident, .. }, - ) => match convert_with { + ) => match upgrade_with { // The user specified a custom conversion function which // will be used here instead of the default .into() call // which utilizes From impls. - Some(convert_fn) => quote! { - #to_ident: #convert_fn(#from_struct_ident.#old_field_ident), + Some(upgrade_fn) => quote! { + #to_ident: #upgrade_fn(#from_struct_ident.#old_field_ident), }, // Default .into() call using From impls. None => quote! { @@ -212,4 +212,62 @@ impl VersionedField { } } } + + pub fn generate_for_downgrade_from_impl( + &self, + version: &VersionDefinition, + next_version: &VersionDefinition, + from_struct_ident: &IdentString, + ) -> TokenStream { + match &self.changes { + Some(changes) => { + let next_change = changes.get_expect(&next_version.inner); + let change = changes.get_expect(&version.inner); + + match (change, next_change) { + // If both this status and the next one is NotPresent, which means + // a field was introduced after a bunch of versions, we don't + // need to generate any code for the From impl. + (ItemStatus::NotPresent, ItemStatus::NotPresent) => { + quote! {} + } + (_, ItemStatus::Addition { .. }) => quote! {}, + ( + _, + ItemStatus::Change { + downgrade_with, + from_ident: old_field_ident, + to_ident, + .. + }, + ) => match downgrade_with { + Some(downgrade_fn) => quote! { + #old_field_ident: #downgrade_fn(#from_struct_ident.#to_ident), + }, + None => quote! { + #old_field_ident: #from_struct_ident.#to_ident.into(), + }, + }, + (old, next) => { + let next_field_ident = next.get_ident(); + let old_field_ident = old.get_ident(); + + // NOTE (@Techassi): Do we really need .into() here. I'm + // currently not sure why it is there and if it is needed + // in some edge cases. + quote! { + #old_field_ident: #from_struct_ident.#next_field_ident.into(), + } + } + } + } + None => { + let field_ident = &*self.ident; + + quote! { + #field_ident: #from_struct_ident.#field_ident.into(), + } + } + } + } } diff --git a/crates/stackable-versioned-macros/src/codegen/item/variant.rs b/crates/stackable-versioned-macros/src/codegen/item/variant.rs index a65168c57..24c62f76b 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/variant.rs @@ -133,40 +133,67 @@ impl VersionedVariant { } } - pub(crate) fn generate_for_from_impl( + pub fn generate_for_upgrade_from_impl( &self, version: &VersionDefinition, next_version: &VersionDefinition, enum_ident: &IdentString, ) -> Option { - let variant_fields = match &self.fields { - Fields::Named(fields_named) => { - let fields: Vec<_> = fields_named - .named - .iter() - .map(|field| { - field - .ident - .as_ref() - .expect("named fields always have an ident") - .clone() - }) - .collect(); + let variant_fields = self.fields_as_token_stream(); + + match &self.changes { + Some(changes) => { + let next_change = changes.get_expect(&next_version.inner); + let change = changes.get_expect(&version.inner); + + match (change, next_change) { + (_, ItemStatus::Addition { .. }) => None, + (old, next) => { + let next_version_ident = &next_version.ident; + let old_version_ident = &version.ident; - quote! { { #(#fields),* } } + let next_variant_ident = next.get_ident(); + let old_variant_ident = old.get_ident(); + + let old = quote! { + #old_version_ident::#enum_ident::#old_variant_ident #variant_fields + }; + let next = quote! { + #next_version_ident::#enum_ident::#next_variant_ident #variant_fields + }; + + Some(quote! { + #old => #next, + }) + } + } } - Fields::Unnamed(fields_unnamed) => { - let fields: Vec<_> = fields_unnamed - .unnamed - .iter() - .enumerate() - .map(|(index, _)| format_ident!("__sv_{index}")) - .collect(); + None => { + let next_version_ident = &next_version.ident; + let old_version_ident = &version.ident; + let variant_ident = &*self.ident; + + let old = quote! { + #old_version_ident::#enum_ident::#variant_ident #variant_fields + }; + let next = quote! { + #next_version_ident::#enum_ident::#variant_ident #variant_fields + }; - quote! { ( #(#fields),* ) } + Some(quote! { + #old => #next, + }) } - Fields::Unit => TokenStream::new(), - }; + } + } + + pub fn generate_for_downgrade_from_impl( + &self, + version: &VersionDefinition, + next_version: &VersionDefinition, + enum_ident: &IdentString, + ) -> Option { + let variant_fields = self.fields_as_token_stream(); match &self.changes { Some(changes) => { @@ -190,7 +217,7 @@ impl VersionedVariant { }; Some(quote! { - #old => #next, + #next => #old, }) } } @@ -208,9 +235,39 @@ impl VersionedVariant { }; Some(quote! { - #old => #next, + #next => #old, }) } } } + + fn fields_as_token_stream(&self) -> Option { + match &self.fields { + Fields::Named(fields_named) => { + let fields: Vec<_> = fields_named + .named + .iter() + .map(|field| { + field + .ident + .as_ref() + .expect("named fields always have an ident") + }) + .collect(); + + Some(quote! { { #(#fields),* } }) + } + Fields::Unnamed(fields_unnamed) => { + let fields: Vec<_> = fields_unnamed + .unnamed + .iter() + .enumerate() + .map(|(index, _)| format_ident!("__sv_{index}")) + .collect(); + + Some(quote! { ( #(#fields),* ) }) + } + Fields::Unit => None, + } + } } diff --git a/crates/stackable-versioned-macros/src/codegen/mod.rs b/crates/stackable-versioned-macros/src/codegen/mod.rs index 4f4b2ea3c..88bf340c6 100644 --- a/crates/stackable-versioned-macros/src/codegen/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/mod.rs @@ -81,7 +81,8 @@ pub enum ItemStatus { ty: Type, }, Change { - convert_with: Option, + downgrade_with: Option, + upgrade_with: Option, from_ident: IdentString, to_ident: IdentString, from_type: Type, @@ -107,7 +108,7 @@ impl ItemStatus { ItemStatus::Change { to_ident, .. } => to_ident, ItemStatus::Deprecation { ident, .. } => ident, ItemStatus::NoChange { ident, .. } => ident, - ItemStatus::NotPresent => unreachable!(), + ItemStatus::NotPresent => unreachable!("ItemStatus::NotPresent does not have an ident"), } } } diff --git a/crates/stackable-versioned-macros/src/codegen/module.rs b/crates/stackable-versioned-macros/src/codegen/module.rs index 217dacced..11e84e47c 100644 --- a/crates/stackable-versioned-macros/src/codegen/module.rs +++ b/crates/stackable-versioned-macros/src/codegen/module.rs @@ -151,6 +151,7 @@ impl Module { let mut versions = self.versions.iter().peekable(); while let Some(version) = versions.next() { + let next_version = versions.peek().copied(); let mut container_definitions = TokenStream::new(); let mut from_impls = TokenStream::new(); @@ -160,9 +161,15 @@ impl Module { container_definitions.extend(container.generate_definition(version)); if !self.skip_from { - from_impls.extend(container.generate_from_impl( + from_impls.extend(container.generate_upgrade_from_impl( version, - versions.peek().copied(), + next_version, + self.preserve_module, + )); + + from_impls.extend(container.generate_downgrade_from_impl( + version, + next_version, self.preserve_module, )); } @@ -228,6 +235,8 @@ impl Module { version_module_vis, self.preserve_module, )); + + kubernetes_tokens.extend(container.generate_kubernetes_status_struct()); } } diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index fdb955b51..9f013a6db 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -463,11 +463,14 @@ mod utils; /// - `since` to indicate since which version the item is changed. /// - `from_name` to indicate from which previous name the field is renamed. /// - `from_type` to indicate from which previous type the field is changed. -/// - `convert_with` to provide a custom conversion function instead of using -/// a [`From`] implementation by default. This argument can only be used in -/// combination with the `from_type` argument. The expected function signature -/// is: `fn (OLD_TYPE) -> NEW_TYPE`. This function must not fail. -/// +/// - `upgrade_with` to provide a custom upgrade function. This argument can +/// only be used in combination with the `from_type` argument. The expected +/// function signature is: `fn (OLD_TYPE) -> NEW_TYPE`. This function must +/// not fail. +///- `downgrade_with` to provide a custom downgrade function. This argument can +/// only be used in combination with the `from_type` argument. The expected +/// function signature is: `fn (NEW_TYPE) -> OLD_TYPE`. This function must +/// not fail. /// ``` /// # use stackable_versioned_macros::versioned; /// #[versioned( @@ -478,11 +481,16 @@ mod utils; /// #[versioned(changed( /// since = "v1beta1", /// from_name = "prev_bar", -/// from_type = "u16" +/// from_type = "u16", +/// downgrade_with = usize_to_u16 /// ))] /// bar: usize, /// baz: bool, /// } +/// +/// fn usize_to_u16(input: usize) -> u16 { +/// input.try_into().unwrap() +/// } /// ``` /// ///
@@ -591,8 +599,8 @@ mod utils; /// ### Custom conversion function at field level /// /// As stated in the [`changed()`](#changed-action) section, a custom conversion -/// function can be provided using the `convert_with` argument. A simple example -/// looks like this: +/// function can be provided using the `downgrade_with` and `upgrade_with` +/// argument. A simple example looks like this: /// /// ``` /// # use stackable_versioned_macros::versioned; @@ -604,13 +612,13 @@ mod utils; /// #[versioned(changed( /// since = "v1beta1", /// from_type = "u8", -/// convert_with = "u8_to_u16" +/// downgrade_with = "u16_to_u8" /// ))] /// bar: u16, /// } /// -/// fn u8_to_u16(old: u8) -> u16 { -/// old as u16 +/// fn u16_to_u8(input: u16) -> u8 { +/// input.try_into().unwrap() /// } /// ``` /// @@ -628,7 +636,15 @@ mod utils; /// impl ::std::convert::From for v1beta1::Foo { /// fn from(__sv_foo: v1alpha1::Foo) -> Self { /// Self { -/// bar: u8_to_u16(__sv_foo.bar), +/// bar: __sv_foo.bar.into(), +/// } +/// } +/// } +/// +/// impl ::std::convert::From for v1alpha1::Foo { +/// fn from(__sv_foo: v1beta1::Foo) -> Self { +/// Self { +/// bar: u16_to_u8(__sv_foo.bar), /// } /// } /// } @@ -718,12 +734,16 @@ use serde::{Deserialize, Serialize}; pub struct FooSpec { #[versioned( added(since = "v1beta1"), - changed(since = "v1", from_name = "prev_bar", from_type = "u16") + changed(since = "v1", from_name = "prev_bar", from_type = "u16", downgrade_with = usize_to_u16) )] bar: usize, baz: bool, } +fn usize_to_u16(input: usize) -> u16 { + input.try_into().unwrap() +} + # fn main() { let merged_crd = Foo::merged_crd(Foo::V1).unwrap(); println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); diff --git a/crates/stackable-versioned-macros/tests/inputs/default/pass/basic_struct.rs b/crates/stackable-versioned-macros/tests/inputs/default/pass/basic_struct.rs index c074e9456..488ea73ad 100644 --- a/crates/stackable-versioned-macros/tests/inputs/default/pass/basic_struct.rs +++ b/crates/stackable-versioned-macros/tests/inputs/default/pass/basic_struct.rs @@ -13,8 +13,8 @@ pub(crate) struct Foo { foo: String, #[versioned( - changed(since = "v1beta1", from_name = "jjj", from_type = "u8"), - changed(since = "v1", from_type = "u16"), + changed(since = "v1beta1", from_name = "jjj", from_type = "u8", downgrade_with = u16_to_u8), + changed(since = "v1", from_type = "u16", downgrade_with = usize_to_u16), deprecated(since = "v2", note = "not empty") )] /// Test @@ -23,3 +23,11 @@ pub(crate) struct Foo { } // --- fn main() {} + +fn usize_to_u16(input: usize) -> u16 { + input.try_into().unwrap() +} + +fn u16_to_u8(input: u16) -> u8 { + input.try_into().unwrap() +} diff --git a/crates/stackable-versioned-macros/tests/inputs/default/pass/convert_with.rs b/crates/stackable-versioned-macros/tests/inputs/default/pass/downgrade_with.rs similarity index 65% rename from crates/stackable-versioned-macros/tests/inputs/default/pass/convert_with.rs rename to crates/stackable-versioned-macros/tests/inputs/default/pass/downgrade_with.rs index cfeea317e..e589b2d57 100644 --- a/crates/stackable-versioned-macros/tests/inputs/default/pass/convert_with.rs +++ b/crates/stackable-versioned-macros/tests/inputs/default/pass/downgrade_with.rs @@ -7,18 +7,18 @@ struct Foo { // This tests two additional things: // - that both unquoted and quoted usage works // - that the renamed name does get picked up correctly by the conversion function - changed(since = "v1", from_type = "u16", from_name = "bar", convert_with = u16_to_u32), - changed(since = "v2", from_type = "u32", convert_with = "u32_to_u64") + changed(since = "v1", from_type = "u16", from_name = "bar", downgrade_with = u32_to_u16), + changed(since = "v2", from_type = "u32", downgrade_with = "u64_to_u32") )] baz: u64, } // --- fn main() {} -fn u16_to_u32(input: u16) -> u32 { - input as u32 +fn u32_to_u16(input: u32) -> u16 { + input.try_into().unwrap() } -fn u32_to_u64(input: u32) -> u64 { - input as u64 +fn u64_to_u32(input: u64) -> u32 { + input.try_into().unwrap() } diff --git a/crates/stackable-versioned-macros/tests/inputs/default/pass/enum_data_simple.rs b/crates/stackable-versioned-macros/tests/inputs/default/pass/enum_data_simple.rs index 99281f414..178c36e9c 100644 --- a/crates/stackable-versioned-macros/tests/inputs/default/pass/enum_data_simple.rs +++ b/crates/stackable-versioned-macros/tests/inputs/default/pass/enum_data_simple.rs @@ -5,12 +5,16 @@ use stackable_versioned::versioned; enum Foo { Foo, Bar(u32, String), - - #[versioned(added(since = "v1alpha2"))] - Baz { - id: u32, - name: String, - }, + // FIXME (@Techassi): How do we handle downgrades of enums? The variant just + // doesn't exist in the earlier version, but we still need to handle the + // variant in the match. I think for this to work, we would need to require + // the user to specify a downgrade_with function. For now, I commented out + // the code to get the test to pass again. + // #[versioned(added(since = "v1alpha2"))] + // Baz { + // id: u32, + // name: String, + // }, } // --- fn main() {} diff --git a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/basic.rs b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/basic.rs index 95499f5d8..0b6b9bf5c 100644 --- a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/basic.rs +++ b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/basic.rs @@ -18,10 +18,14 @@ use stackable_versioned::versioned; pub(crate) struct FooSpec { #[versioned( added(since = "v1beta1"), - changed(since = "v1", from_name = "bah", from_type = "u16") + changed(since = "v1", from_name = "bah", from_type = "u16", downgrade_with = usize_to_u16) )] bar: usize, baz: bool, } // --- fn main() {} + +fn usize_to_u16(input: usize) -> u16 { + input.try_into().unwrap() +} diff --git a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/conversion_tracking.rs b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/conversion_tracking.rs new file mode 100644 index 000000000..325256f4b --- /dev/null +++ b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/conversion_tracking.rs @@ -0,0 +1,33 @@ +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use stackable_versioned::versioned; +// --- +#[versioned( + version(name = "v1alpha1"), + version(name = "v1beta1"), + version(name = "v1"), + k8s( + group = "stackable.tech", + status = FooStatus, + options(experimental_conversion_tracking), + ) +)] +// --- +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] +pub(crate) struct FooSpec { + #[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))] + bar: usize, + baz: bool, +} +// --- +fn main() {} + +#[derive(Clone, Debug, JsonSchema, Deserialize, Serialize)] +pub struct FooStatus { + foo: String, +} + +fn usize_to_u16(input: usize) -> u16 { + input.try_into().unwrap() +} diff --git a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/crate_overrides.rs b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/crate_overrides.rs index e6a21dbb2..ff3dacdb9 100644 --- a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/crate_overrides.rs +++ b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/crate_overrides.rs @@ -6,11 +6,9 @@ use stackable_versioned::versioned; version(name = "v1"), k8s( group = "foo.example.org", - singular = "foo", - plural = "foos", - namespaced, crates( - kube_core = ::kube::core + kube_core = ::kube::core, + schemars = ::schemars ) ) )] @@ -19,10 +17,7 @@ use stackable_versioned::versioned; Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema, kube::CustomResource, )] pub struct FooSpec { - #[versioned( - added(since = "v1beta1"), - changed(since = "v1", from_name = "bah", from_type = "u16") - )] + #[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))] bar: usize, baz: bool, } diff --git a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/renamed_kind.rs b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/renamed_kind.rs index 29c99d76f..53f479b9a 100644 --- a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/renamed_kind.rs +++ b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/renamed_kind.rs @@ -4,23 +4,14 @@ use stackable_versioned::versioned; version(name = "v1alpha1"), version(name = "v1beta1"), version(name = "v1"), - k8s( - group = "stackable.tech", - kind = "FooBar", - singular = "foo", - plural = "foos", - namespaced, - ) + k8s(group = "stackable.tech", kind = "FooBar", namespaced) )] // --- #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema, kube::CustomResource, )] pub struct FooSpec { - #[versioned( - added(since = "v1beta1"), - changed(since = "v1", from_name = "bah", from_type = "u16") - )] + #[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))] bar: usize, baz: bool, } diff --git a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/skip.rs b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/skip.rs index 773b0ff16..535091c9e 100644 --- a/crates/stackable-versioned-macros/tests/inputs/k8s/pass/skip.rs +++ b/crates/stackable-versioned-macros/tests/inputs/k8s/pass/skip.rs @@ -4,23 +4,14 @@ use stackable_versioned::versioned; version(name = "v1alpha1"), version(name = "v1beta1"), version(name = "v1"), - k8s( - group = "stackable.tech", - singular = "foo", - plural = "foos", - namespaced, - skip(merged_crd) - ) + k8s(group = "stackable.tech", skip(merged_crd)) )] // --- #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema, kube::CustomResource, )] pub struct FooSpec { - #[versioned( - added(since = "v1beta1"), - changed(since = "v1", from_name = "bah", from_type = "u16") - )] + #[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))] bar: usize, baz: bool, } diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@added.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@added.rs.snap index 01950c0b4..95a89142a 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@added.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@added.rs.snap @@ -20,6 +20,14 @@ impl ::std::convert::From for v1alpha2::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1alpha2::Foo) -> Self { + Self { + username: __sv_foo.username.into(), + } + } +} +#[automatically_derived] mod v1alpha2 { use super::*; pub struct Foo { @@ -38,6 +46,15 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha2::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + username: __sv_foo.username.into(), + first_name: __sv_foo.first_name.into(), + } + } +} +#[automatically_derived] mod v1beta1 { use super::*; pub struct Foo { @@ -57,6 +74,16 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + username: __sv_foo.username.into(), + first_name: __sv_foo.first_name.into(), + last_name: __sv_foo.last_name.into(), + } + } +} +#[automatically_derived] mod v1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@basic_struct.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@basic_struct.rs.snap index 6bacdab22..1e8601603 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@basic_struct.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@basic_struct.rs.snap @@ -24,6 +24,16 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + jjj: u16_to_u8(__sv_foo.bar), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v1beta1 { use super::*; pub struct Foo { @@ -43,6 +53,15 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: usize_to_u16(__sv_foo.bar), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v1 { use super::*; pub struct Foo { @@ -64,6 +83,17 @@ impl ::std::convert::From for v2::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + Self { + foo: __sv_foo.foo.into(), + bar: __sv_foo.deprecated_bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v2 { use super::*; pub struct Foo { @@ -86,6 +116,17 @@ impl ::std::convert::From for v3::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v2::Foo { + fn from(__sv_foo: v3::Foo) -> Self { + Self { + foo: __sv_foo.foo.into(), + deprecated_bar: __sv_foo.deprecated_bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v3 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_enum.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_enum.rs.snap index a0ca3f518..274de6173 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_enum.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_enum.rs.snap @@ -21,6 +21,15 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + match __sv_foo { + v1beta1::Foo::Bar => v1alpha1::Foo::Bar, + v1beta1::Foo::Baz => v1alpha1::Foo::Baz, + } + } +} +#[automatically_derived] mod v1beta1 { use super::*; pub enum Foo { @@ -39,6 +48,16 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1beta1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + match __sv_foo { + v1::Foo::DeprecatedBar => v1beta1::Foo::Bar, + v1::Foo::Baz => v1beta1::Foo::Baz, + } + } +} +#[automatically_derived] mod v1 { use super::*; pub enum Foo { @@ -58,6 +77,16 @@ impl ::std::convert::From for v2::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + match __sv_foo { + v2::Foo::DeprecatedBar => v1::Foo::DeprecatedBar, + v2::Foo::Baz => v1::Foo::Baz, + } + } +} +#[automatically_derived] mod v2 { use super::*; pub enum Foo { @@ -77,6 +106,16 @@ impl ::std::convert::From for v3::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v2::Foo { + fn from(__sv_foo: v3::Foo) -> Self { + match __sv_foo { + v3::Foo::DeprecatedBar => v2::Foo::DeprecatedBar, + v3::Foo::Baz => v2::Foo::Baz, + } + } +} +#[automatically_derived] mod v3 { use super::*; pub enum Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_struct.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_struct.rs.snap index 0b141ae28..3159caee8 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_struct.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@deprecate_struct.rs.snap @@ -21,6 +21,15 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v1beta1 { use super::*; pub struct Foo { @@ -39,6 +48,16 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1beta1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.deprecated_bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v1 { use super::*; pub struct Foo { @@ -58,6 +77,16 @@ impl ::std::convert::From for v2::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.deprecated_bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v2 { use super::*; pub struct Foo { @@ -77,6 +106,16 @@ impl ::std::convert::From for v3::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v2::Foo { + fn from(__sv_foo: v3::Foo) -> Self { + Self { + deprecated_bar: __sv_foo.deprecated_bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v3 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@convert_with.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@downgrade_with.rs.snap similarity index 63% rename from crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@convert_with.rs.snap rename to crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@downgrade_with.rs.snap index 5d3a432dd..28915bc31 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@convert_with.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@downgrade_with.rs.snap @@ -1,7 +1,7 @@ --- source: crates/stackable-versioned-macros/src/lib.rs expression: formatted -input_file: crates/stackable-versioned-macros/tests/inputs/default/pass/convert_with.rs +input_file: crates/stackable-versioned-macros/tests/inputs/default/pass/downgrade_with.rs --- #[automatically_derived] mod v1alpha1 { @@ -13,8 +13,14 @@ mod v1alpha1 { #[automatically_derived] impl ::std::convert::From for v1::Foo { fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { baz: __sv_foo.bar.into() } + } +} +#[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { Self { - baz: u16_to_u32(__sv_foo.bar), + bar: u32_to_u16(__sv_foo.baz), } } } @@ -28,8 +34,14 @@ mod v1 { #[automatically_derived] impl ::std::convert::From for v2::Foo { fn from(__sv_foo: v1::Foo) -> Self { + Self { baz: __sv_foo.baz.into() } + } +} +#[automatically_derived] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2::Foo) -> Self { Self { - baz: u32_to_u64(__sv_foo.baz), + baz: u64_to_u32(__sv_foo.baz), } } } diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@enum_data_simple.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@enum_data_simple.rs.snap index 6bc2a4efd..572d6639c 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@enum_data_simple.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@enum_data_simple.rs.snap @@ -21,11 +21,19 @@ impl ::std::convert::From for v1alpha2::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1alpha2::Foo) -> Self { + match __sv_foo { + v1alpha2::Foo::Foo => v1alpha1::Foo::Foo, + v1alpha2::Foo::Bar(__sv_0, __sv_1) => v1alpha1::Foo::Bar(__sv_0, __sv_1), + } + } +} +#[automatically_derived] mod v1alpha2 { use super::*; pub enum Foo { Foo, Bar(u32, String), - Baz { id: u32, name: String }, } } diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_defaults.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_defaults.rs.snap index 0d97edfab..94286a5f8 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_defaults.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_defaults.rs.snap @@ -27,6 +27,18 @@ where } } #[automatically_derived] +impl ::std::convert::From> for v1alpha1::Foo +where + T: Default, +{ + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub mod v1 { use super::*; pub struct Foo diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_enum.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_enum.rs.snap index bdcd82562..dbaf91fcb 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_enum.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_enum.rs.snap @@ -27,6 +27,18 @@ where } } #[automatically_derived] +impl ::std::convert::From> for v1alpha1::Foo +where + T: Default, +{ + fn from(__sv_foo: v1::Foo) -> Self { + match __sv_foo { + v1::Foo::Bar(__sv_0) => v1alpha1::Foo::Bar(__sv_0), + v1::Foo::Baz => v1alpha1::Foo::Baz, + } + } +} +#[automatically_derived] pub mod v1 { use super::*; pub enum Foo diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_module.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_module.rs.snap index 033a18f3b..68b14527f 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_module.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_module.rs.snap @@ -33,6 +33,17 @@ pub mod versioned { } } } + impl ::std::convert::From> for v1alpha1::Foo + where + T: Default, + { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } + } impl ::std::convert::From> for v1::Boom where T: Default, @@ -44,6 +55,17 @@ pub mod versioned { } } } + impl ::std::convert::From> for v1alpha1::Boom + where + T: Default, + { + fn from(__sv_boom: v1::Boom) -> Self { + match __sv_boom { + v1::Boom::Big(__sv_0) => v1alpha1::Boom::Big(__sv_0), + v1::Boom::Shaq => v1alpha1::Boom::Shaq, + } + } + } pub mod v1 { use super::*; pub struct Foo diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_struct.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_struct.rs.snap index 7504298eb..af5daf229 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_struct.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@generics_struct.rs.snap @@ -27,6 +27,18 @@ where } } #[automatically_derived] +impl ::std::convert::From> for v1alpha1::Foo +where + T: Default, +{ + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] pub mod v1 { use super::*; pub struct Foo diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module.rs.snap index 073eb41cd..3f4ed5ff3 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module.rs.snap @@ -25,12 +25,27 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + foo: __sv_foo.foo.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v1::Bar { fn from(__sv_bar: v1alpha1::Bar) -> Self { Self { baz: __sv_bar.baz.into() } } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Bar { + fn from(__sv_bar: v1::Bar) -> Self { + Self { baz: __sv_bar.baz.into() } + } +} +#[automatically_derived] pub(crate) mod v1 { use super::*; pub struct Foo { @@ -54,12 +69,29 @@ impl ::std::convert::From for v2alpha1::Foo { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + foo: __sv_foo.deprecated_foo.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v2alpha1::Bar { fn from(__sv_bar: v1::Bar) -> Self { Self { baz: __sv_bar.baz.into() } } } #[automatically_derived] +impl ::std::convert::From for v1::Bar { + fn from(__sv_bar: v2alpha1::Bar) -> Self { + Self { baz: __sv_bar.baz.into() } + } +} +#[automatically_derived] pub(crate) mod v2alpha1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module_preserve.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module_preserve.rs.snap index 0e818835b..f4113c479 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@module_preserve.rs.snap @@ -24,11 +24,24 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + foo: __sv_foo.foo.into(), + } + } + } impl ::std::convert::From for v1::Bar { fn from(__sv_bar: v1alpha1::Bar) -> Self { Self { baz: __sv_bar.baz.into() } } } + impl ::std::convert::From for v1alpha1::Bar { + fn from(__sv_bar: v1::Bar) -> Self { + Self { baz: __sv_bar.baz.into() } + } + } pub mod v1 { use super::*; pub struct Foo { @@ -50,11 +63,26 @@ pub(crate) mod versioned { } } } + #[allow(deprecated)] + impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v2alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + foo: __sv_foo.deprecated_foo.into(), + } + } + } impl ::std::convert::From for v2alpha1::Bar { fn from(__sv_bar: v1::Bar) -> Self { Self { baz: __sv_bar.baz.into() } } } + impl ::std::convert::From for v1::Bar { + fn from(__sv_bar: v2alpha1::Bar) -> Self { + Self { baz: __sv_bar.baz.into() } + } + } pub mod v2alpha1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@rename.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@rename.rs.snap index a01141775..90b9db812 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@rename.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@rename.rs.snap @@ -21,6 +21,15 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { + bat: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v1beta1 { use super::*; pub struct Foo { @@ -38,6 +47,15 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] mod v1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_for_version.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_for_version.rs.snap index 84d4bf428..34f9a8db4 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_for_version.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_for_version.rs.snap @@ -20,6 +20,12 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { baz: __sv_foo.baz.into() } + } +} +#[automatically_derived] pub mod v1beta1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_module_for_version.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_module_for_version.rs.snap index 209dbcd95..578388d05 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_module_for_version.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@skip_from_module_for_version.rs.snap @@ -23,6 +23,12 @@ impl ::std::convert::From for v1beta1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1beta1::Foo) -> Self { + Self { bar: __sv_foo.bar.into() } + } +} +#[automatically_derived] impl ::std::convert::From for v1beta1::Bar { fn from(__sv_bar: v1alpha1::Bar) -> Self { Self { @@ -32,6 +38,12 @@ impl ::std::convert::From for v1beta1::Bar { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Bar { + fn from(__sv_bar: v1beta1::Bar) -> Self { + Self { foo: __sv_bar.foo.into() } + } +} +#[automatically_derived] pub(crate) mod v1beta1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@submodule.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@submodule.rs.snap index 49d7b51b1..76176c894 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@submodule.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__default@submodule.rs.snap @@ -19,6 +19,12 @@ impl ::std::convert::From for v1::Foo { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { bar: __sv_foo.bar.into() } + } +} +#[automatically_derived] mod v1 { use super::*; pub struct Foo { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@basic.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@basic.rs.snap index babcabc0b..e30c2f3ad 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@basic.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@basic.rs.snap @@ -36,6 +36,14 @@ impl ::std::convert::From for v1beta1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v1beta1 { use super::*; #[derive( @@ -69,6 +77,15 @@ impl ::std::convert::From for v1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bah: usize_to_u16(__sv_foospec.bar), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub(crate) mod v1 { use super::*; #[derive( diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@conversion_tracking.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@conversion_tracking.rs.snap new file mode 100644 index 000000000..9aa32539b --- /dev/null +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@conversion_tracking.rs.snap @@ -0,0 +1,135 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/tests/inputs/k8s/pass/conversion_tracking.rs +--- +#[automatically_derived] +pub(crate) mod v1alpha1 { + use super::*; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1alpha1", + kind = "Foo", + status = FooStatusWithChangedValues + )] + pub struct FooSpec { + pub baz: bool, + } +} +#[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1alpha1::FooSpec) -> Self { + Self { + bah: ::std::default::Default::default(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] +pub(crate) mod v1beta1 { + use super::*; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1beta1", + kind = "Foo", + status = FooStatusWithChangedValues + )] + pub struct FooSpec { + pub bah: usize, + pub baz: bool, + } +} +#[automatically_derived] +impl ::std::convert::From for v1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bah.into(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bah: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] +pub(crate) mod v1 { + use super::*; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "stackable.tech", + version = "v1", + kind = "Foo", + status = FooStatusWithChangedValues + )] + pub struct FooSpec { + pub bar: usize, + pub baz: bool, + } +} +#[automatically_derived] +pub(crate) enum Foo { + V1Alpha1, + V1Beta1, + V1, +} +#[automatically_derived] +impl ::std::fmt::Display for Foo { + fn fmt( + &self, + f: &mut ::std::fmt::Formatter<'_>, + ) -> ::std::result::Result<(), ::std::fmt::Error> { + match self { + Self::V1Alpha1 => f.write_str("v1alpha1"), + Self::V1Beta1 => f.write_str("v1beta1"), + Self::V1 => f.write_str("v1"), + } + } +} +#[automatically_derived] +impl Foo { + /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. + pub fn merged_crd( + stored_apiversion: Self, + ) -> ::std::result::Result< + ::k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, + ::kube::core::crd::MergeError, + > { + ::kube::core::crd::merge_crds( + vec![ + < v1alpha1::Foo as ::kube::core::CustomResourceExt > ::crd(), < + v1beta1::Foo as ::kube::core::CustomResourceExt > ::crd(), < v1::Foo as + ::kube::core::CustomResourceExt > ::crd() + ], + &stored_apiversion.to_string(), + ) + } +} +#[derive( + ::core::clone::Clone, + ::core::fmt::Debug, + ::serde::Deserialize, + ::serde::Serialize, + ::schemars::JsonSchema +)] +#[serde(rename_all = "camelCase")] +pub struct FooStatusWithChangedValues { + pub changed_values: ::stackable_versioned::ChangedValues, + #[serde(flatten)] + pub status: FooStatus, +} diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@crate_overrides.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@crate_overrides.rs.snap index d48520b30..cf7afc27a 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@crate_overrides.rs.snap @@ -18,10 +18,7 @@ pub mod v1alpha1 { group = "foo.example.org", version = "v1alpha1", kind = "Foo", - singular = "foo", - plural = "foos", - namespaced, - crates(kube_core = ::kube::core) + crates(kube_core = ::kube::core, schemars = ::schemars) )] pub struct FooSpec { pub baz: bool, @@ -37,6 +34,14 @@ impl ::std::convert::From for v1beta1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1beta1 { use super::*; #[derive( @@ -51,13 +56,10 @@ pub mod v1beta1 { group = "foo.example.org", version = "v1beta1", kind = "Foo", - singular = "foo", - plural = "foos", - namespaced, - crates(kube_core = ::kube::core) + crates(kube_core = ::kube::core, schemars = ::schemars) )] pub struct FooSpec { - pub bah: u16, + pub bah: usize, pub baz: bool, } } @@ -71,6 +73,15 @@ impl ::std::convert::From for v1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bah: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1 { use super::*; #[derive( @@ -85,10 +96,7 @@ pub mod v1 { group = "foo.example.org", version = "v1", kind = "Foo", - singular = "foo", - plural = "foos", - namespaced, - crates(kube_core = ::kube::core) + crates(kube_core = ::kube::core, schemars = ::schemars) )] pub struct FooSpec { pub bar: usize, diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module.rs.snap index 05b547893..44fa16f86 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module.rs.snap @@ -57,6 +57,12 @@ impl ::std::convert::From for v1::Baz { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Baz { + fn from(__sv_baz: v1::Baz) -> Self { + Self { boom: __sv_baz.boom.into() } + } +} +#[automatically_derived] impl ::std::convert::From for v1::FooSpec { fn from(__sv_foospec: v1alpha1::FooSpec) -> Self { Self { @@ -67,6 +73,15 @@ impl ::std::convert::From for v1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bar.into(), + foo: __sv_foospec.foo.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v1::BarSpec { fn from(__sv_barspec: v1alpha1::BarSpec) -> Self { Self { @@ -75,6 +90,14 @@ impl ::std::convert::From for v1::BarSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::BarSpec { + fn from(__sv_barspec: v1::BarSpec) -> Self { + Self { + baz: __sv_barspec.baz.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v1::Boom { fn from(__sv_boom: v1alpha1::Boom) -> Self { match __sv_boom { @@ -84,6 +107,15 @@ impl ::std::convert::From for v1::Boom { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::Boom { + fn from(__sv_boom: v1::Boom) -> Self { + match __sv_boom { + v1::Boom::Big => v1alpha1::Boom::Big, + v1::Boom::Shaq => v1alpha1::Boom::Shaq, + } + } +} +#[automatically_derived] pub(crate) mod v1 { use super::*; pub struct Baz { @@ -133,6 +165,12 @@ impl ::std::convert::From for v2alpha1::Baz { } } #[automatically_derived] +impl ::std::convert::From for v1::Baz { + fn from(__sv_baz: v2alpha1::Baz) -> Self { + Self { boom: __sv_baz.boom.into() } + } +} +#[automatically_derived] #[allow(deprecated)] impl ::std::convert::From for v2alpha1::FooSpec { fn from(__sv_foospec: v1::FooSpec) -> Self { @@ -144,6 +182,17 @@ impl ::std::convert::From for v2alpha1::FooSpec { } } #[automatically_derived] +#[allow(deprecated)] +impl ::std::convert::From for v1::FooSpec { + fn from(__sv_foospec: v2alpha1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + foo: __sv_foospec.deprecated_foo.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v2alpha1::BarSpec { fn from(__sv_barspec: v1::BarSpec) -> Self { Self { @@ -152,6 +201,14 @@ impl ::std::convert::From for v2alpha1::BarSpec { } } #[automatically_derived] +impl ::std::convert::From for v1::BarSpec { + fn from(__sv_barspec: v2alpha1::BarSpec) -> Self { + Self { + baz: __sv_barspec.baz.into(), + } + } +} +#[automatically_derived] impl ::std::convert::From for v2alpha1::Boom { fn from(__sv_boom: v1::Boom) -> Self { match __sv_boom { @@ -161,6 +218,15 @@ impl ::std::convert::From for v2alpha1::Boom { } } #[automatically_derived] +impl ::std::convert::From for v1::Boom { + fn from(__sv_boom: v2alpha1::Boom) -> Self { + match __sv_boom { + v2alpha1::Boom::Big => v1::Boom::Big, + v2alpha1::Boom::Shaq => v1::Boom::Shaq, + } + } +} +#[automatically_derived] pub(crate) mod v2alpha1 { use super::*; pub struct Baz { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module_preserve.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module_preserve.rs.snap index ed18728fd..5fe460b51 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@module_preserve.rs.snap @@ -56,6 +56,11 @@ pub(crate) mod versioned { Self { boom: __sv_baz.boom.into() } } } + impl ::std::convert::From for v1alpha1::Baz { + fn from(__sv_baz: v1::Baz) -> Self { + Self { boom: __sv_baz.boom.into() } + } + } impl ::std::convert::From for v1::FooSpec { fn from(__sv_foospec: v1alpha1::FooSpec) -> Self { Self { @@ -65,6 +70,14 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bar.into(), + foo: __sv_foospec.foo.into(), + } + } + } impl ::std::convert::From for v1::BarSpec { fn from(__sv_barspec: v1alpha1::BarSpec) -> Self { Self { @@ -72,6 +85,13 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1alpha1::BarSpec { + fn from(__sv_barspec: v1::BarSpec) -> Self { + Self { + baz: __sv_barspec.baz.into(), + } + } + } impl ::std::convert::From for v1::Boom { fn from(__sv_boom: v1alpha1::Boom) -> Self { match __sv_boom { @@ -80,6 +100,14 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1alpha1::Boom { + fn from(__sv_boom: v1::Boom) -> Self { + match __sv_boom { + v1::Boom::Big => v1alpha1::Boom::Big, + v1::Boom::Shaq => v1alpha1::Boom::Shaq, + } + } + } pub mod v1 { use super::*; pub struct Baz { @@ -127,6 +155,11 @@ pub(crate) mod versioned { Self { boom: __sv_baz.boom.into() } } } + impl ::std::convert::From for v1::Baz { + fn from(__sv_baz: v2alpha1::Baz) -> Self { + Self { boom: __sv_baz.boom.into() } + } + } #[allow(deprecated)] impl ::std::convert::From for v2alpha1::FooSpec { fn from(__sv_foospec: v1::FooSpec) -> Self { @@ -137,6 +170,16 @@ pub(crate) mod versioned { } } } + #[allow(deprecated)] + impl ::std::convert::From for v1::FooSpec { + fn from(__sv_foospec: v2alpha1::FooSpec) -> Self { + Self { + bar: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + foo: __sv_foospec.deprecated_foo.into(), + } + } + } impl ::std::convert::From for v2alpha1::BarSpec { fn from(__sv_barspec: v1::BarSpec) -> Self { Self { @@ -144,6 +187,13 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1::BarSpec { + fn from(__sv_barspec: v2alpha1::BarSpec) -> Self { + Self { + baz: __sv_barspec.baz.into(), + } + } + } impl ::std::convert::From for v2alpha1::Boom { fn from(__sv_boom: v1::Boom) -> Self { match __sv_boom { @@ -152,6 +202,14 @@ pub(crate) mod versioned { } } } + impl ::std::convert::From for v1::Boom { + fn from(__sv_boom: v2alpha1::Boom) -> Self { + match __sv_boom { + v2alpha1::Boom::Big => v1::Boom::Big, + v2alpha1::Boom::Shaq => v1::Boom::Shaq, + } + } + } pub mod v2alpha1 { use super::*; pub struct Baz { diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@renamed_kind.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@renamed_kind.rs.snap index 60f5201f8..ea38693a9 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@renamed_kind.rs.snap @@ -14,14 +14,7 @@ pub mod v1alpha1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1alpha1", - kind = "FooBar", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1alpha1", kind = "FooBar", namespaced)] pub struct FooSpec { pub baz: bool, } @@ -36,6 +29,14 @@ impl ::std::convert::From for v1beta1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1beta1 { use super::*; #[derive( @@ -46,16 +47,9 @@ pub mod v1beta1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1beta1", - kind = "FooBar", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1beta1", kind = "FooBar", namespaced)] pub struct FooSpec { - pub bah: u16, + pub bah: usize, pub baz: bool, } } @@ -69,6 +63,15 @@ impl ::std::convert::From for v1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bah: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1 { use super::*; #[derive( @@ -79,14 +82,7 @@ pub mod v1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1", - kind = "FooBar", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1", kind = "FooBar", namespaced)] pub struct FooSpec { pub bar: usize, pub baz: bool, diff --git a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@skip.rs.snap b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@skip.rs.snap index c78bc3cc4..84bcd909b 100644 --- a/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@skip.rs.snap +++ b/crates/stackable-versioned-macros/tests/snapshots/stackable_versioned_macros__snapshot_tests__k8s@skip.rs.snap @@ -14,14 +14,7 @@ pub mod v1alpha1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1alpha1", - kind = "Foo", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1alpha1", kind = "Foo")] pub struct FooSpec { pub baz: bool, } @@ -36,6 +29,14 @@ impl ::std::convert::From for v1beta1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1alpha1::FooSpec { + fn from(__sv_foospec: v1beta1::FooSpec) -> Self { + Self { + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1beta1 { use super::*; #[derive( @@ -46,16 +47,9 @@ pub mod v1beta1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1beta1", - kind = "Foo", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1beta1", kind = "Foo")] pub struct FooSpec { - pub bah: u16, + pub bah: usize, pub baz: bool, } } @@ -69,6 +63,15 @@ impl ::std::convert::From for v1::FooSpec { } } #[automatically_derived] +impl ::std::convert::From for v1beta1::FooSpec { + fn from(__sv_foospec: v1::FooSpec) -> Self { + Self { + bah: __sv_foospec.bar.into(), + baz: __sv_foospec.baz.into(), + } + } +} +#[automatically_derived] pub mod v1 { use super::*; #[derive( @@ -79,14 +82,7 @@ pub mod v1 { schemars::JsonSchema, kube::CustomResource, )] - #[kube( - group = "stackable.tech", - version = "v1", - kind = "Foo", - singular = "foo", - plural = "foos", - namespaced - )] + #[kube(group = "stackable.tech", version = "v1", kind = "Foo")] pub struct FooSpec { pub bar: usize, pub baz: bool, diff --git a/crates/stackable-versioned-macros/tests/trybuild.rs b/crates/stackable-versioned-macros/tests/trybuild.rs index 891cc6023..ff26900a7 100644 --- a/crates/stackable-versioned-macros/tests/trybuild.rs +++ b/crates/stackable-versioned-macros/tests/trybuild.rs @@ -21,7 +21,7 @@ mod inputs { // mod attribute_enum; // mod attribute_struct; // mod basic_struct; - // mod convert_with; + // mod downgrade_with; // mod deprecate_enum; // mod deprecate_struct; // mod enum_data_simple; @@ -50,6 +50,7 @@ mod inputs { mod k8s { mod pass { // mod basic; + // mod conversion_tracking; // mod crate_overrides; // mod module; // mod module_preserve; @@ -76,4 +77,5 @@ fn default() { fn k8s() { let t = trybuild::TestCases::new(); t.compile_fail("tests/inputs/k8s/fail/*.rs"); + t.pass("tests/inputs/k8s/pass/*.rs"); } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index fcc86810c..47db554de 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -6,16 +6,41 @@ All notable changes to this project will be documented in this file. ### Added +- Implement basic ground work for downgrading custom resources ([#1033]). + - Emit `From` implementations to downgrade custom resource specs. + - Emit a status struct to be able to track values required during downgrades and upgrades of + custom resources. The generation of code for this feature is opt-in and must be enabled by + adding the `k8s(options(experimental_conversion_tracking))` flag. + - Add `versioned` crate override to `k8s(crates())` to specify a custom import path. This override + will not be passed to the `#[kube()]` attribute, but will only be available to internal + `#[versioned]` macro code. - Add `kube_client` crate override to `k8s(crates())` to specify a custom import path. This override will not be passed to the `#[kube()]` attribute, but will only be available to internal `#[versioned]` macro code ([#1038]). +### Changed + +- BREAKING: The `convert_with` parameter of the `changed()` action was renamed and split into two + parts to be able to control the conversion during upgrades and downgrades: `upgrade_with` and + `downgrade_with` ([#1033]). + ### Fixed - Correctly handle fields added in later versions ([#1031]). +### Removed + +- BREAKING: Remove unused `AsVersionStr` trait ([#1033]). + +### Miscellaneous + +- Fix and add snapshot/compile tests for Kubernetes-specific features ([#1033]). +- Combine snapshot and compile tests ([#1041]). + [#1031]: https://github.com/stackabletech/operator-rs/pull/1031 +[#1033]: https://github.com/stackabletech/operator-rs/pull/1033 [#1038]: https://github.com/stackabletech/operator-rs/pull/1038 +[#1041]: https://github.com/stackabletech/operator-rs/pull/1041 ## [0.7.1] - 2025-04-02 diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index 9f4327f31..b5dc5218b 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -14,7 +14,17 @@ all-features = true full = ["k8s"] k8s = [ "stackable-versioned-macros/k8s", # Forward the k8s feature to the underlying macro crate + "dep:k8s-version", + "dep:serde_json", + "dep:serde_yaml", + "dep:schemars", + "dep:serde", ] [dependencies] +k8s-version = { path = "../k8s-version", features = ["serde"], optional = true } stackable-versioned-macros = { path = "../stackable-versioned-macros" } +schemars = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +serde_yaml = { workspace = true, optional = true } diff --git a/crates/stackable-versioned/src/k8s.rs b/crates/stackable-versioned/src/k8s.rs new file mode 100644 index 000000000..b0cda13dc --- /dev/null +++ b/crates/stackable-versioned/src/k8s.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use k8s_version::Version; +use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec}; + +// NOTE (@Techassi): This struct represents a rough first draft of how tracking values across +// CRD versions can be achieved. It is currently untested and unproven and might change down the +// line. Currently, this struct is only generated by the macro but not actually used by any other +// code. The tracking itself will be introduced in a follow-up PR. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] +pub struct ChangedValues { + /// List of values needed when downgrading to a particular version. + pub downgrades: HashMap>, + + /// List of values needed when upgrading to a particular version. + pub upgrades: HashMap>, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] +pub struct ChangedValue { + /// The name of the field of the custom resource this value is for. + pub name: String, + + /// The value to be used when upgrading or downgrading the custom resource. + #[schemars(schema_with = "raw_object_schema")] + pub value: serde_yaml::Value, +} + +// TODO (@Techassi): Think about where this should live. Basically this already exists in +// stackable-operator, but we cannot use it without depending on it which I would like to +// avoid. +fn raw_object_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema { + Schema::Object(SchemaObject { + instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), + extensions: [( + "x-kubernetes-preserve-unknown-fields".to_owned(), + serde_json::Value::Bool(true), + )] + .into(), + ..Default::default() + }) +} diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 8c0c399b1..85edf234e 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -13,14 +13,9 @@ //! parameters. // Re-export macro -pub use stackable_versioned_macros::*; +#[cfg(feature = "k8s")] +pub use k8s::*; +pub use stackable_versioned_macros::versioned; -// Unused for now, might get picked up again in the future. -#[doc(hidden)] -pub trait AsVersionStr { - const VERSION: &'static str; - - fn as_version_str(&self) -> &'static str { - Self::VERSION - } -} +#[cfg(feature = "k8s")] +mod k8s;