Skip to content

feat(stackable-versioned): Add conversion tracking #1056

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"rust-analyzer.rustfmt.overrideCommand": [
"rustfmt",
"+nightly-2025-05-26",
"--edition",
"2024",
"--"
]
}
171 changes: 61 additions & 110 deletions crates/stackable-versioned-macros/src/codegen/container/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
codegen::{
ItemStatus, StandaloneContainerAttributes, VersionDefinition,
changes::Neighbors,
container::{CommonContainerData, Container, ContainerIdents, ContainerOptions},
container::{CommonContainerData, Container, ContainerIdents, ContainerOptions, Direction},
item::VersionedVariant,
},
};
Expand Down Expand Up @@ -122,9 +122,10 @@ impl Enum {
}
}

/// Generates code for the `From<Version> for NextVersion` implementation.
pub fn generate_upgrade_from_impl(
// TODO (@Techassi): Add doc comments
pub fn generate_from_impl(
&self,
direction: Direction,
version: &VersionDefinition,
next_version: Option<&VersionDefinition>,
add_attributes: bool,
Expand All @@ -133,119 +134,69 @@ impl Enum {
return None;
}

match next_version {
Some(next_version) => {
// TODO (@Techassi): Support generic types which have been removed in newer versions,
// but need to exist for older versions How do we represent that? Because the
// defined struct always represents the latest version. I guess we could generally
// advise against using generic types, but if you have to, avoid removing it in
// later versions.
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
let from_enum_ident = &self.common.idents.parameter;
let enum_ident = &self.common.idents.original;

let for_module_ident = &next_version.idents.module;
let from_module_ident = &version.idents.module;

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
// 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,
}
}

pub fn generate_downgrade_from_impl(
&self,
version: &VersionDefinition,
next_version: Option<&VersionDefinition>,
add_attributes: bool,
) -> Option<TokenStream> {
if version.skip_from || self.common.options.skip_from {
return None;
}

match next_version {
Some(next_version) => {
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
let from_enum_ident = &self.common.idents.parameter;
let enum_ident = &self.common.idents.original;

let from_module_ident = &next_version.idents.module;
let for_module_ident = &version.idents.module;

let variants: TokenStream = self
.variants
next_version.map(|next_version| {
// TODO (@Techassi): Support generic types which have been removed in newer versions,
// but need to exist for older versions How do we represent that? Because the
// defined struct always represents the latest version. I guess we could generally
// advise against using generic types, but if you have to, avoid removing it in
// later versions.
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
let from_enum_ident = &self.common.idents.parameter;
let enum_ident = &self.common.idents.original;

// 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]});

let variants = |direction: Direction| -> TokenStream {
self.variants
.iter()
.filter_map(|v| {
v.generate_for_downgrade_from_impl(version, next_version, enum_ident)
v.generate_for_from_impl(direction, 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
}
.collect()
};

let (variants, for_module_ident, from_module_ident) = match direction {
Direction::Upgrade => {
let for_module_ident = &next_version.idents.module;
let from_module_ident = &version.idents.module;

(variants(Direction::Upgrade), for_module_ident, from_module_ident)
},
Direction::Downgrade => {
let for_module_ident = &version.idents.module;
let from_module_ident = &next_version.idents.module;

(variants(Direction::Downgrade), for_module_ident, from_module_ident)
},
};

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`.
Expand Down
43 changes: 18 additions & 25 deletions crates/stackable-versioned-macros/src/codegen/container/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,20 @@ impl Container {
}
}

/// Generates the `From<Version> for NextVersion` implementation for the container.
pub fn generate_upgrade_from_impl(
pub fn generate_from_impl(
&self,
direction: Direction,
version: &VersionDefinition,
next_version: Option<&VersionDefinition>,
add_attributes: bool,
) -> Option<TokenStream> {
match self {
Container::Struct(s) => {
s.generate_upgrade_from_impl(version, next_version, add_attributes)
// TODO (@Techassi): Decide here (based on K8s args) what we want to generate
s.generate_from_impl(direction, version, next_version, add_attributes)
}
Container::Enum(e) => {
e.generate_upgrade_from_impl(version, next_version, add_attributes)
}
}
}

/// Generates the `From<NextVersion> for Version` implementation for the container.
pub fn generate_downgrade_from_impl(
&self,
version: &VersionDefinition,
next_version: Option<&VersionDefinition>,
add_attributes: bool,
) -> Option<TokenStream> {
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)
e.generate_from_impl(direction, version, next_version, add_attributes)
}
}
}
Expand Down Expand Up @@ -183,12 +167,15 @@ impl StandaloneContainer {
// Generate the From impl for upgrading the CRD.
let upgrade_from_impl =
self.container
.generate_upgrade_from_impl(version, next_version, false);
.generate_from_impl(Direction::Upgrade, version, next_version, false);

// Generate the From impl for downgrading the CRD.
let downgrade_from_impl =
self.container
.generate_downgrade_from_impl(version, next_version, false);
let downgrade_from_impl = self.container.generate_from_impl(
Direction::Downgrade,
version,
next_version,
false,
);

// Add the #[deprecated] attribute when the version is marked as deprecated.
let deprecated_attribute = version
Expand Down Expand Up @@ -287,3 +274,9 @@ pub struct ContainerOptions {
pub kubernetes_arguments: Option<KubernetesArguments>,
pub skip_from: bool,
}

#[derive(Copy, Clone, Debug)]
pub enum Direction {
Upgrade,
Downgrade,
}
Loading