Skip to content

feat: Add stackable-versioned and k8s-version crates for CRD versioning #764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
31df5b1
chore: Add skeleton code
Techassi Apr 4, 2024
c93cdee
feat: Add basic container and attribute validation
Techassi Apr 8, 2024
109e38f
Start field attribute validation
Techassi Apr 8, 2024
40df3af
Move code generation into structs
Techassi Apr 9, 2024
cff3fe9
Adjust field actions which require generation in multiple versions
Techassi Apr 16, 2024
aaaedaa
Add basic support for added and always present fields
Techassi Apr 16, 2024
be20ad8
feat(k8s-version): Add Kubernetes version crate
Techassi Apr 19, 2024
f46d370
feat(k8s-version): Add support for FromMeta
Techassi Apr 19, 2024
50f8076
chore(k8s-version): Add changelog
Techassi Apr 19, 2024
b09ad5f
test(k8s-version): Add more unit tests
Techassi Apr 19, 2024
015ce8d
docs(k8s-version): Add README
Techassi Apr 19, 2024
9ba774f
chore: Switch work machine
Techassi Apr 19, 2024
03c32e7
Add basic support for renamed fields
Techassi Apr 22, 2024
5448375
Enfore version sorting, add option to opt out
Techassi Apr 22, 2024
cc2190a
Add basic support for deprecated fields
Techassi Apr 22, 2024
2eb23d7
Remove unused dependency
Techassi Apr 22, 2024
2586e77
Fix k8s-version unit tests
Techassi Apr 22, 2024
9f6c682
chore: Merge branch 'main' into feat/crd-versioning
Techassi Apr 22, 2024
2506fc4
Add basic support for multiple field actions on one field
Techassi Apr 22, 2024
957dd91
Restructure field validation code
Techassi Apr 23, 2024
cba9153
Generate chain of statuses
Techassi Apr 25, 2024
2269675
Merge branch 'main' into feat/crd-versioning
Techassi Apr 29, 2024
f3515e2
Add Ord impl for Level and Version
Techassi Apr 29, 2024
e324d30
Add Part(Ord) unit tests for Level and Version
Techassi Apr 29, 2024
3fa20f4
Add FromMeta unit test for Level
Techassi Apr 29, 2024
bd935d6
Generate code for multiple field actions
Techassi Apr 30, 2024
a9eeafd
Improve field attribute validation
Techassi Apr 30, 2024
0916a93
Improve error handling, add doc comments
Techassi Apr 30, 2024
727fbdf
k8s-version: Add validated Group
Techassi May 2, 2024
92fafcf
k8s-version: Add library doc comments
Techassi May 2, 2024
1952db8
k8s-version: Add doc comments for error enums
Techassi May 2, 2024
6148a09
Add more (doc) comments
Techassi May 3, 2024
3252c55
Add changelog for stackable-versioned
Techassi May 6, 2024
2393ae0
Apply suggestions
Techassi May 6, 2024
9e6fdef
Clean-up suggestions
Techassi May 6, 2024
8a38f47
Rename API_VERSION_REGEX to API_GROUP_REGEX
Techassi May 6, 2024
1aa7fcf
Use expect instead of unwrap for regular expressions
Techassi May 6, 2024
36279ff
Include duplicate version name in error message
Techassi May 6, 2024
cbf7c8c
Bump json-patch to 1.4.0 because 1.3.0 was yanked
Techassi May 6, 2024
0fdd492
Adjust level format
Techassi May 6, 2024
005c203
Improve derive macro test
Techassi May 6, 2024
4944a7f
Add how to use the ApiVersion::new() function
Techassi May 6, 2024
3a2e052
Add doc comment for FieldAttributes::validate_versions
Techassi May 6, 2024
232f883
Fix doc comment
Techassi May 6, 2024
ba3fc19
Fix doc tests
Techassi May 6, 2024
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ chrono = { version = "0.4.37", default-features = false }
clap = { version = "4.5.4", features = ["derive", "cargo", "env"] }
const_format = "0.2.32"
const-oid = "0.9.6"
convert_case = "0.6.0"
darling = "0.20.8"
delegate = "0.12.0"
derivative = "2.2.0"
Expand Down
17 changes: 17 additions & 0 deletions crates/stackable-versioned/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "stackable-versioned"
version = "0.1.0"
authors.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true

[lib]
proc-macro = true

[dependencies]
convert_case.workspace = true
darling.workspace = true
proc-macro2.workspace = true
syn.workspace = true
quote.workspace = true
68 changes: 68 additions & 0 deletions crates/stackable-versioned/src/attrs/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use darling::{
util::{Flag, SpannedValue},
Error, FromDeriveInput, FromMeta,
};

#[derive(Debug, FromDeriveInput)]
#[darling(
attributes(versioned),
supports(struct_named),
forward_attrs(allow, doc, cfg, serde),
and_then = ContainerAttributes::validate
)]
pub(crate) struct ContainerAttributes {
#[darling(multiple)]
pub(crate) version: SpannedValue<Vec<VersionAttributes>>,
}

impl ContainerAttributes {
fn validate(mut self) -> darling::Result<Self> {
if self.version.is_empty() {
return Err(Error::custom(
"attribute `#[versioned()]` must contain at least one `version`",
)
.with_span(&self.version.span()));
}

for version in &mut *self.version {
if version.name.is_empty() {
return Err(Error::custom("field `name` of `version` must not be empty")
.with_span(&version.name.span()));
}

if !version
.name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
{
return Err(Error::custom(
"field `name` of `version` must only contain alphanumeric ASCII characters (a-z, A-Z, 0-9, '.', '-')",
)
.with_span(&version.name.span()));
}

// TODO (@Techassi): Use Diagnostics API when stablizized to throw
// a warning when the input mismatches the generated module name.
// See https://github.com/rust-lang/rust/issues/54140
let module_name = version.name.to_lowercase();
if module_name != *version.name {
println!("the generated module name differs from the provided version name which might cause confusion around what the code seems to suggest")
}
version.module_name = module_name
}

Ok(self)
}
}

#[derive(Debug, FromMeta)]
pub struct VersionAttributes {
pub(crate) name: SpannedValue<String>,

pub(crate) deprecated: Flag,

#[darling(skip)]
pub(crate) module_name: String,
// #[darling(default = default_visibility)]
// pub(crate) visibility: Visibility,
}
1 change: 1 addition & 0 deletions crates/stackable-versioned/src/attrs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod container;
45 changes: 45 additions & 0 deletions crates/stackable-versioned/src/gen/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Error, Ident, Result};

use crate::attrs::container::ContainerAttributes;

pub(crate) mod version;

pub(crate) fn expand(input: DeriveInput) -> Result<TokenStream> {
// Extract container attributes
let attributes = ContainerAttributes::from_derive_input(&input)?;

// Validate container shape
let expanded = match input.data {
Data::Struct(data) => expand_struct(input.ident, data, attributes)?,
Data::Enum(data) => expand_enum(input.ident, data, attributes)?,
Data::Union(_) => {
return Err(Error::new(
input.span(),
"derive macro `Versioned` only supports structs and enums",
))
}
};

Ok(quote! {
#expanded
})
}

pub(crate) fn expand_struct(
ident: Ident,
data: DataStruct,
attributes: ContainerAttributes,
) -> Result<TokenStream> {
Ok(quote!())
}

pub(crate) fn expand_enum(
ident: Ident,
data: DataEnum,
attributes: ContainerAttributes,
) -> Result<TokenStream> {
Ok(quote!())
}
12 changes: 12 additions & 0 deletions crates/stackable-versioned/src/gen/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use syn::{Field, Ident};

pub(crate) struct Version {
struct_ident: Ident,
version: String,

deprecated: Vec<Field>,
renamed: Vec<Field>,
added: Vec<Field>,

fields: Vec<Field>,
}
14 changes: 14 additions & 0 deletions crates/stackable-versioned/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use proc_macro::TokenStream;
use syn::{DeriveInput, Error};

mod attrs;
mod gen;

#[proc_macro_derive(Versioned, attributes(versioned))]
pub fn versioned_macro_derive(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);

gen::expand(input)
.unwrap_or_else(Error::into_compile_error)
.into()
}
10 changes: 10 additions & 0 deletions crates/stackable-versioned/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use stackable_versioned::Versioned;

#[test]
fn basic() {
#[derive(Versioned)]
#[versioned(version(name = "1.2.3", deprecated))]
struct Foo {
bar: usize,
}
}
Loading