stackabletech / operator-rs

A simple wrapper/framework around kube-rs to make implementing Operators/Controllers easier
Apache License 2.0
112 stars 11 forks source link

feat(stackable-versioned): Generate From implementations for automatic versions upgrades #790

Closed Techassi closed 1 month ago

Techassi commented 2 months ago

Description

Tracked by https://github.com/stackabletech/issues/issues/507, follow-up of #764, ~blocked by #793~

This PR adds code generation for From<OLD> for NEW implementations to enable conversion between versions. The macro also allows to implement the From trait by hand if customization is required.

Additionally, it adds support to customize the default function (used in the automatically generated From impl) for added fields.

Examples

Generated From implementations

#[versioned(
    version(name = "v1alpha1"),
    version(name = "v1beta1"),
    version(name = "v1")
)]
pub struct Foo {
    #[versioned(
        added(since = "v1beta1"),
        deprecated(since = "v1", note = "not needed")
    )]
    deprecated_bar: usize,
    baz: bool,
}

// Produces ...

#[automatically_derived]
pub mod v1alpha1 {
    pub struct Foo {
        pub baz: bool,
    }
}
#[automatically_derived]
#[allow(deprecated)]
impl From<v1alpha1::Foo> for v1beta1::Foo {
    fn from(__sv_foo: v1alpha1::Foo) -> Self {
        Self {
            bar: std::default::Default::default(),
            baz: __sv_foo.baz,
        }
    }
}
#[automatically_derived]
pub mod v1beta1 {
    pub struct Foo {
        pub bar: usize,
        pub baz: bool,
    }
}
#[automatically_derived]
#[allow(deprecated)]
impl From<v1beta1::Foo> for v1::Foo {
    fn from(__sv_foo: v1beta1::Foo) -> Self {
        Self {
            deprecated_bar: __sv_foo.bar,
            baz: __sv_foo.baz,
        }
    }
}
#[automatically_derived]
pub mod v1 {
    pub struct Foo {
        #[deprecated = "not needed"]
        pub deprecated_bar: usize,
        pub baz: bool,
    }
}

// Which can be used like this ...

let foo_v1alpha1 = v1alpha1::Foo { baz: true };
let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1);
let foo_v1 = v1::Foo::from(foo_v1beta1);

assert_eq!(foo_v1.deprecated_bar, 0);
assert!(foo_v1.baz);

Skip generating From implementations

The generation can be skipped at the container level (no implementations are generated) or at the version level (only the generation from that to the next version is skipped).

#[versioned(
    version(name = "v1alpha1"),
    version(name = "v1beta1"),
    version(name = "v1"),
    options(skip(from))
)]
pub struct Foo {
    #[versioned(
        added(since = "v1beta1"),
        deprecated(since = "v1", note = "not needed")
    )]
    deprecated_bar: usize,
    baz: bool,
}

// Or ...

#[versioned(
    version(name = "v1alpha1"),
    version(name = "v1beta1", skip(from)),
    version(name = "v1")
)]
pub struct Foo {
    #[versioned(
        added(since = "v1beta1"),
        deprecated(since = "v1", note = "not needed")
    )]
    deprecated_bar: usize,
    baz: bool,
}

Custom default function for added fields

#[versioned(
    version(name = "v1alpha1"),
    version(name = "v1beta1"),
    version(name = "v1")
)]
pub struct Foo {
    #[versioned(
        added(since = "v1beta1", default = "default_bar"),
        deprecated(since = "v1", note = "not needed")
    )]
    deprecated_bar: usize,
    baz: bool,
}

// Can be used like this ...

fn default_bar() -> usize {
    42
}

let foo_v1alpha1 = v1alpha1::Foo { baz: true };
let foo_v1beta1 = v1beta1::Foo::from(foo_v1alpha1);

assert_eq!(foo_v1beta1.bar, 42);
assert!(foo_v1beta1.baz);
# Reviewer
- [ ] Code contains useful comments
- [ ] (Integration-)Test cases added
- [ ] Documentation added or updated
- [ ] Changelog updated
- [ ] Cargo.toml only contains references to git tags (not specific commits or branches)