@@ -139,16 +139,19 @@ mod consts;
139
139
///
140
140
/// ### Added Action
141
141
///
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.
143
143
/// Available parameters are:
144
144
///
145
145
/// - `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
147
147
/// in auto-generated [`From`] implementations.
148
148
///
149
149
/// ```
150
150
/// # use stackable_versioned_macros::versioned;
151
- /// #[versioned(version(name = "v1alpha1"), version(name = "v1beta1"))]
151
+ /// #[versioned(
152
+ /// version(name = "v1alpha1"),
153
+ /// version(name = "v1beta1")
154
+ /// )]
152
155
/// pub struct Foo {
153
156
/// #[versioned(added(since = "v1beta1"))]
154
157
/// bar: usize,
@@ -161,123 +164,218 @@ mod consts;
161
164
///
162
165
/// 1. The field `bar` is not yet present in version `v1alpha1` and is therefore
163
166
/// 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.
165
170
///
166
171
/// ```ignore
167
172
/// pub mod v1alpha1 {
168
173
/// use super::*;
169
- /// pub struct Foo { // 1
174
+ /// pub struct Foo { // 1
170
175
/// pub baz: bool,
171
176
/// }
172
177
/// }
173
178
///
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
+ ///
174
188
/// pub mod v1beta1 {
175
189
/// use super::*;
176
190
/// pub struct Foo {
177
- /// pub bar: usize, // 2
191
+ /// pub bar: usize, // 2
178
192
/// pub baz: bool,
179
193
/// }
180
194
/// }
181
195
/// ```
182
196
/// </details>
183
197
///
184
- /// ### Changed Action
198
+ /// #### Custom Default Function
185
199
///
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.
187
203
///
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
+ /// ```
189
220
///
190
- /// TODO.
221
+ /// <details>
222
+ /// <summary>Generated code</summary>
191
223
///
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).
193
227
///
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.
198
253
///
199
254
/// ```
200
255
/// # use stackable_versioned_macros::versioned;
201
256
/// #[versioned(
202
257
/// version(name = "v1alpha1"),
203
- /// version(name = "v1beta1"),
204
- /// version(name = "v1")
258
+ /// version(name = "v1beta1")
205
259
/// )]
206
260
/// 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,
212
267
/// baz: bool,
213
268
/// }
214
269
/// ```
215
270
///
216
271
/// <details>
217
272
/// <summary>Generated code</summary>
218
273
///
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.
228
278
///
229
279
/// ```ignore
230
280
/// pub mod v1alpha1 {
231
281
/// use super::*;
232
- /// pub struct Foo { // 1
282
+ /// pub struct Foo {
283
+ /// pub prev_bar: u16, // 1
233
284
/// pub baz: bool,
234
285
/// }
235
286
/// }
236
287
///
237
288
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
238
- /// fn from(__sv_foo : v1alpha1::Foo) -> Self {
289
+ /// fn from(foo : v1alpha1::Foo) -> Self {
239
290
/// Self {
240
- /// bar: ::std::default::Default::default (), // 2
241
- /// baz: __sv_foo .baz,
291
+ /// bar: foo.prev_bar.into (), // 2
292
+ /// baz: foo .baz,
242
293
/// }
243
294
/// }
244
295
/// }
245
296
///
246
297
/// pub mod v1beta1 {
247
298
/// use super::*;
248
299
/// pub struct Foo {
249
- /// pub bar: usize,
300
+ /// pub bar: usize, // 2
250
301
/// pub baz: bool,
251
302
/// }
252
303
/// }
304
+ /// ```
305
+ /// </details>
253
306
///
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 {
256
345
/// Self {
257
- /// deprecated_bar: __sv_foo .bar, // 3
258
- /// baz: __sv_foo .baz,
346
+ /// deprecated_bar: foo .bar, // 2
347
+ /// baz: foo .baz,
259
348
/// }
260
349
/// }
261
350
/// }
262
351
///
263
- /// pub mod v1 {
352
+ /// pub mod v1beta1 {
264
353
/// use super::*;
265
354
/// pub struct Foo {
266
- /// #[deprecated] // 3
355
+ /// #[deprecated] // 2
267
356
/// pub deprecated_bar: usize,
268
357
/// pub baz: bool,
269
358
/// }
270
359
/// }
271
360
/// ```
272
361
/// </details>
273
362
///
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.
275
374
///
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
279
376
///
280
- /// #### Skipping at the Container Level
377
+ /// Disabling this feature on the container level results in no `From`
378
+ /// implementation for all versions.
281
379
///
282
380
/// ```
283
381
/// # use stackable_versioned_macros::versioned;
@@ -290,39 +388,72 @@ mod consts;
290
388
/// pub struct Foo {
291
389
/// #[versioned(
292
390
/// added(since = "v1beta1"),
293
- /// deprecated(since = "v1", note = "not needed" )
391
+ /// deprecated(since = "v1")
294
392
/// )]
295
393
/// deprecated_bar: usize,
296
394
/// baz: bool,
297
395
/// }
298
396
/// ```
299
397
///
300
- /// ### Customize Default Function for Added Fields
398
+ /// ### Skipping at the Version Level
301
399
///
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.
305
404
///
306
405
/// ```
307
406
/// # use stackable_versioned_macros::versioned;
308
407
/// #[versioned(
309
408
/// version(name = "v1alpha1"),
310
- /// version(name = "v1beta1"),
409
+ /// version(name = "v1beta1", skip(from) ),
311
410
/// version(name = "v1")
312
411
/// )]
313
412
/// pub struct Foo {
314
413
/// #[versioned(
315
- /// added(since = "v1beta1", default = "default_bar" ),
316
- /// deprecated(since = "v1", note = "not needed" )
414
+ /// added(since = "v1beta1"),
415
+ /// deprecated(since = "v1")
317
416
/// )]
318
417
/// deprecated_bar: usize,
319
418
/// baz: bool,
320
419
/// }
321
- ///
322
- /// fn default_bar() -> usize {
323
- /// 42
324
- /// }
325
420
/// ```
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
+ ) ]
326
457
#[ proc_macro_attribute]
327
458
pub fn versioned ( attrs : TokenStream , input : TokenStream ) -> TokenStream {
328
459
let attrs = match NestedMeta :: parse_meta_list ( attrs. into ( ) ) {
@@ -334,7 +465,7 @@ pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream {
334
465
} ;
335
466
336
467
// 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.
338
469
// In the future - if we decide to support modules - this requires
339
470
// adjustments to also support modules. One possible solution might be to
340
471
// use an enum with two variants: Container(DeriveInput) and
0 commit comments