Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/stackable-versioned-macros/src/attrs/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ impl ContainerAttributes {
}
}

// TODO (@Techassi): Add validation for skip(from) for last version,
// which will skip nothing, because nothing is generated in the first
// place.

// Ensure every version is unique and isn't declared multiple times. This
// is inspired by the itertools all_unique function.
let mut unique = HashSet::new();
Expand Down
1 change: 1 addition & 0 deletions crates/stackable-versioned-macros/src/attrs/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ impl FieldAttributes {

// First, validate that the added version is less than the deprecated
// version.
// NOTE (@Techassi): Is this already covered by the code below?
if let (Some(added_version), Some(deprecated_version)) = (added_version, deprecated_version)
{
if added_version >= deprecated_version {
Expand Down
2 changes: 2 additions & 0 deletions crates/stackable-versioned-macros/src/gen/vstruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ impl VersionedStruct {

let fields = self.generate_from_fields(version, next_version, from_ident);

// TODO (@Techassi): Be a little bit more clever about when to include
// the #[allow(deprecated)] attribute.
return quote! {
#[automatically_derived]
#[allow(deprecated)]
Expand Down
216 changes: 216 additions & 0 deletions crates/stackable-versioned-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,222 @@ mod attrs;
mod consts;
mod gen;

/// This macro enables generating versioned structs.
///
/// ## Usage Guide
///
/// ### Quickstart
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1alpha1"),
/// version(name = "v1beta1"),
/// version(name = "v1"),
/// version(name = "v2"),
/// version(name = "v3")
/// )]
/// struct Foo {
/// /// My docs
/// #[versioned(
/// added(since = "v1beta1"),
/// renamed(since = "v1", from = "gau"),
/// deprecated(since = "v2", note = "not empty")
/// )]
/// deprecated_bar: usize,
/// baz: bool,
/// }
/// ```
///
/// ### Declaring Versions
///
/// Before any of the fields can be versioned, versions need to be declared at
/// the container level. Each version currently supports two parameters: `name`
/// and the `deprecated` flag. The `name` must be a valid (and supported)
/// format. The macro checks each declared version and reports any error
/// encountered during parsing.
/// The `deprecated` flag marks the version as deprecated. This currently adds
/// the `#[deprecated]` attribute to the appropriate piece of code.
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1alpha1", deprecated)
/// )]
/// struct Foo {}
/// ```
///
/// Additionally, it is ensured that each version is unique. Declaring the same
/// version multiple times will result in an error. Furthermore, declaring the
/// versions out-of-order ist prohibited by default. It is possible to opt-out
/// of this check by setting `options(allow_unsorted)`:
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1beta1"),
/// version(name = "v1alpha1"),
/// options(allow_unsorted)
/// )]
/// struct Foo {}
/// ```
///
/// ### Field Actions
///
/// This library currently supports three different field actions. Fields can
/// be added, renamed and deprecated. The macro ensures that these actions
/// adhere to the following set of rules:
///
/// - Fields cannot be added and deprecated in the same version.
/// - Fields cannot be added and renamed in the same version.
/// - Fields cannot be renamed and deprecated in the same version.
/// - Fields added in version _a_, renamed _0...n_ times in versions
/// b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>n</sub> and deprecated in
/// version _c_ must ensure _a < b<sub>1</sub>, b<sub>2</sub>, ...,
/// b<sub>n</sub> < c_.
/// - All field actions must use previously declared versions. Using versions
/// not present at the container level will result in an error.
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1alpha1"),
/// version(name = "v1beta1"),
/// version(name = "v1"),
/// version(name = "v2"),
/// )]
/// struct Foo {
/// #[versioned(
/// added(since = "v1beta1"),
/// renamed(since = "v1", from = "gau"),
/// deprecated(since = "v2", note = "not empty")
/// )]
/// deprecated_bar: usize,
/// baz: bool,
/// }
/// ```
///
/// For fields marked as deprecated, two additional rules apply:
///
/// - Fields must start with the `deprecated_` prefix.
/// - The deprecation note cannot be empty.
///
/// ### Auto-generated [`From`] Implementations
///
/// To enable smooth version upgrades of the same struct, the macro automatically
/// generates [`From`] implementations. On a high level, code generated for two
/// versions _a_ and _b_, with _a < b_ looks like this: `impl From<a> for b`.
///
/// ```ignore
/// #[versioned(
/// version(name = "v1alpha1"),
/// version(name = "v1beta1"),
/// version(name = "v1")
/// )]
/// pub struct Foo {
/// #[versioned(
/// added(since = "v1beta1"),
/// deprecated(since = "v1", note = "not needed")
/// )]
/// deprecated_bar: usize,
/// baz: bool,
/// }
///
/// // Produces ...
///
/// #[automatically_derived]
/// pub mod v1alpha1 {
/// pub struct Foo {
/// pub baz: bool,
/// }
/// }
/// #[automatically_derived]
/// #[allow(deprecated)]
/// impl From<v1alpha1::Foo> for v1beta1::Foo {
/// fn from(__sv_foo: v1alpha1::Foo) -> Self {
/// Self {
/// bar: std::default::Default::default(),
/// baz: __sv_foo.baz,
/// }
/// }
/// }
/// #[automatically_derived]
/// pub mod v1beta1 {
/// pub struct Foo {
/// pub bar: usize,
/// pub baz: bool,
/// }
/// }
/// #[automatically_derived]
/// #[allow(deprecated)]
/// impl From<v1beta1::Foo> for v1::Foo {
/// fn from(__sv_foo: v1beta1::Foo) -> Self {
/// Self {
/// deprecated_bar: __sv_foo.bar,
/// baz: __sv_foo.baz,
/// }
/// }
/// }
/// #[automatically_derived]
/// pub mod v1 {
/// pub struct Foo {
/// #[deprecated = "not needed"]
/// pub deprecated_bar: usize,
/// pub baz: bool,
/// }
/// }
/// ```
///
/// #### Skip [`From`] generation
///
/// Generation of these [`From`] implementations can be skipped at the container
/// and version level. This enables customization of the implementations if the
/// default implementation is not sufficient.
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1alpha1"),
/// version(name = "v1beta1"),
/// version(name = "v1"),
/// options(skip(from))
/// )]
/// pub struct Foo {
/// #[versioned(
/// added(since = "v1beta1"),
/// deprecated(since = "v1", note = "not needed")
/// )]
/// deprecated_bar: usize,
/// baz: bool,
/// }
/// ```
///
/// #### Customize Default Function for Added Fields
///
/// It is possible to customize the default function used in the generated
/// [`From`] implementation for populating added fields. By default,
/// [`Default::default()`] is used.
///
/// ```
/// # use stackable_versioned_macros::versioned;
/// #[versioned(
/// version(name = "v1alpha1"),
/// version(name = "v1beta1"),
/// version(name = "v1")
/// )]
/// pub struct Foo {
/// #[versioned(
/// added(since = "v1beta1", default = "default_bar"),
/// deprecated(since = "v1", note = "not needed")
/// )]
/// deprecated_bar: usize,
/// baz: bool,
/// }
///
/// fn default_bar() -> usize {
/// 42
/// }
/// ```
#[proc_macro_attribute]
pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream {
let attrs = match NestedMeta::parse_meta_list(attrs.into()) {
Expand Down
4 changes: 0 additions & 4 deletions crates/stackable-versioned/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ All notable changes to this project will be documented in this file.
[#790]: https://github.com/stackabletech/operator-rs/pull/790
[#793]: https://github.com/stackabletech/operator-rs/pull/793

- Improve action chain generation ([#784]).

[#784](ttps://github.com/stackabletech/operator-rs/pull/784)

## [0.1.0] - 2024-05-08

### Changed
Expand Down
9 changes: 6 additions & 3 deletions crates/stackable-versioned/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! data type. This will be extended to support SemVer versions, as well as
//! custom version formats in the future.
//!
//! ## Basic Usage
//! ## Usage Guide
//!
//! ```
//! use stackable_versioned::versioned;
Expand All @@ -18,14 +18,17 @@
//! struct Foo {
//! /// My docs
//! #[versioned(
//! added(since = "v1alpha1"),
//! renamed(since = "v1beta1", from = "gau"),
//! added(since = "v1beta1"),
//! renamed(since = "v1", from = "gau"),
//! deprecated(since = "v2", note = "not empty")
//! )]
//! deprecated_bar: usize,
//! baz: bool,
//! }
//! ```
//!
//! See [`versioned`] for an in-depth usage guide and a list of supported
//! parameters.

pub use stackable_versioned_macros::*;

Expand Down