Skip to content
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

docs(stackable-versioned): Improve docs for module usage #957

Merged
merged 4 commits into from
Feb 7, 2025
Merged
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: 1 addition & 1 deletion crates/stackable-versioned-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
236 changes: 230 additions & 6 deletions crates/stackable-versioned-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
///
/// <details>
/// <summary>Expand Generated Invalid Code</summary>
///
/// ```ignore
/// mod v1alpha1 {
/// struct Foo {}
/// }
///
/// mod v1alpha1 {
/// struct Bar {}
/// }
/// ```
/// </details>
///
/// 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,
/// }
/// }
/// ```
///
/// <details>
/// <summary>Expand Generated Code</summary>
///
/// 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,
/// }
/// }
/// ```
/// </details>
///
/// 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,
/// }
/// }
/// ```
///
/// <div class="warning">
/// It is planned to move the <code>preserve_module</code> flag into the
/// <code>options()</code> argument list, but currently seems tricky to
/// implement.
/// </div>
///
/// ## Item Actions
///
/// This crate currently supports three different item actions. Items can
Expand Down Expand Up @@ -176,7 +303,7 @@ mod utils;
/// ```
///
/// <details>
/// <summary>Generated code</summary>
/// <summary>Expand Generated Code</summary>
///
/// 1. The field `bar` is not yet present in version `v1alpha1` and is therefore
/// not generated.
Expand Down Expand Up @@ -235,7 +362,7 @@ mod utils;
/// ```
///
/// <details>
/// <summary>Generated code</summary>
/// <summary>Expand Generated Code</summary>
///
/// 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
Expand Down Expand Up @@ -285,7 +412,7 @@ mod utils;
/// ```
///
/// <details>
/// <summary>Generated code</summary>
/// <summary>Expand Generated Code</summary>
///
/// 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
Expand Down Expand Up @@ -336,7 +463,7 @@ mod utils;
/// ```
///
/// <details>
/// <summary>Generated code</summary>
/// <summary>Expand Generated Code</summary>
///
/// 1. In version `v1alpha1` the field `bar` is not yet deprecated and thus uses
/// the name without the `deprecated_` prefix.
Expand Down Expand Up @@ -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());
# }
```

<details>
<summary>Expand Generated Code</summary>

```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 ...
```
</details>

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]
Expand Down