Skip to content

feat(stackable-versioned): Generate downgrade From impls #1033

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

Merged
merged 31 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c351c02
feat(stackable-versioned): Generate downgrade From impls
Techassi May 14, 2025
711aabe
feat(stackable-versioned): Generate primitive Status struct
Techassi May 14, 2025
92ed46c
feat(stackable-versioned): Emit status struct
Techassi May 15, 2025
857710f
chore: Fix clippy lints
Techassi May 16, 2025
14b09f2
fix(stackable-versioned): Emit correct code for modules
Techassi May 16, 2025
93570c6
test(stackable-versioned): Update snapshot tests
Techassi May 16, 2025
760a7bc
test(stackable-versioned): Update doc tests
Techassi May 16, 2025
3fc0b82
chore(stackable-versioned): Update changelog
Techassi May 16, 2025
bdf70e8
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 16, 2025
1048bae
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
6579eb0
chore(stackable-versioned): Remove unused dev dependencies
Techassi May 21, 2025
4f3a8d8
chore(stackable-versioned): Adjust {upgrade,downgrade}_with validation
Techassi May 21, 2025
c448ed2
chore(stackable-versioned): Improve K8s code generation
Techassi May 21, 2025
122e000
chore(stackable-versioned): Change crd_values to changed_values
Techassi May 21, 2025
0f7468b
test(stackable-versioned): Update snapshot tests
Techassi May 21, 2025
db47269
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
634b977
chore(stackable-versioned): Clean up changelog
Techassi May 21, 2025
7fd4808
test(stackable-versioned): Update 'added' snapshot test
Techassi May 21, 2025
c613404
test(stackable-versioned): Fix and comment previously untested code
Techassi May 21, 2025
ee8a0a6
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
8b095da
docs(stackable-versioned): Update 'convert_with' doc comment
Techassi May 22, 2025
229dcef
test(stackable-versioned): Rename convert_with test to downgrade_with
Techassi May 22, 2025
fd4d1ad
chore(stackable-versioned): Add disclaimer comment
Techassi May 22, 2025
436e294
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 22, 2025
abf5deb
refactor(stackable-versioned): Make conversion tracking opt in
Techassi May 22, 2025
1a0fcc3
refactor(stackable-versioned): Move status struct behind feature flag
Techassi May 22, 2025
44bb861
test(stackable-versioned): Add passing Kubernetes compile tests
Techassi May 22, 2025
69ee88c
fix(stackable-versioned): Use correct module name
Techassi May 22, 2025
e38161b
chore(stackable-versioned): Update status struct name to avoid collis…
Techassi May 22, 2025
1fdc2aa
chore(stackable-versioned): Update changelog
Techassi May 22, 2025
46da1d6
test(stackable-versioned): Update status struct name
Techassi May 22, 2025
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
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/k8s-version/src/api_version/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/k8s-version/src/level/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/k8s-version/src/version/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion crates/stackable-versioned-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 23 additions & 9 deletions crates/stackable-versioned-macros/src/attrs/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
);
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -435,7 +448,8 @@ pub struct ChangedAttributes {
pub since: SpannedValue<Version>,
pub from_name: Option<SpannedValue<String>>,
pub from_type: Option<SpannedValue<Type>>,
pub convert_with: Option<SpannedValue<Path>>,
pub upgrade_with: Option<SpannedValue<Path>>,
pub downgrade_with: Option<SpannedValue<Path>>,
}

/// For the deprecated() action
Expand Down
13 changes: 7 additions & 6 deletions crates/stackable-versioned-macros/src/attrs/k8s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ pub(crate) struct KubernetesSkipArguments {
/// This struct contains crate overrides to be passed to `#[kube]`.
#[derive(Clone, Debug, FromMeta)]
pub(crate) struct KubernetesCrateArguments {
pub(crate) kube_core: Option<Path>,
pub(crate) kube_client: Option<Path>,
pub(crate) k8s_openapi: Option<Path>,
pub(crate) schemars: Option<Path>,
pub(crate) serde: Option<Path>,
pub(crate) serde_json: Option<Path>,
pub kube_core: Option<Path>,
pub kube_client: Option<Path>,
pub k8s_openapi: Option<Path>,
pub schemars: Option<Path>,
pub serde: Option<Path>,
pub serde_json: Option<Path>,
pub versioned: Option<Path>,
}
95 changes: 73 additions & 22 deletions crates/stackable-versioned-macros/src/codegen/container/enum.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -126,11 +126,11 @@ impl Enum {
}

/// Generates code for the `From<Version> 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<TokenStream> {
if version.skip_from || self.common.options.skip_from {
return None;
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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<TokenStream> {
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`.
Expand Down
Loading
Loading