diff --git a/crates/stackable-versioned-macros/src/attrs/item/field.rs b/crates/stackable-versioned-macros/src/attrs/item/field.rs index 758f327c4..f088bc438 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/field.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/field.rs @@ -1,7 +1,10 @@ use darling::{Error, FromField, Result, util::Flag}; use syn::{Attribute, Ident}; -use crate::{attrs::item::CommonItemAttributes, codegen::VersionDefinition, utils::FieldIdent}; +use crate::{ + attrs::item::CommonItemAttributes, + codegen::{VersionDefinition, item::FieldIdents}, +}; /// This struct describes all available field attributes, as well as the field /// name to display better diagnostics. @@ -57,7 +60,9 @@ impl FieldAttributes { .expect("internal error: field must have an ident") .clone(); - self.common.validate(FieldIdent::from(ident), &self.attrs)?; + self.common + .validate(FieldIdents::from(ident), &self.attrs)?; + Ok(self) } diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index c7c3d5fe3..d6124f627 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -8,7 +8,7 @@ use syn::{Attribute, Path, Type, spanned::Spanned}; use crate::{ codegen::{VersionDefinition, item::ItemStatus}, - utils::ItemIdentExt, + utils::ItemIdents, }; mod field; @@ -50,14 +50,14 @@ pub struct CommonItemAttributes { // it contains functions which can only be called after the initial parsing and validation because // they need additional context, namely the list of versions defined on the container or module. impl CommonItemAttributes { - pub fn validate(&self, item_ident: impl ItemIdentExt, item_attrs: &[Attribute]) -> Result<()> { + pub fn validate(&self, item_idents: impl ItemIdents, item_attrs: &[Attribute]) -> Result<()> { let mut errors = Error::accumulator(); - errors.handle(self.validate_action_combinations(&item_ident)); - errors.handle(self.validate_action_order(&item_ident)); - errors.handle(self.validate_item_name(&item_ident)); + errors.handle(self.validate_action_combinations(&item_idents)); + errors.handle(self.validate_action_order(&item_idents)); + errors.handle(self.validate_item_name(&item_idents)); errors.handle(self.validate_added_action()); - errors.handle(self.validate_changed_action(&item_ident)); + errors.handle(self.validate_changed_action(&item_idents)); errors.handle(self.validate_item_attributes(item_attrs)); errors.finish() @@ -106,15 +106,15 @@ impl CommonItemAttributes { /// at least one version before being changed. /// - `changed` and `deprecated` using the same version: Again, the same /// rules from above apply here as well. - fn validate_action_combinations(&self, item_ident: &impl ItemIdentExt) -> Result<()> { + fn validate_action_combinations(&self, item_idents: &impl ItemIdents) -> Result<()> { match (&self.added, &self.changes, &self.deprecated) { (Some(added), _, Some(deprecated)) if *added.since == *deprecated.since => Err( Error::custom("cannot be marked as `added` and `deprecated` in the same version") - .with_span(item_ident), + .with_span(item_idents.original()), ), (Some(added), changed, _) if changed.iter().any(|r| *r.since == *added.since) => Err( Error::custom("cannot be marked as `added` and `changed` in the same version") - .with_span(item_ident), + .with_span(item_idents.original()), ), (_, changed, Some(deprecated)) if changed.iter().any(|r| *r.since == *deprecated.since) => @@ -122,7 +122,7 @@ impl CommonItemAttributes { Err(Error::custom( "cannot be marked as `deprecated` and `changed` in the same version", ) - .with_span(item_ident)) + .with_span(item_idents.original())) } _ => Ok(()), } @@ -140,7 +140,7 @@ impl CommonItemAttributes { /// version of the added action. /// - All `changed` actions must use a greater version than `added` but a /// lesser version than `deprecated`. - fn validate_action_order(&self, item_ident: &impl ItemIdentExt) -> Result<()> { + fn validate_action_order(&self, item_idents: &impl ItemIdents) -> Result<()> { let added_version = self.added.as_ref().map(|a| *a.since); let deprecated_version = self.deprecated.as_ref().map(|d| *d.since); @@ -152,7 +152,7 @@ impl CommonItemAttributes { if added_version > deprecated_version { return Err(Error::custom(format!( "cannot marked as `added` in version `{added_version}` while being marked as `deprecated` in an earlier version `{deprecated_version}`" - )).with_span(item_ident)); + )).with_span(item_idents.original())); } } @@ -165,7 +165,7 @@ impl CommonItemAttributes { return Err(Error::custom( "all changes must use versions higher than `added` and lower than `deprecated`", ) - .with_span(item_ident)); + .with_span(item_idents.original())); } Ok(()) @@ -180,21 +180,22 @@ impl CommonItemAttributes { /// - Fields or variants marked as deprecated need to include the /// deprecation 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: &impl ItemIdentExt) -> Result<()> { - let starts_with_deprecated = item_ident.starts_with_deprecated_prefix(); + fn validate_item_name(&self, item_idents: &impl ItemIdents) -> Result<()> { + let starts_with_deprecated = item_idents.starts_with_deprecation_prefix(); if self.deprecated.is_some() && !starts_with_deprecated { return Err(Error::custom(format!( - "marked as `deprecated` and thus must include the `{deprecated_prefix}` prefix", - deprecated_prefix = item_ident.deprecated_prefix() + "marked as `deprecated` and thus must include the `{deprecation_prefix}` prefix", + deprecation_prefix = item_idents.deprecation_prefix() )) - .with_span(item_ident)); + .with_span(item_idents.original())); } if self.deprecated.is_none() && starts_with_deprecated { - return Err(Error::custom( - format!("not marked as `deprecated` and thus must not include the `{deprecated_prefix}` prefix", deprecated_prefix = item_ident.deprecated_prefix()) - ).with_span(item_ident)); + return Err(Error::custom(format!( + "not marked as `deprecated` and thus must not include the `{deprecation_prefix}` prefix", + deprecation_prefix = item_idents.deprecation_prefix() + )).with_span(item_idents.original())); } Ok(()) @@ -218,7 +219,7 @@ impl CommonItemAttributes { /// This associated function is called by the top-level validation function /// and validates that parameters provided to the `changed` actions are /// valid. - fn validate_changed_action(&self, item_ident: &impl ItemIdentExt) -> Result<()> { + fn validate_changed_action(&self, item_ident: &impl ItemIdents) -> Result<()> { let mut errors = Error::accumulator(); for change in &self.changes { @@ -230,7 +231,7 @@ impl CommonItemAttributes { // This ensures that `from_name` doesn't include the deprecation prefix. if let Some(from_name) = change.from_name.as_ref() { - if from_name.starts_with(item_ident.deprecated_prefix()) { + if from_name.starts_with(item_ident.deprecation_prefix()) { errors.push( Error::custom( "the previous name must not start with the deprecation prefix", @@ -291,17 +292,17 @@ impl CommonItemAttributes { impl CommonItemAttributes { pub fn into_changeset( self, - ident: &impl ItemIdentExt, + idents: &impl ItemIdents, ty: Type, ) -> Option> { // TODO (@Techassi): Use Change instead of ItemStatus if let Some(deprecated) = self.deprecated { - let deprecated_ident = ident.deref(); + let deprecated_ident = idents.original(); // When the item is deprecated, any change which occurred beforehand // requires access to the item ident to infer the item ident for // the latest change. - let mut ident = ident.as_cleaned_ident(); + let mut ident = idents.cleaned().clone(); let mut ty = ty; let mut actions = BTreeMap::new(); @@ -361,7 +362,7 @@ impl CommonItemAttributes { Some(actions) } else if !self.changes.is_empty() { - let mut ident = ident.deref().clone(); + let mut ident = idents.original().clone(); let mut ty = ty; let mut actions = BTreeMap::new(); @@ -419,7 +420,7 @@ impl CommonItemAttributes { *added.since, ItemStatus::Addition { default_fn: added.default_fn.deref().clone(), - ident: ident.deref().clone(), + ident: idents.original().clone(), ty: Box::new(ty), }, ); diff --git a/crates/stackable-versioned-macros/src/attrs/item/variant.rs b/crates/stackable-versioned-macros/src/attrs/item/variant.rs index afaec94a0..a0642b0d3 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/variant.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/variant.rs @@ -2,7 +2,10 @@ use convert_case::{Case, Casing}; use darling::{Error, FromVariant, Result}; use syn::{Attribute, Ident}; -use crate::{attrs::item::CommonItemAttributes, codegen::VersionDefinition, utils::VariantIdent}; +use crate::{ + attrs::item::CommonItemAttributes, + codegen::{VersionDefinition, item::VariantIdents}, +}; /// This struct describes all available variant attributes, as well as the /// variant name to display better diagnostics. @@ -51,7 +54,7 @@ impl VariantAttributes { errors.handle( self.common - .validate(VariantIdent::from(self.ident.clone()), &self.attrs), + .validate(VariantIdents::from(self.ident.clone()), &self.attrs), ); // Validate names of renames diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index 17f717a44..5579f6f3f 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -3,8 +3,8 @@ use std::collections::BTreeMap; use darling::{FromField, Result, util::IdentString}; use k8s_version::Version; use proc_macro2::TokenStream; -use quote::{format_ident, quote}; -use syn::{Attribute, Field, Type}; +use quote::quote; +use syn::{Attribute, Field, Ident, Type}; use crate::{ attrs::item::FieldAttributes, @@ -14,13 +14,13 @@ use crate::{ item::ItemStatus, module::ModuleGenerationContext, }, - utils::FieldIdent, + utils::{ItemIdentExt, ItemIdents}, }; pub struct VersionedField { pub original_attributes: Vec, pub changes: Option>, - pub ident: FieldIdent, + pub idents: FieldIdents, pub nested: bool, pub ty: Type, } @@ -38,7 +38,7 @@ impl VersionedField { let ident = field .ident .expect("internal error: field must have an ident"); - let idents = ident.into(); + let idents = FieldIdents::from(ident); let changes = field_attributes .common @@ -47,9 +47,9 @@ impl VersionedField { Ok(Self { original_attributes: field_attributes.attrs, - ident: idents, ty: field.ty, changes, + idents, nested, }) } @@ -139,7 +139,7 @@ impl VersionedField { None => { // If there is no chain of field actions, the field is not // versioned and therefore included in all versions. - let field_ident = &self.ident; + let field_ident = &self.idents.original; let field_type = &self.ty; Some(quote! { @@ -190,16 +190,15 @@ impl VersionedField { // The user specified a custom conversion function which // will be used here instead of the default .into() call // which utilizes From impls. + // FIXME (@Techassi): A custom conversion function needs + // to integrate with tracking as well. Some(upgrade_fn) => Some(quote! { #to_ident: #upgrade_fn(#from_struct_ident.#from_ident), }), // Default .into() call using From impls. None => { if self.nested { - let json_path_ident = format_ident!( - "__sv_{ident}_path", - ident = to_ident.as_ident() - ); + let json_path_ident = to_ident.json_path_ident(); Some(quote! { #to_ident: #from_struct_ident.#from_ident.tracking_into(status, &#json_path_ident), @@ -217,10 +216,7 @@ impl VersionedField { }), None => { if self.nested { - let json_path_ident = format_ident!( - "__sv_{ident}_path", - ident = from_ident.as_ident() - ); + let json_path_ident = from_ident.json_path_ident(); Some(quote! { #from_ident: #from_struct_ident.#to_ident.tracking_into(status, &#json_path_ident), @@ -243,10 +239,7 @@ impl VersionedField { match direction { Direction::Upgrade => { if self.nested { - let json_path_ident = format_ident!( - "__sv_{ident}_path", - ident = next_field_ident.as_ident() - ); + let json_path_ident = next_field_ident.json_path_ident(); Some(quote! { #next_field_ident: #from_struct_ident.#old_field_ident.tracking_into(status, &#json_path_ident), @@ -265,11 +258,10 @@ impl VersionedField { } } None => { - let field_ident = &*self.ident; + let field_ident = &self.idents.original; if self.nested { - let json_path_ident = - format_ident!("__sv_{ident}_path", ident = field_ident.as_ident()); + let json_path_ident = field_ident.json_path_ident(); Some(quote! { #field_ident: #from_struct_ident.#field_ident.tracking_into(status, &#json_path_ident), @@ -310,8 +302,7 @@ impl VersionedField { ItemStatus::Addition { ident, .. } => { // TODO (@Techassi): Only do this formatting once, but that requires extensive // changes to the field ident and changeset generation - let json_path_ident = - format_ident!("__sv_{ident}_path", ident = ident.as_ident()); + let json_path_ident = ident.json_path_ident(); Some(quote! { upgrades.push(#versioned_path::ChangedValue { @@ -343,7 +334,7 @@ impl VersionedField { // NOTE (@Techassi): We currently only support tracking added fields. As such // we only need to generate code if the next change is "Addition". ItemStatus::Addition { ident, .. } => { - let json_path_ident = format_ident!("__sv_{}_path", ident.as_ident()); + let json_path_ident = ident.json_path_ident(); Some(quote! { json_path if json_path == #json_path_ident => { @@ -372,12 +363,13 @@ impl VersionedField { (None, false) => None, // If the field is marked as nested, a path variable for that field needs to be generated - // which is then passed down to the sub struct. There is however no need to look determine - // if the field itself also has changes. This is explicitly handled by the following match + // which is then passed down to the sub struct. There is however no need to determine if + // the field itself also has changes. This is explicitly handled by the following match // arm. (_, true) => { - let field_ident = format_ident!("__sv_{}_path", &self.ident.as_ident()); - let child_string = &self.ident.to_string(); + let field_ident = &self.idents.json_path; + let child_string = self.idents.original.to_string(); + Some(quote! { let #field_ident = #versioned_path::jthong_path(parent, #child_string); }) @@ -387,7 +379,7 @@ impl VersionedField { match next_change { ItemStatus::Addition { ident, .. } => { - let field_ident = format_ident!("__sv_{}_path", ident.as_ident()); + let field_ident = ident.json_path_ident(); let child_string = ident.to_string(); Some(quote! { @@ -400,3 +392,45 @@ impl VersionedField { } } } + +/// A collection of field idents used for different purposes. +#[derive(Debug)] +pub struct FieldIdents { + /// The original ident. + pub original: IdentString, + + /// The cleaned ident, with the deprecation prefix removed. + pub cleaned: IdentString, + + /// The cleaned ident used for JSONPath variables. + pub json_path: IdentString, +} + +impl ItemIdents for FieldIdents { + const DEPRECATION_PREFIX: &str = "deprecated_"; + + fn cleaned(&self) -> &IdentString { + &self.cleaned + } + + fn original(&self) -> &IdentString { + &self.original + } +} + +impl From for FieldIdents { + fn from(ident: Ident) -> Self { + let original = IdentString::new(ident); + let cleaned = original + .clone() + .map(|s| s.trim_start_matches(Self::DEPRECATION_PREFIX).to_owned()); + + let json_path = cleaned.json_path_ident(); + + Self { + json_path, + original, + cleaned, + } + } +} diff --git a/crates/stackable-versioned-macros/src/codegen/item/variant.rs b/crates/stackable-versioned-macros/src/codegen/item/variant.rs index 4463cd5e6..c6b3d6f7f 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/variant.rs @@ -15,13 +15,13 @@ use crate::{ changes::{BTreeMapExt, ChangesetExt}, item::ItemStatus, }, - utils::VariantIdent, + utils::ItemIdents, }; pub struct VersionedVariant { pub original_attributes: Vec, pub changes: Option>, - pub ident: VariantIdent, + pub idents: VariantIdents, pub fields: Fields, } @@ -30,20 +30,20 @@ impl VersionedVariant { let variant_attributes = VariantAttributes::from_variant(&variant)?; variant_attributes.validate_versions(versions)?; - let variant_ident = VariantIdent::from(variant.ident); + let idents = VariantIdents::from(variant.ident); // FIXME (@Techassi): The chain of changes currently doesn't track versioning of variant - // date and as such, we just use the never type here. During codegen, we just re-emit the + // data and as such, we just use the never type here. During codegen, we just re-emit the // variant data as is. let ty = Type::Never(TypeNever { bang_token: Not([Span::call_site()]), }); - let changes = variant_attributes.common.into_changeset(&variant_ident, ty); + let changes = variant_attributes.common.into_changeset(&idents, ty); Ok(Self { original_attributes: variant_attributes.attrs, fields: variant.fields, - ident: variant_ident, + idents, changes, }) } @@ -123,7 +123,7 @@ impl VersionedVariant { // If there is no chain of variant actions, the variant is not // versioned and code generation is straight forward. // Unversioned variants are always included in versioned enums. - let ident = &self.ident; + let ident = &self.idents.original; Some(quote! { #(#original_attributes)* @@ -173,7 +173,7 @@ impl VersionedVariant { None => { let next_version_ident = &next_version.idents.module; let old_version_ident = &version.idents.module; - let variant_ident = &*self.ident; + let variant_ident = &self.idents.original; match direction { Direction::Upgrade => Some(quote! { @@ -239,3 +239,36 @@ impl VersionedVariant { .collect() } } + +/// A collection of variant idents used for different purposes. +#[derive(Debug)] +pub struct VariantIdents { + /// The original ident. + pub original: IdentString, + + /// The cleaned ident, with the deprecation prefix removed. + pub cleaned: IdentString, +} + +impl ItemIdents for VariantIdents { + const DEPRECATION_PREFIX: &str = "Deprecated"; + + fn cleaned(&self) -> &IdentString { + &self.cleaned + } + + fn original(&self) -> &IdentString { + &self.original + } +} + +impl From for VariantIdents { + fn from(ident: Ident) -> Self { + let original = IdentString::new(ident); + let cleaned = original + .clone() + .map(|s| s.trim_start_matches(Self::DEPRECATION_PREFIX).to_owned()); + + Self { original, cleaned } + } +} diff --git a/crates/stackable-versioned-macros/src/utils/mod.rs b/crates/stackable-versioned-macros/src/utils/mod.rs index 50a255673..d69efaa73 100644 --- a/crates/stackable-versioned-macros/src/utils/mod.rs +++ b/crates/stackable-versioned-macros/src/utils/mod.rs @@ -1,11 +1,9 @@ -use std::ops::Deref; - use convert_case::{Case, Casing}; use darling::util::IdentString; use k8s_version::Version; use proc_macro2::Span; -use quote::{ToTokens, format_ident}; -use syn::{Ident, Path, spanned::Spanned}; +use quote::format_ident; +use syn::{Ident, Path}; pub mod doc_comments; @@ -43,7 +41,10 @@ impl ContainerIdentExt for Ident { } fn as_parameter_ident(&self) -> IdentString { - let ident = format_ident!("__sv_{}", self.to_string().to_lowercase()); + let ident = format_ident!( + "__sv_{lowercase_ident}", + lowercase_ident = self.to_string().to_lowercase() + ); IdentString::new(ident) } } @@ -58,82 +59,34 @@ impl ContainerIdentExt for IdentString { } } -pub trait ItemIdentExt: Deref + From + Spanned { - const DEPRECATED_PREFIX: &'static str; - - fn deprecated_prefix(&self) -> &'static str { - Self::DEPRECATED_PREFIX - } - - fn starts_with_deprecated_prefix(&self) -> bool { - self.deref().as_str().starts_with(Self::DEPRECATED_PREFIX) - } - - /// Removes deprecation prefixed from field or variant idents. - fn as_cleaned_ident(&self) -> IdentString; -} - -pub struct FieldIdent(IdentString); - -impl ItemIdentExt for FieldIdent { - const DEPRECATED_PREFIX: &'static str = "deprecated_"; - - fn as_cleaned_ident(&self) -> IdentString { - self.0 - .clone() - .map(|i| i.trim_start_matches(Self::DEPRECATED_PREFIX).to_string()) - } -} - -impl From for FieldIdent { - fn from(value: Ident) -> Self { - Self(IdentString::from(value)) - } -} +pub trait ItemIdents { + const DEPRECATION_PREFIX: &str; -impl Deref for FieldIdent { - type Target = IdentString; - - fn deref(&self) -> &Self::Target { - &self.0 + fn deprecation_prefix(&self) -> &str { + Self::DEPRECATION_PREFIX } -} -impl ToTokens for FieldIdent { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.0.to_tokens(tokens); + fn starts_with_deprecation_prefix(&self) -> bool { + self.original() + .as_str() + .starts_with(Self::DEPRECATION_PREFIX) } -} - -pub struct VariantIdent(IdentString); - -impl ItemIdentExt for VariantIdent { - const DEPRECATED_PREFIX: &'static str = "Deprecated"; - fn as_cleaned_ident(&self) -> IdentString { - self.0 - .clone() - .map(|i| i.trim_start_matches(Self::DEPRECATED_PREFIX).to_string()) - } -} - -impl From for VariantIdent { - fn from(value: Ident) -> Self { - Self(IdentString::from(value)) - } + fn cleaned(&self) -> &IdentString; + fn original(&self) -> &IdentString; } -impl Deref for VariantIdent { - type Target = IdentString; - - fn deref(&self) -> &Self::Target { - &self.0 - } +pub trait ItemIdentExt { + fn json_path_ident(&self) -> IdentString; } -impl ToTokens for VariantIdent { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.0.to_tokens(tokens); +impl ItemIdentExt for IdentString { + fn json_path_ident(&self) -> IdentString { + format_ident!( + "__sv_{lowercase_ident}_path", + lowercase_ident = self.as_str().to_lowercase() + ) + .into() } } @@ -146,7 +99,7 @@ pub fn path_to_string(path: &Path) -> String { .join("::"); match path.leading_colon { - Some(_) => format!("::{}", pretty_path), + Some(_) => format!("::{pretty_path}"), None => pretty_path, } }