Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ impl ContainerAttributes {
/// - `name` of the version, like `v1alpha1`.
/// - `deprecated` flag to mark that version as deprecated.
/// - `skip` option to skip generating various pieces of code.
/// - `doc` option to add version-specific documentation.
#[derive(Clone, Debug, FromMeta)]
pub(crate) struct VersionAttributes {
pub(crate) deprecated: Flag,
pub(crate) name: Version,
pub(crate) skip: Option<SkipOptions>,
pub(crate) doc: Option<String>,
}

/// This struct contains supported container options.
Expand Down
76 changes: 51 additions & 25 deletions crates/stackable-versioned-macros/src/attrs/common/item.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use darling::{util::SpannedValue, Error, FromMeta};
use k8s_version::Version;
use proc_macro2::Span;
use syn::{spanned::Spanned, Ident, Path};
use syn::{spanned::Spanned, Attribute, Ident, Path};

use crate::{
attrs::common::ContainerAttributes,
Expand All @@ -19,8 +19,8 @@ pub(crate) trait ValidateVersions<I>
where
I: Spanned,
{
/// Validates that each field action version is present in the declared
/// container versions.
/// Validates that each field or variant action version is present in the
/// declared container versions.
fn validate_versions(
&self,
container_attrs: &ContainerAttributes,
Expand All @@ -42,7 +42,7 @@ where

let mut errors = Error::accumulator();

if let Some(added) = &self.common_attrs().added {
if let Some(added) = &self.common_attributes().added {
if !container_attrs
.versions
.iter()
Expand All @@ -55,7 +55,7 @@ where
}
}

for rename in &*self.common_attrs().renames {
for rename in &*self.common_attributes().renames {
if !container_attrs
.versions
.iter()
Expand All @@ -68,7 +68,7 @@ where
}
}

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

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

let mut errors = Error::accumulator();

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

Expand All @@ -150,10 +155,12 @@ impl ItemAttributes {
// Semantic validation
errors.handle(self.validate_action_combinations(item_ident, item_type));
errors.handle(self.validate_action_order(item_ident, item_type));
errors.handle(self.validate_field_name(item_ident, item_type));
errors.handle(self.validate_item_name(item_ident, item_type));
errors.handle(self.validate_item_attributes(item_attrs));

// TODO (@Techassi): Add hint if a field is added in the first version
// that it might be clever to remove the 'added' attribute.
// TODO (@Techassi): Add hint if a field or variant is added in the
// first version that it might be clever to remove the 'added'
// attribute.

errors.finish()?;

Expand All @@ -164,13 +171,13 @@ impl ItemAttributes {
/// and validates that each item uses a valid combination of actions.
/// Invalid combinations are:
///
/// - `added` and `deprecated` using the same version: A field cannot be
/// marked as added in a particular version and then marked as deprecated
/// immediately after. Fields must be included for at least one version
/// before being marked deprecated.
/// - `added` and `deprecated` using the same version: A field or variant
/// cannot be marked as added in a particular version and then marked as
/// deprecated immediately after. Fields and variants must be included for
/// at least one version before being marked deprecated.
/// - `added` and `renamed` using the same version: The same reasoning from
/// above applies here as well. Fields must be included for at least one
/// version before being renamed.
/// above applies here as well. Fields and variants must be included for
/// at least one version before being renamed.
/// - `renamed` and `deprecated` using the same version: Again, the same
/// rules from above apply here as well.
fn validate_action_combinations(
Expand All @@ -195,7 +202,7 @@ impl ItemAttributes {
if renamed.iter().any(|r| *r.since == *deprecated.since) =>
{
Err(Error::custom(
"field cannot be marked as `deprecated` and `renamed` in the same version",
"cannot be marked as `deprecated` and `renamed` in the same version",
)
.with_span(item_ident))
}
Expand Down Expand Up @@ -252,10 +259,10 @@ impl ItemAttributes {
///
/// The following naming rules apply:
///
/// - Fields marked as deprecated need to include the 'deprecated_' prefix
/// in their name. The prefix must not be included for fields which are
/// not deprecated.
fn validate_field_name(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> {
/// - Fields or variants marked as deprecated need to include the
/// 'deprecated_' prefix in their name. The prefix must not be included
/// for fields or variants which are not deprecated.
fn validate_item_name(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> {
let prefix = match item_type {
ItemType::Field => DEPRECATED_FIELD_PREFIX,
ItemType::Variant => DEPRECATED_VARIANT_PREFIX,
Expand All @@ -277,6 +284,25 @@ impl ItemAttributes {

Ok(())
}

/// This associated function is called by the top-level validation function
/// and validates that disallowed item attributes are not used.
///
/// The following naming rules apply:
///
/// - `deprecated` must not be set on items. Instead, the
/// stackable-versioned method of deprecating items should be used.
fn validate_item_attributes(&self, item_attrs: &Vec<Attribute>) -> Result<(), Error> {
for attr in item_attrs {
for segment in &attr.path().segments {
if segment.ident == "deprecated" {
return Err(Error::custom("deprecation must be done using #[versioned(deprecated(since = \"VERSION\"))]")
.with_span(&segment.ident.span()));
}
}
}
Ok(())
}
}

/// For the added() action
Expand Down
12 changes: 9 additions & 3 deletions crates/stackable-versioned-macros/src/attrs/field.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use darling::{Error, FromField};
use syn::Ident;
use syn::{Attribute, Ident};

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

Expand All @@ -19,7 +19,7 @@ use crate::attrs::common::{ItemAttributes, ItemType};
#[derive(Debug, FromField)]
#[darling(
attributes(versioned),
forward_attrs(allow, doc, cfg, serde),
forward_attrs,
and_then = FieldAttributes::validate
)]
pub(crate) struct FieldAttributes {
Expand All @@ -30,6 +30,12 @@ pub(crate) struct FieldAttributes {
// shared item attributes because for struct fields, the type is
// `Option<Ident>`, while for enum variants, the type is `Ident`.
pub(crate) ident: Option<Ident>,

/// The original attributes for the field.
// This must be named `attrs` for darling to populate it accordingly, and
// cannot live in common because Vec<Attribute> is not implemented for
// FromMeta.
pub(crate) attrs: Vec<Attribute>,
}

impl FieldAttributes {
Expand All @@ -44,7 +50,7 @@ impl FieldAttributes {
.ident
.as_ref()
.expect("internal error: field must have an ident");
self.common.validate(ident, &ItemType::Field)?;
self.common.validate(ident, &ItemType::Field, &self.attrs)?;

Ok(self)
}
Expand Down
15 changes: 12 additions & 3 deletions crates/stackable-versioned-macros/src/attrs/variant.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use convert_case::{Case, Casing};
use darling::{Error, FromVariant};
use syn::Ident;
use syn::{Attribute, Ident};

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

Expand All @@ -20,7 +20,7 @@ use crate::attrs::common::{ItemAttributes, ItemType};
#[derive(Debug, FromVariant)]
#[darling(
attributes(versioned),
forward_attrs(allow, doc, cfg, serde),
forward_attrs,
and_then = VariantAttributes::validate
)]
pub(crate) struct VariantAttributes {
Expand All @@ -31,6 +31,12 @@ pub(crate) struct VariantAttributes {
// shared item attributes because for struct fields, the type is
// `Option<Ident>`, while for enum variants, the type is `Ident`.
pub(crate) ident: Ident,

/// The original attributes for the field.
// This must be named `attrs` for darling to populate it accordingly, and
// cannot live in common because Vec<Attribute> is not implemented for
// FromMeta.
pub(crate) attrs: Vec<Attribute>,
}

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

errors.handle(self.common.validate(&self.ident, &ItemType::Variant));
errors.handle(
self.common
.validate(&self.ident, &ItemType::Variant, &self.attrs),
);

// Validate names of renames
if !self
Expand Down
28 changes: 26 additions & 2 deletions crates/stackable-versioned-macros/src/codegen/common/container.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ops::Deref;

use proc_macro2::TokenStream;
use syn::Ident;
use syn::{Attribute, Ident};

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

Expand All @@ -21,7 +21,12 @@ where
Self: Sized + Deref<Target = VersionedContainer<I>>,
{
/// Creates a new versioned container.
fn new(ident: Ident, data: D, attributes: ContainerAttributes) -> syn::Result<Self>;
fn new(
ident: Ident,
data: D,
attributes: ContainerAttributes,
original_attributes: Vec<Attribute>,
) -> syn::Result<Self>;

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

/// Stores individual versions of a single container.
///
/// Each version tracks item actions, which describe if the item was added,
/// renamed or deprecated in that particular version. Items which are not
/// versioned are included in every version of the container.
#[derive(Debug)]
pub(crate) struct VersionedContainer<I> {
/// List of declared versions for this container. Each version generates a
/// definition with appropriate items.
pub(crate) versions: Vec<ContainerVersion>,

/// List of items defined in the original container. How, and if, an item
/// should generate code, is decided by the currently generated version.
pub(crate) items: Vec<I>,

/// The ident, or name, of the versioned container.
pub(crate) ident: Ident,

/// The name of the container used in `From` implementations.
pub(crate) from_ident: Ident,

/// Whether the [`From`] implementation generation should be skipped for all
/// versions of this container.
pub(crate) skip_from: bool,

/// The original attributes that were added to the container.
pub(crate) original_attributes: Vec<Attribute>,
}
Loading
Loading