Skip to content

Commit 76c686b

Browse files
committed
docs: Add action specific docs, add K8s docs
1 parent 0111a5c commit 76c686b

File tree

3 files changed

+204
-60
lines changed

3 files changed

+204
-60
lines changed

crates/stackable-versioned-macros/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ license.workspace = true
66
edition.workspace = true
77
repository.workspace = true
88

9+
# Enable all features to ensure content appears in the online documentation.
10+
[package.metadata."docs.rs"]
11+
all-features = true
12+
913
# cargo-udeps throws an error that these dependencies are unused. They are,
1014
# however, used in K8s specific test cases. This is a false-positive and an
1115
# apparent limitation of cargo-udeps. These entries can be removed once

crates/stackable-versioned-macros/src/lib.rs

Lines changed: 191 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,19 @@ mod consts;
139139
///
140140
/// ### Added Action
141141
///
142-
/// This action indicates that a item is added in a particular version.
142+
/// This action indicates that an item is added in a particular version.
143143
/// Available parameters are:
144144
///
145145
/// - `since` to indicate since which version the item is present.
146-
/// - `default_fn` to customize the default function used to populate the item
146+
/// - `default` to customize the default function used to populate the item
147147
/// in auto-generated [`From`] implementations.
148148
///
149149
/// ```
150150
/// # use stackable_versioned_macros::versioned;
151-
/// #[versioned(version(name = "v1alpha1"), version(name = "v1beta1"))]
151+
/// #[versioned(
152+
/// version(name = "v1alpha1"),
153+
/// version(name = "v1beta1")
154+
/// )]
152155
/// pub struct Foo {
153156
/// #[versioned(added(since = "v1beta1"))]
154157
/// bar: usize,
@@ -161,123 +164,218 @@ mod consts;
161164
///
162165
/// 1. The field `bar` is not yet present in version `v1alpha1` and is therefore
163166
/// not generated.
164-
/// 2. Now the field `bar` is present in version `v1beta1`.
167+
/// 2. Now the field `bar` is present and uses `Default::default()` to populate
168+
/// the field during conversion. This function can be customized as shown
169+
/// later in this guide.
165170
///
166171
/// ```ignore
167172
/// pub mod v1alpha1 {
168173
/// use super::*;
169-
/// pub struct Foo { // 1
174+
/// pub struct Foo { // 1
170175
/// pub baz: bool,
171176
/// }
172177
/// }
173178
///
179+
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
180+
/// fn from(foo: v1alpha1::Foo) -> Self {
181+
/// Self {
182+
/// bar: Default::default(), // 2
183+
/// baz: foo.baz,
184+
/// }
185+
/// }
186+
/// }
187+
///
174188
/// pub mod v1beta1 {
175189
/// use super::*;
176190
/// pub struct Foo {
177-
/// pub bar: usize, // 2
191+
/// pub bar: usize, // 2
178192
/// pub baz: bool,
179193
/// }
180194
/// }
181195
/// ```
182196
/// </details>
183197
///
184-
/// ### Changed Action
198+
/// #### Custom Default Function
185199
///
186-
/// TODO.
200+
/// To customize the default function used in the generated `From` implementation
201+
/// you can use the `default` parameter. It expects a path to a function without
202+
/// braces.
187203
///
188-
/// ### Deprecated Action
204+
/// ```
205+
/// # use stackable_versioned_macros::versioned;
206+
/// #[versioned(
207+
/// version(name = "v1alpha1"),
208+
/// version(name = "v1beta1")
209+
/// )]
210+
/// pub struct Foo {
211+
/// #[versioned(added(since = "v1beta1", default = "default_bar"))]
212+
/// bar: usize,
213+
/// baz: bool,
214+
/// }
215+
///
216+
/// fn default_bar() -> usize {
217+
/// 42
218+
/// }
219+
/// ```
189220
///
190-
/// TODO.
221+
/// <details>
222+
/// <summary>Generated code</summary>
191223
///
192-
/// ### Auto-generated [`From`] Implementations
224+
/// 1. Instead of `Default::default()`, the provided function `default_bar()` is
225+
/// used. It is of course fully type checked and needs to return the expected
226+
/// type (`usize` in this case).
193227
///
194-
/// To enable smooth version upgrades of the same container, the macro
195-
/// automatically generates [`From`] implementations. On a high level, code
196-
/// generated for two versions _a_ and _b_, with _a < b_ looks like this:
197-
/// `impl From<a> for b`.
228+
/// ```ignore
229+
/// // Snip
230+
///
231+
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
232+
/// fn from(foo: v1alpha1::Foo) -> Self {
233+
/// Self {
234+
/// bar: default_bar(), // 1
235+
/// baz: foo.baz,
236+
/// }
237+
/// }
238+
/// }
239+
///
240+
/// // Snip
241+
/// ```
242+
/// </details>
243+
///
244+
/// ### Changed Action
245+
///
246+
/// This action indicates that an item is changed in a particular version. It
247+
/// combines renames and type changes into a single action. You can choose to
248+
/// change the name, change the type or do both. Available parameters are:
249+
///
250+
/// - `since` to indicate since which version the item is changed.
251+
/// - `from_name` to indicate from which previous name the field is renamed.
252+
/// - `from_type` to indicate from which previous type the field is changed.
198253
///
199254
/// ```
200255
/// # use stackable_versioned_macros::versioned;
201256
/// #[versioned(
202257
/// version(name = "v1alpha1"),
203-
/// version(name = "v1beta1"),
204-
/// version(name = "v1")
258+
/// version(name = "v1beta1")
205259
/// )]
206260
/// pub struct Foo {
207-
/// #[versioned(
208-
/// added(since = "v1beta1"),
209-
/// deprecated(since = "v1")
210-
/// )]
211-
/// deprecated_bar: usize,
261+
/// #[versioned(changed(
262+
/// since = "v1beta1",
263+
/// from_name = "prev_bar",
264+
/// from_type = "u16"
265+
/// ))]
266+
/// bar: usize,
212267
/// baz: bool,
213268
/// }
214269
/// ```
215270
///
216271
/// <details>
217272
/// <summary>Generated code</summary>
218273
///
219-
/// 1. The field `bar` is not yet present in version `v1alpha1` and is therefore
220-
/// not generated.
221-
/// 2. Now the field `bar` is present and uses `Default::default()` to populate
222-
/// the field during conversion. This function can be customized as shown
223-
/// later in this guide.
224-
/// 3. In version `v1` the field `bar` is deprecated and as such includes the
225-
/// `deprecated_` prefix. Additionally, the `#[deprecated]` attribute is
226-
/// added to indicate that this part of the Rust code is deprecated. The
227-
/// note is optional.
274+
/// 1. In version `v1alpha1` the field is named `prev_bar` and uses a `u16`.
275+
/// 2. In the next version, `v1beta1`, the field is now named `bar` and uses
276+
/// `usize` instead of a `u16`. The `From` implementation transforms the
277+
/// type automatically via the `.into()` call.
228278
///
229279
/// ```ignore
230280
/// pub mod v1alpha1 {
231281
/// use super::*;
232-
/// pub struct Foo { // 1
282+
/// pub struct Foo {
283+
/// pub prev_bar: u16, // 1
233284
/// pub baz: bool,
234285
/// }
235286
/// }
236287
///
237288
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
238-
/// fn from(__sv_foo: v1alpha1::Foo) -> Self {
289+
/// fn from(foo: v1alpha1::Foo) -> Self {
239290
/// Self {
240-
/// bar: ::std::default::Default::default(), // 2
241-
/// baz: __sv_foo.baz,
291+
/// bar: foo.prev_bar.into(), // 2
292+
/// baz: foo.baz,
242293
/// }
243294
/// }
244295
/// }
245296
///
246297
/// pub mod v1beta1 {
247298
/// use super::*;
248299
/// pub struct Foo {
249-
/// pub bar: usize,
300+
/// pub bar: usize, // 2
250301
/// pub baz: bool,
251302
/// }
252303
/// }
304+
/// ```
305+
/// </details>
253306
///
254-
/// impl From<v1beta1::Foo> for v1::Foo {
255-
/// fn from(__sv_foo: v1beta1::Foo) -> Self {
307+
/// ### Deprecated Action
308+
///
309+
/// This action indicates that an item is deprecated in a particular version.
310+
/// Deprecated items are not removed.
311+
///
312+
/// ```
313+
/// # use stackable_versioned_macros::versioned;
314+
/// #[versioned(version(name = "v1alpha1"), version(name = "v1beta1"))]
315+
/// pub struct Foo {
316+
/// #[versioned(deprecated(since = "v1beta1"))]
317+
/// deprecated_bar: usize,
318+
/// baz: bool,
319+
/// }
320+
/// ```
321+
///
322+
/// <details>
323+
/// <summary>Generated code</summary>
324+
///
325+
/// 1. In version `v1alpha1` the field `bar` is not yet deprecated and thus uses
326+
/// the name without the `deprecated_` prefix.
327+
/// 2. In version `v1beta1` the field is deprecated and now includes the
328+
/// `deprecated_` prefix. It also uses the `#[deprecated]` attribute to
329+
/// indicate to Clippy this part of Rust code is deprecated. Therefore, the
330+
/// `From` implementation includes `#[allow(deprecated)]` to allow the
331+
/// usage of deprecated items in automatically generated code.
332+
///
333+
/// ```ignore
334+
/// pub mod v1alpha1 {
335+
/// use super::*;
336+
/// pub struct Foo {
337+
/// pub bar: usize, // 1
338+
/// pub baz: bool,
339+
/// }
340+
/// }
341+
///
342+
/// #[allow(deprecated)] // 2
343+
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
344+
/// fn from(foo: v1alpha1::Foo) -> Self {
256345
/// Self {
257-
/// deprecated_bar: __sv_foo.bar, // 3
258-
/// baz: __sv_foo.baz,
346+
/// deprecated_bar: foo.bar, // 2
347+
/// baz: foo.baz,
259348
/// }
260349
/// }
261350
/// }
262351
///
263-
/// pub mod v1 {
352+
/// pub mod v1beta1 {
264353
/// use super::*;
265354
/// pub struct Foo {
266-
/// #[deprecated] // 3
355+
/// #[deprecated] // 2
267356
/// pub deprecated_bar: usize,
268357
/// pub baz: bool,
269358
/// }
270359
/// }
271360
/// ```
272361
/// </details>
273362
///
274-
/// ### Skip [`From`] Generation
363+
/// ## Auto-generated `From` Implementations
364+
///
365+
/// To enable smooth version upgrades of the same container, the macro
366+
/// automatically generates `From` implementations. On a high level, code
367+
/// generated for two versions _a_ and _b_, with _a < b_ looks like this:
368+
/// `impl From<a> for b`. As you can see, only upgrading is currently supported.
369+
/// Downgrading, so going from a higher version to a lower one, is not supported
370+
/// at the moment.
371+
///
372+
/// This automatic generation can be skipped to for example enable a custom
373+
/// implementation for more complex conversions.
275374
///
276-
/// Generation of auto-generated [`From`] implementations can be skipped at the
277-
/// container and version level. This enables customization of the
278-
/// implementations if the default implementation is not sufficient.
375+
/// ### Skipping at the Container Level
279376
///
280-
/// #### Skipping at the Container Level
377+
/// Disabling this feature on the container level results in no `From`
378+
/// implementation for all versions.
281379
///
282380
/// ```
283381
/// # use stackable_versioned_macros::versioned;
@@ -290,39 +388,72 @@ mod consts;
290388
/// pub struct Foo {
291389
/// #[versioned(
292390
/// added(since = "v1beta1"),
293-
/// deprecated(since = "v1", note = "not needed")
391+
/// deprecated(since = "v1")
294392
/// )]
295393
/// deprecated_bar: usize,
296394
/// baz: bool,
297395
/// }
298396
/// ```
299397
///
300-
/// ### Customize Default Function for Added Fields
398+
/// ### Skipping at the Version Level
301399
///
302-
/// It is possible to customize the default function used in the generated
303-
/// [`From`] implementation for populating added fields. By default,
304-
/// [`Default::default()`] is used.
400+
/// Disabling this feature at the version level results in no `From`
401+
/// implementation for that particular version. This can be read as "skip
402+
/// generation for converting _this_ version to the next one". In the example
403+
/// below no conversion between version `v1beta1` and `v1` is generated.
305404
///
306405
/// ```
307406
/// # use stackable_versioned_macros::versioned;
308407
/// #[versioned(
309408
/// version(name = "v1alpha1"),
310-
/// version(name = "v1beta1"),
409+
/// version(name = "v1beta1", skip(from)),
311410
/// version(name = "v1")
312411
/// )]
313412
/// pub struct Foo {
314413
/// #[versioned(
315-
/// added(since = "v1beta1", default = "default_bar"),
316-
/// deprecated(since = "v1", note = "not needed")
414+
/// added(since = "v1beta1"),
415+
/// deprecated(since = "v1")
317416
/// )]
318417
/// deprecated_bar: usize,
319418
/// baz: bool,
320419
/// }
321-
///
322-
/// fn default_bar() -> usize {
323-
/// 42
324-
/// }
325420
/// ```
421+
///
422+
/// ## Kubernetes-specific Features
423+
///
424+
/// This macro also offers support for Kubernetes-specific versioning,
425+
/// especially for CustomResourceDefinitions, short CRDs. These features are
426+
/// completely opt-in. You need to enable the `k8s` feature (which enables
427+
/// optional dependencies) and use the `k8s()` parameter in the macro.
428+
///
429+
#[cfg_attr(
430+
feature = "k8s",
431+
doc = r#"
432+
```
433+
# use stackable_versioned_macros::versioned;
434+
use schemars::JsonSchema;
435+
use serde::{Deserialize, Serialize};
436+
437+
#[versioned(
438+
version(name = "v1alpha1"),
439+
version(name = "v1beta1"),
440+
version(name = "v1"),
441+
k8s(group = "example.com")
442+
)]
443+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
444+
pub struct FooSpec {
445+
#[versioned(
446+
added(since = "v1beta1"),
447+
changed(since = "v1", from_name = "prev_bar", from_type = "u16")
448+
)]
449+
bar: usize,
450+
baz: bool,
451+
}
452+
let merged_crd = Foo::merged_crd("v1").unwrap();
453+
println!("{}", serde_yaml::to_string(&merged_crd).unwrap());
454+
```
455+
"#
456+
)]
326457
#[proc_macro_attribute]
327458
pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream {
328459
let attrs = match NestedMeta::parse_meta_list(attrs.into()) {
@@ -334,7 +465,7 @@ pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream {
334465
};
335466

336467
// NOTE (@Techassi): For now, we can just use the DeriveInput type here,
337-
// because we only support structs (and eventually enums) to be versioned.
468+
// because we only support structs end enums to be versioned.
338469
// In the future - if we decide to support modules - this requires
339470
// adjustments to also support modules. One possible solution might be to
340471
// use an enum with two variants: Container(DeriveInput) and

0 commit comments

Comments
 (0)