Skip to content

Commit bc58ff9

Browse files
authored
feat(stackable-versioned): Generate downgrade From impls (#1033)
* feat(stackable-versioned): Generate downgrade From impls * feat(stackable-versioned): Generate primitive Status struct * feat(stackable-versioned): Emit status struct * chore: Fix clippy lints * fix(stackable-versioned): Emit correct code for modules * test(stackable-versioned): Update snapshot tests * test(stackable-versioned): Update doc tests * chore(stackable-versioned): Update changelog * chore(stackable-versioned): Remove unused dev dependencies * chore(stackable-versioned): Adjust {upgrade,downgrade}_with validation * chore(stackable-versioned): Improve K8s code generation * chore(stackable-versioned): Change crd_values to changed_values * test(stackable-versioned): Update snapshot tests * chore(stackable-versioned): Clean up changelog * test(stackable-versioned): Update 'added' snapshot test * test(stackable-versioned): Fix and comment previously untested code * docs(stackable-versioned): Update 'convert_with' doc comment * test(stackable-versioned): Rename convert_with test to downgrade_with * chore(stackable-versioned): Add disclaimer comment * refactor(stackable-versioned): Make conversion tracking opt in * refactor(stackable-versioned): Move status struct behind feature flag * test(stackable-versioned): Add passing Kubernetes compile tests * fix(stackable-versioned): Use correct module name * chore(stackable-versioned): Update status struct name to avoid collisions * chore(stackable-versioned): Update changelog * test(stackable-versioned): Update status struct name
1 parent 65a6de5 commit bc58ff9

File tree

51 files changed

+1477
-382
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1477
-382
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/k8s-version/src/api_version/serde.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for ApiVersion {
1111
{
1212
struct ApiVersionVisitor;
1313

14-
impl<'de> Visitor<'de> for ApiVersionVisitor {
14+
impl Visitor<'_> for ApiVersionVisitor {
1515
type Value = ApiVersion;
1616

1717
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {

crates/k8s-version/src/level/serde.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for Level {
1111
{
1212
struct LevelVisitor;
1313

14-
impl<'de> Visitor<'de> for LevelVisitor {
14+
impl Visitor<'_> for LevelVisitor {
1515
type Value = Level;
1616

1717
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {

crates/k8s-version/src/version/serde.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ impl<'de> Deserialize<'de> for Version {
1111
{
1212
struct VersionVisitor;
1313

14-
impl<'de> Visitor<'de> for VersionVisitor {
14+
impl Visitor<'_> for VersionVisitor {
1515
type Value = Version;
1616

1717
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {

crates/stackable-versioned-macros/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ quote.workspace = true
4343
[dev-dependencies]
4444
# Only needed for doc tests / examples
4545
stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] }
46-
k8s-openapi.workspace = true
4746

4847
insta.workspace = true
4948
prettyplease.workspace = true

crates/stackable-versioned-macros/src/attrs/item/mod.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,26 @@ impl CommonItemAttributes {
238238
}
239239
}
240240

241-
// The convert_with argument only makes sense to use when the
242-
// type changed
243-
if let Some(convert_func) = change.convert_with.as_ref() {
244-
if change.from_type.is_none() {
241+
if change.from_type.is_none() {
242+
// The upgrade_with argument only makes sense to use when the
243+
// type changed
244+
if let Some(upgrade_func) = change.upgrade_with.as_ref() {
245245
errors.push(
246246
Error::custom(
247-
"the `convert_with` argument must be used in combination with `from_type`",
247+
"the `upgrade_with` argument must be used in combination with `from_type`",
248248
)
249-
.with_span(&convert_func.span()),
249+
.with_span(&upgrade_func.span()),
250+
);
251+
}
252+
253+
// The downgrade_with argument only makes sense to use when the
254+
// type changed
255+
if let Some(downgrade_func) = change.downgrade_with.as_ref() {
256+
errors.push(
257+
Error::custom(
258+
"the `downgrade_with` argument must be used in combination with `from_type`",
259+
)
260+
.with_span(&downgrade_func.span()),
250261
);
251262
}
252263
}
@@ -315,7 +326,8 @@ impl CommonItemAttributes {
315326
.unwrap_or(ty.clone());
316327

317328
actions.insert(*change.since, ItemStatus::Change {
318-
convert_with: change.convert_with.as_deref().cloned(),
329+
downgrade_with: change.downgrade_with.as_deref().cloned(),
330+
upgrade_with: change.upgrade_with.as_deref().cloned(),
319331
from_ident: from_ident.clone(),
320332
from_type: from_ty.clone(),
321333
to_ident: ident,
@@ -359,7 +371,8 @@ impl CommonItemAttributes {
359371
.unwrap_or(ty.clone());
360372

361373
actions.insert(*change.since, ItemStatus::Change {
362-
convert_with: change.convert_with.as_deref().cloned(),
374+
downgrade_with: change.downgrade_with.as_deref().cloned(),
375+
upgrade_with: change.upgrade_with.as_deref().cloned(),
363376
from_ident: from_ident.clone(),
364377
from_type: from_ty.clone(),
365378
to_ident: ident,
@@ -429,13 +442,15 @@ fn default_default_fn() -> SpannedValue<Path> {
429442
/// Example usage:
430443
/// - `changed(since = "...", from_name = "...")`
431444
/// - `changed(since = "...", from_name = "...", from_type="...")`
432-
/// - `changed(since = "...", from_name = "...", from_type="...", convert_with = "...")`
445+
/// - `changed(since = "...", from_name = "...", from_type="...", upgrade_with = "...")`
446+
/// - `changed(since = "...", from_name = "...", from_type="...", downgrade_with = "...")`
433447
#[derive(Clone, Debug, FromMeta)]
434448
pub struct ChangedAttributes {
435449
pub since: SpannedValue<Version>,
436450
pub from_name: Option<SpannedValue<String>>,
437451
pub from_type: Option<SpannedValue<Type>>,
438-
pub convert_with: Option<SpannedValue<Path>>,
452+
pub upgrade_with: Option<SpannedValue<Path>>,
453+
pub downgrade_with: Option<SpannedValue<Path>>,
439454
}
440455

441456
/// For the deprecated() action

crates/stackable-versioned-macros/src/attrs/k8s.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,30 @@ use syn::Path;
2222
/// times.
2323
/// - `skip`: Controls skipping parts of the generation.
2424
#[derive(Clone, Debug, FromMeta)]
25-
pub(crate) struct KubernetesArguments {
26-
pub(crate) group: String,
27-
pub(crate) kind: Option<String>,
28-
pub(crate) singular: Option<String>,
29-
pub(crate) plural: Option<String>,
30-
pub(crate) namespaced: Flag,
25+
pub struct KubernetesArguments {
26+
pub group: String,
27+
pub kind: Option<String>,
28+
pub singular: Option<String>,
29+
pub plural: Option<String>,
30+
pub namespaced: Flag,
3131
// root
32-
pub(crate) crates: Option<KubernetesCrateArguments>,
33-
pub(crate) status: Option<Path>,
32+
pub crates: Option<KubernetesCrateArguments>,
33+
pub status: Option<Path>,
3434
// derive
3535
// schema
3636
// scale
3737
// printcolumn
3838
#[darling(multiple, rename = "shortname")]
39-
pub(crate) shortnames: Vec<String>,
39+
pub shortnames: Vec<String>,
4040
// category
4141
// selectable
4242
// doc
4343
// annotation
4444
// label
45-
pub(crate) skip: Option<KubernetesSkipArguments>,
45+
pub skip: Option<KubernetesSkipArguments>,
46+
47+
#[darling(default)]
48+
pub options: RawKubernetesOptions,
4649
}
4750

4851
/// This struct contains supported kubernetes skip arguments.
@@ -52,19 +55,25 @@ pub(crate) struct KubernetesArguments {
5255
/// - `merged_crd` flag, which skips generating the `crd()` and `merged_crd()` functions are
5356
/// generated.
5457
#[derive(Clone, Debug, FromMeta)]
55-
pub(crate) struct KubernetesSkipArguments {
58+
pub struct KubernetesSkipArguments {
5659
/// Whether the `crd()` and `merged_crd()` generation should be skipped for
5760
/// this container.
58-
pub(crate) merged_crd: Flag,
61+
pub merged_crd: Flag,
5962
}
6063

6164
/// This struct contains crate overrides to be passed to `#[kube]`.
6265
#[derive(Clone, Debug, FromMeta)]
63-
pub(crate) struct KubernetesCrateArguments {
64-
pub(crate) kube_core: Option<Path>,
65-
pub(crate) kube_client: Option<Path>,
66-
pub(crate) k8s_openapi: Option<Path>,
67-
pub(crate) schemars: Option<Path>,
68-
pub(crate) serde: Option<Path>,
69-
pub(crate) serde_json: Option<Path>,
66+
pub struct KubernetesCrateArguments {
67+
pub kube_core: Option<Path>,
68+
pub kube_client: Option<Path>,
69+
pub k8s_openapi: Option<Path>,
70+
pub schemars: Option<Path>,
71+
pub serde: Option<Path>,
72+
pub serde_json: Option<Path>,
73+
pub versioned: Option<Path>,
74+
}
75+
76+
#[derive(Clone, Default, Debug, FromMeta)]
77+
pub struct RawKubernetesOptions {
78+
pub experimental_conversion_tracking: Flag,
7079
}

crates/stackable-versioned-macros/src/codegen/container/enum.rs

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops::Not;
22

3-
use darling::{FromAttributes, Result, util::IdentString};
3+
use darling::{FromAttributes, Result};
44
use proc_macro2::TokenStream;
55
use quote::quote;
66
use syn::{Generics, ItemEnum};
@@ -126,11 +126,11 @@ impl Enum {
126126
}
127127

128128
/// Generates code for the `From<Version> for NextVersion` implementation.
129-
pub(crate) fn generate_from_impl(
129+
pub fn generate_upgrade_from_impl(
130130
&self,
131131
version: &VersionDefinition,
132132
next_version: Option<&VersionDefinition>,
133-
is_nested: bool,
133+
add_attributes: bool,
134134
) -> Option<TokenStream> {
135135
if version.skip_from || self.common.options.skip_from {
136136
return None;
@@ -145,12 +145,18 @@ impl Enum {
145145
// later versions.
146146
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
147147
let enum_ident = &self.common.idents.original;
148-
let from_ident = &self.common.idents.from;
148+
let from_enum_ident = &self.common.idents.from;
149149

150-
let next_version_ident = &next_version.ident;
151-
let version_ident = &version.ident;
150+
let for_module_ident = &next_version.ident;
151+
let from_module_ident = &version.ident;
152152

153-
let variants = self.generate_from_variants(version, next_version, enum_ident);
153+
let variants: TokenStream = self
154+
.variants
155+
.iter()
156+
.filter_map(|v| {
157+
v.generate_for_upgrade_from_impl(version, next_version, enum_ident)
158+
})
159+
.collect();
154160

155161
// Include allow(deprecated) only when this or the next version is
156162
// deprecated. Also include it, when a variant in this or the next
@@ -163,17 +169,18 @@ impl Enum {
163169

164170
// Only add the #[automatically_derived] attribute only if this impl is used
165171
// outside of a module (in standalone mode).
166-
let automatically_derived =
167-
is_nested.not().then(|| quote! {#[automatically_derived]});
172+
let automatically_derived = add_attributes
173+
.not()
174+
.then(|| quote! {#[automatically_derived]});
168175

169176
Some(quote! {
170177
#automatically_derived
171178
#allow_attribute
172-
impl #impl_generics ::std::convert::From<#version_ident::#enum_ident #type_generics> for #next_version_ident::#enum_ident #type_generics
179+
impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics
173180
#where_clause
174181
{
175-
fn from(#from_ident: #version_ident::#enum_ident #type_generics) -> Self {
176-
match #from_ident {
182+
fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self {
183+
match #from_enum_ident {
177184
#variants
178185
}
179186
}
@@ -184,20 +191,64 @@ impl Enum {
184191
}
185192
}
186193

187-
/// Generates code for enum variants used in `From` implementations.
188-
fn generate_from_variants(
194+
pub fn generate_downgrade_from_impl(
189195
&self,
190196
version: &VersionDefinition,
191-
next_version: &VersionDefinition,
192-
enum_ident: &IdentString,
193-
) -> TokenStream {
194-
let mut tokens = TokenStream::new();
195-
196-
for variant in &self.variants {
197-
tokens.extend(variant.generate_for_from_impl(version, next_version, enum_ident));
197+
next_version: Option<&VersionDefinition>,
198+
add_attributes: bool,
199+
) -> Option<TokenStream> {
200+
if version.skip_from || self.common.options.skip_from {
201+
return None;
198202
}
199203

200-
tokens
204+
match next_version {
205+
Some(next_version) => {
206+
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
207+
let enum_ident = &self.common.idents.original;
208+
let from_enum_ident = &self.common.idents.from;
209+
210+
let for_module_ident = &version.ident;
211+
let from_module_ident = &next_version.ident;
212+
213+
let variants: TokenStream = self
214+
.variants
215+
.iter()
216+
.filter_map(|v| {
217+
v.generate_for_downgrade_from_impl(version, next_version, enum_ident)
218+
})
219+
.collect();
220+
221+
// Include allow(deprecated) only when this or the next version is
222+
// deprecated. Also include it, when a variant in this or the next
223+
// version is deprecated.
224+
let allow_attribute = (version.deprecated.is_some()
225+
|| next_version.deprecated.is_some()
226+
|| self.is_any_variant_deprecated(version)
227+
|| self.is_any_variant_deprecated(next_version))
228+
.then_some(quote! { #[allow(deprecated)] });
229+
230+
// Only add the #[automatically_derived] attribute only if this impl is used
231+
// outside of a module (in standalone mode).
232+
let automatically_derived = add_attributes
233+
.not()
234+
.then(|| quote! {#[automatically_derived]});
235+
236+
Some(quote! {
237+
#automatically_derived
238+
#allow_attribute
239+
impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics
240+
#where_clause
241+
{
242+
fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self {
243+
match #from_enum_ident {
244+
#variants
245+
}
246+
}
247+
}
248+
})
249+
}
250+
None => None,
251+
}
201252
}
202253

203254
/// Returns whether any variant is deprecated in the provided `version`.

0 commit comments

Comments
 (0)