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]