diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index 4bca2874..97cff851 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -43,7 +43,7 @@ quote.workspace = true [dev-dependencies] # Only needed for doc tests / examples stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] } -k8s-openapi = { workspace = true } +k8s-openapi.workspace = true insta.workspace = true prettyplease.workspace = true diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 0725fcbe..b28e7a43 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -33,8 +33,8 @@ mod utils; /// /// It is **important** to note that this macro must be placed before any other /// (derive) macros and attributes. Macros supplied before the versioned macro -/// will be erased, because the original struct or enum (container) is erased, -/// and new containers are generated. This ensures that the macros and +/// will be erased, because the original struct, enum or module (container) is +/// erased, and new containers are generated. This ensures that the macros and /// attributes are applied to the generated versioned instances of the /// container. /// @@ -131,6 +131,133 @@ mod utils; /// } /// ``` /// +/// ## Versioning Items in a Module +/// +/// Using the macro on structs and enums is explained in detail in the following +/// sections. This section is dedicated to explain the usage of the macro when +/// applied to a module. +/// +/// Using the macro on a module has one clear use-case: Versioning multiple +/// structs and enums at once in **a single file**. Applying the `#[versioned]` +/// macro to individual containers will result in invalid Rust code which the +/// compiler rejects. This behaviour can best be explained using the following +/// example: +/// +/// ```ignore +/// # use stackable_versioned_macros::versioned; +/// #[versioned(version(name = "v1alpha1"))] +/// struct Foo {} +/// +/// #[versioned(version(name = "v1alpha1"))] +/// struct Bar {} +/// ``` +/// +/// In this example, two different structs are versioned using the same version, +/// `v1alpha1`. Each macro will now (independently) expand into versioned code. +/// This will result in the module named `v1alpha1` to be emitted twice, in the +/// same file. This is invalid Rust code. You cannot define the same module more +/// than once in the same file. +/// +///
+/// Expand Generated Invalid Code +/// +/// ```ignore +/// mod v1alpha1 { +/// struct Foo {} +/// } +/// +/// mod v1alpha1 { +/// struct Bar {} +/// } +/// ``` +///
+/// +/// This behaviour makes it impossible to version multiple containers in the +/// same file. The only solution would be to put each container into its own +/// file which in many cases is not needed or even undesired. To solve this +/// issue, it is thus possible to apply the macro to a module. +/// +/// ``` +/// # use stackable_versioned_macros::versioned; +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1") +/// )] +/// mod versioned { +/// struct Foo { +/// bar: usize, +/// } +/// +/// struct Bar { +/// baz: String, +/// } +/// } +/// ``` +/// +///
+/// Expand Generated Code +/// +/// 1. All containers defined in the module will get versioned. That's why every +/// version module includes all containers. +/// 2. Each version will expand to a version module, as expected. +/// +/// ```ignore +/// mod v1alpha1 { +/// use super::*; +/// pub struct Foo { // 1 +/// bar: usize, +/// } +/// pub struct Bar { // 1 +/// baz: String, +/// } +/// } +/// +/// mod v1 { // 2 +/// use super::*; +/// pub struct Foo { +/// bar: usize, +/// } +/// pub struct Bar { +/// baz: String, +/// } +/// } +/// ``` +///
+/// +/// It should be noted that versions are now defined at the module level and +/// **not** at the struct / enum level. Item actions describes in the following +/// section can be used as expected. +/// +/// ### Preserve Module +/// +/// The previous examples completely replaced the `versioned` module with +/// top-level version modules. This is the default behaviour. Preserving the +/// module can however be enabled by setting the `preserve_module` flag. +/// +/// ``` +/// # use stackable_versioned_macros::versioned; +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1"), +/// preserve_module +/// )] +/// mod versioned { +/// struct Foo { +/// bar: usize, +/// } +/// +/// struct Bar { +/// baz: String, +/// } +/// } +/// ``` +/// +///
+/// It is planned to move the preserve_module flag into the +/// options() argument list, but currently seems tricky to +/// implement. +///
+/// /// ## Item Actions /// /// This crate currently supports three different item actions. Items can @@ -176,7 +303,7 @@ mod utils; /// ``` /// ///
-/// Generated code +/// Expand Generated Code /// /// 1. The field `bar` is not yet present in version `v1alpha1` and is therefore /// not generated. @@ -235,7 +362,7 @@ mod utils; /// ``` /// ///
-/// Generated code +/// Expand Generated Code /// /// 1. Instead of `Default::default()`, the provided function `default_bar()` is /// used. It is of course fully type checked and needs to return the expected @@ -285,7 +412,7 @@ mod utils; /// ``` /// ///
-/// Generated code +/// Expand Generated Code /// /// 1. In version `v1alpha1` the field is named `prev_bar` and uses a `u16`. /// 2. In the next version, `v1beta1`, the field is now named `bar` and uses @@ -336,7 +463,7 @@ mod utils; /// ``` /// ///
-/// Generated code +/// Expand Generated Code /// /// 1. In version `v1alpha1` the field `bar` is not yet deprecated and thus uses /// the name without the `deprecated_` prefix. @@ -486,6 +613,103 @@ Currently, the following arguments are supported: - `crates`: Override specific crates. - `status`: Sets the specified struct as the status subresource. - `shortname`: Sets the shortname of the CRD. + +### Versioning Items in a Module + +Versioning multiple CRD related structs via a module is supported and common +rules from [above](#versioning-items-in-a-module) apply here as well. It should +however be noted, that specifying Kubernetes specific arguments is done on the +container level instead of on the module level, which is detailed in the +following example: + +``` +# use stackable_versioned_macros::versioned; +# use kube::CustomResource; +# use schemars::JsonSchema; +# use serde::{Deserialize, Serialize}; +#[versioned( + version(name = "v1alpha1"), + version(name = "v1") +)] +mod versioned { + #[versioned(k8s(group = "foo.example.org"))] + #[derive(Clone, Debug, Deserialize, Serialize, CustomResource, JsonSchema)] + struct FooSpec { + bar: usize, + } + + #[versioned(k8s(group = "bar.example.org"))] + #[derive(Clone, Debug, Deserialize, Serialize, CustomResource, JsonSchema)] + struct BarSpec { + baz: String, + } +} + +# fn main() { +let merged_crd = Foo::merged_crd(Foo::V1).unwrap(); +println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); +# } +``` + +
+Expand Generated Code + +```ignore +mod v1alpha1 { + use super::*; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "foo.example.org", + version = "v1alpha1", + kind = "Foo" + )] + pub struct FooSpec { + pub bar: usize, + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "bar.example.org", + version = "v1alpha1", + kind = "Bar" + )] + pub struct BarSpec { + pub bar: usize, + } +} + +// Automatic From implementations for conversion between versions ... + +mod v1 { + use super::*; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "foo.example.org", + version = "v1", + kind = "Foo" + )] + pub struct FooSpec { + pub bar: usize, + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)] + #[kube( + group = "bar.example.org", + version = "v1", + kind = "Bar" + )] + pub struct BarSpec { + pub bar: usize, + } +} + +// Implementations to create the merged CRDs ... +``` +
+ +It is possible to include structs and enums which are not CRDs. They are instead +versioned as expected (without adding the `#[kube]` derive macro and generating +code to merge CRD versions). "# )] #[proc_macro_attribute]