Skip to content

Commit de22bca

Browse files
feat(stackable-versioned-macros): Handle attribute forwarding (#847)
* chore(stackable-versioned): copy and adjust doc-comments from before the refactor * feat(stackable-versioned): pass-through original container attributes to generated containers * feat(stackable-versioned): pass-through original item attributes to generated container items * chore(stackable-versioned): replace field with item in functions, update docs to mention fields and/or variants * feat(stackable-versioned): add version specific docs to containers * chore(stackable-versioned): update changelog * test(stackable-versioned): add enum example * Apply suggestions from code review Co-authored-by: Techassi <[email protected]> --------- Co-authored-by: Techassi <[email protected]>
1 parent 46d77e1 commit de22bca

File tree

14 files changed

+341
-56
lines changed

14 files changed

+341
-56
lines changed

crates/stackable-versioned-macros/src/attrs/common/container.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,13 @@ impl ContainerAttributes {
9696
/// - `name` of the version, like `v1alpha1`.
9797
/// - `deprecated` flag to mark that version as deprecated.
9898
/// - `skip` option to skip generating various pieces of code.
99+
/// - `doc` option to add version-specific documentation.
99100
#[derive(Clone, Debug, FromMeta)]
100101
pub(crate) struct VersionAttributes {
101102
pub(crate) deprecated: Flag,
102103
pub(crate) name: Version,
103104
pub(crate) skip: Option<SkipOptions>,
105+
pub(crate) doc: Option<String>,
104106
}
105107

106108
/// This struct contains supported container options.

crates/stackable-versioned-macros/src/attrs/common/item.rs

+51-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use darling::{util::SpannedValue, Error, FromMeta};
22
use k8s_version::Version;
33
use proc_macro2::Span;
4-
use syn::{spanned::Spanned, Ident, Path};
4+
use syn::{spanned::Spanned, Attribute, Ident, Path};
55

66
use crate::{
77
attrs::common::ContainerAttributes,
@@ -19,8 +19,8 @@ pub(crate) trait ValidateVersions<I>
1919
where
2020
I: Spanned,
2121
{
22-
/// Validates that each field action version is present in the declared
23-
/// container versions.
22+
/// Validates that each field or variant action version is present in the
23+
/// declared container versions.
2424
fn validate_versions(
2525
&self,
2626
container_attrs: &ContainerAttributes,
@@ -42,7 +42,7 @@ where
4242

4343
let mut errors = Error::accumulator();
4444

45-
if let Some(added) = &self.common_attrs().added {
45+
if let Some(added) = &self.common_attributes().added {
4646
if !container_attrs
4747
.versions
4848
.iter()
@@ -55,7 +55,7 @@ where
5555
}
5656
}
5757

58-
for rename in &*self.common_attrs().renames {
58+
for rename in &*self.common_attributes().renames {
5959
if !container_attrs
6060
.versions
6161
.iter()
@@ -68,7 +68,7 @@ where
6868
}
6969
}
7070

71-
if let Some(deprecated) = &self.common_attrs().deprecated {
71+
if let Some(deprecated) = &self.common_attributes().deprecated {
7272
if !container_attrs
7373
.versions
7474
.iter()
@@ -107,8 +107,8 @@ pub(crate) enum ItemType {
107107
/// is part of the container in every version until renamed or deprecated.
108108
/// - An item can be renamed many times. That's why renames are stored in a
109109
/// [`Vec`].
110-
/// - An item can only be deprecated once. A field not marked as 'deprecated'
111-
/// will be included up until the latest version.
110+
/// - An item can only be deprecated once. A field or variant not marked as
111+
/// 'deprecated' will be included up until the latest version.
112112
#[derive(Debug, FromMeta)]
113113
pub(crate) struct ItemAttributes {
114114
/// This parses the `added` attribute on items (fields or variants). It can
@@ -126,15 +126,20 @@ pub(crate) struct ItemAttributes {
126126
}
127127

128128
impl ItemAttributes {
129-
pub(crate) fn validate(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> {
129+
pub(crate) fn validate(
130+
&self,
131+
item_ident: &Ident,
132+
item_type: &ItemType,
133+
item_attrs: &Vec<Attribute>,
134+
) -> Result<(), Error> {
130135
// NOTE (@Techassi): This associated function is NOT called by darling's
131136
// and_then attribute, but instead by the wrapper, FieldAttributes and
132137
// VariantAttributes.
133138

134139
let mut errors = Error::accumulator();
135140

136-
// TODO (@Techassi): Make the field 'note' optional, because in the
137-
// future, the macro will generate parts of the deprecation note
141+
// TODO (@Techassi): Make the field or variant 'note' optional, because
142+
// in the future, the macro will generate parts of the deprecation note
138143
// automatically. The user-provided note will then be appended to the
139144
// auto-generated one.
140145

@@ -150,10 +155,12 @@ impl ItemAttributes {
150155
// Semantic validation
151156
errors.handle(self.validate_action_combinations(item_ident, item_type));
152157
errors.handle(self.validate_action_order(item_ident, item_type));
153-
errors.handle(self.validate_field_name(item_ident, item_type));
158+
errors.handle(self.validate_item_name(item_ident, item_type));
159+
errors.handle(self.validate_item_attributes(item_attrs));
154160

155-
// TODO (@Techassi): Add hint if a field is added in the first version
156-
// that it might be clever to remove the 'added' attribute.
161+
// TODO (@Techassi): Add hint if a field or variant is added in the
162+
// first version that it might be clever to remove the 'added'
163+
// attribute.
157164

158165
errors.finish()?;
159166

@@ -164,13 +171,13 @@ impl ItemAttributes {
164171
/// and validates that each item uses a valid combination of actions.
165172
/// Invalid combinations are:
166173
///
167-
/// - `added` and `deprecated` using the same version: A field cannot be
168-
/// marked as added in a particular version and then marked as deprecated
169-
/// immediately after. Fields must be included for at least one version
170-
/// before being marked deprecated.
174+
/// - `added` and `deprecated` using the same version: A field or variant
175+
/// cannot be marked as added in a particular version and then marked as
176+
/// deprecated immediately after. Fields and variants must be included for
177+
/// at least one version before being marked deprecated.
171178
/// - `added` and `renamed` using the same version: The same reasoning from
172-
/// above applies here as well. Fields must be included for at least one
173-
/// version before being renamed.
179+
/// above applies here as well. Fields and variants must be included for
180+
/// at least one version before being renamed.
174181
/// - `renamed` and `deprecated` using the same version: Again, the same
175182
/// rules from above apply here as well.
176183
fn validate_action_combinations(
@@ -195,7 +202,7 @@ impl ItemAttributes {
195202
if renamed.iter().any(|r| *r.since == *deprecated.since) =>
196203
{
197204
Err(Error::custom(
198-
"field cannot be marked as `deprecated` and `renamed` in the same version",
205+
format!("{item_type} cannot be marked as `deprecated` and `renamed` in the same version"),
199206
)
200207
.with_span(item_ident))
201208
}
@@ -252,10 +259,10 @@ impl ItemAttributes {
252259
///
253260
/// The following naming rules apply:
254261
///
255-
/// - Fields marked as deprecated need to include the 'deprecated_' prefix
256-
/// in their name. The prefix must not be included for fields which are
257-
/// not deprecated.
258-
fn validate_field_name(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> {
262+
/// - Fields or variants marked as deprecated need to include the
263+
/// deprecation prefix in their name. The prefix must not be included for
264+
/// fields or variants which are not deprecated.
265+
fn validate_item_name(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> {
259266
let prefix = match item_type {
260267
ItemType::Field => DEPRECATED_FIELD_PREFIX,
261268
ItemType::Variant => DEPRECATED_VARIANT_PREFIX,
@@ -277,6 +284,25 @@ impl ItemAttributes {
277284

278285
Ok(())
279286
}
287+
288+
/// This associated function is called by the top-level validation function
289+
/// and validates that disallowed item attributes are not used.
290+
///
291+
/// The following naming rules apply:
292+
///
293+
/// - `deprecated` must not be set on items. Instead, use the `deprecated()`
294+
/// action of the `#[versioned()]` macro.
295+
fn validate_item_attributes(&self, item_attrs: &Vec<Attribute>) -> Result<(), Error> {
296+
for attr in item_attrs {
297+
for segment in &attr.path().segments {
298+
if segment.ident == "deprecated" {
299+
return Err(Error::custom("deprecation must be done using #[versioned(deprecated(since = \"VERSION\"))]")
300+
.with_span(&attr.span()));
301+
}
302+
}
303+
}
304+
Ok(())
305+
}
280306
}
281307

282308
/// For the added() action

crates/stackable-versioned-macros/src/attrs/field.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use darling::{Error, FromField};
2-
use syn::Ident;
2+
use syn::{Attribute, Ident};
33

44
use crate::attrs::common::{ItemAttributes, ItemType};
55

@@ -19,7 +19,7 @@ use crate::attrs::common::{ItemAttributes, ItemType};
1919
#[derive(Debug, FromField)]
2020
#[darling(
2121
attributes(versioned),
22-
forward_attrs(allow, doc, cfg, serde),
22+
forward_attrs,
2323
and_then = FieldAttributes::validate
2424
)]
2525
pub(crate) struct FieldAttributes {
@@ -30,6 +30,12 @@ pub(crate) struct FieldAttributes {
3030
// shared item attributes because for struct fields, the type is
3131
// `Option<Ident>`, while for enum variants, the type is `Ident`.
3232
pub(crate) ident: Option<Ident>,
33+
34+
// This must be named `attrs` for darling to populate it accordingly, and
35+
// cannot live in common because Vec<Attribute> is not implemented for
36+
// FromMeta.
37+
/// The original attributes for the field.
38+
pub(crate) attrs: Vec<Attribute>,
3339
}
3440

3541
impl FieldAttributes {
@@ -44,7 +50,7 @@ impl FieldAttributes {
4450
.ident
4551
.as_ref()
4652
.expect("internal error: field must have an ident");
47-
self.common.validate(ident, &ItemType::Field)?;
53+
self.common.validate(ident, &ItemType::Field, &self.attrs)?;
4854

4955
Ok(self)
5056
}

crates/stackable-versioned-macros/src/attrs/variant.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use convert_case::{Case, Casing};
22
use darling::{Error, FromVariant};
3-
use syn::Ident;
3+
use syn::{Attribute, Ident};
44

55
use crate::attrs::common::{ItemAttributes, ItemType};
66

@@ -20,7 +20,7 @@ use crate::attrs::common::{ItemAttributes, ItemType};
2020
#[derive(Debug, FromVariant)]
2121
#[darling(
2222
attributes(versioned),
23-
forward_attrs(allow, doc, cfg, serde),
23+
forward_attrs,
2424
and_then = VariantAttributes::validate
2525
)]
2626
pub(crate) struct VariantAttributes {
@@ -31,6 +31,12 @@ pub(crate) struct VariantAttributes {
3131
// shared item attributes because for struct fields, the type is
3232
// `Option<Ident>`, while for enum variants, the type is `Ident`.
3333
pub(crate) ident: Ident,
34+
35+
// This must be named `attrs` for darling to populate it accordingly, and
36+
// cannot live in common because Vec<Attribute> is not implemented for
37+
// FromMeta.
38+
/// The original attributes for the field.
39+
pub(crate) attrs: Vec<Attribute>,
3440
}
3541

3642
impl VariantAttributes {
@@ -43,7 +49,10 @@ impl VariantAttributes {
4349
fn validate(self) -> Result<Self, Error> {
4450
let mut errors = Error::accumulator();
4551

46-
errors.handle(self.common.validate(&self.ident, &ItemType::Variant));
52+
errors.handle(
53+
self.common
54+
.validate(&self.ident, &ItemType::Variant, &self.attrs),
55+
);
4756

4857
// Validate names of renames
4958
if !self

crates/stackable-versioned-macros/src/codegen/common/container.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ops::Deref;
22

33
use proc_macro2::TokenStream;
4-
use syn::Ident;
4+
use syn::{Attribute, Ident};
55

66
use crate::{attrs::common::ContainerAttributes, codegen::common::ContainerVersion};
77

@@ -21,7 +21,12 @@ where
2121
Self: Sized + Deref<Target = VersionedContainer<I>>,
2222
{
2323
/// Creates a new versioned container.
24-
fn new(ident: Ident, data: D, attributes: ContainerAttributes) -> syn::Result<Self>;
24+
fn new(
25+
ident: Ident,
26+
data: D,
27+
attributes: ContainerAttributes,
28+
original_attributes: Vec<Attribute>,
29+
) -> syn::Result<Self>;
2530

2631
/// This generates the complete code for a single versioned container.
2732
///
@@ -32,12 +37,31 @@ where
3237
fn generate_tokens(&self) -> TokenStream;
3338
}
3439

40+
/// Stores individual versions of a single container.
41+
///
42+
/// Each version tracks item actions, which describe if the item was added,
43+
/// renamed or deprecated in that particular version. Items which are not
44+
/// versioned are included in every version of the container.
3545
#[derive(Debug)]
3646
pub(crate) struct VersionedContainer<I> {
47+
/// List of declared versions for this container. Each version generates a
48+
/// definition with appropriate items.
3749
pub(crate) versions: Vec<ContainerVersion>,
50+
51+
/// List of items defined in the original container. How, and if, an item
52+
/// should generate code, is decided by the currently generated version.
3853
pub(crate) items: Vec<I>,
54+
55+
/// The ident, or name, of the versioned container.
3956
pub(crate) ident: Ident,
4057

58+
/// The name of the container used in `From` implementations.
4159
pub(crate) from_ident: Ident,
60+
61+
/// Whether the [`From`] implementation generation should be skipped for all
62+
/// versions of this container.
4263
pub(crate) skip_from: bool,
64+
65+
/// The original attributes that were added to the container.
66+
pub(crate) original_attributes: Vec<Attribute>,
4367
}

0 commit comments

Comments
 (0)