Skip to content

Commit 14ddaf4

Browse files
committed
feat: Add support for CustomResource derive
1 parent 0af2a26 commit 14ddaf4

File tree

9 files changed

+366
-124
lines changed

9 files changed

+366
-124
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stackable-versioned-macros/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,27 @@ repository.workspace = true
99
[lib]
1010
proc-macro = true
1111

12+
[features]
13+
full = ["k8s"]
14+
k8s = ["dep:kube", "dep:k8s-openapi"]
15+
1216
[dependencies]
1317
k8s-version = { path = "../k8s-version", features = ["darling"] }
1418

1519
convert_case.workspace = true
1620
darling.workspace = true
1721
itertools.workspace = true
22+
k8s-openapi = { workspace = true, optional = true }
23+
kube = { workspace = true, optional = true }
1824
proc-macro2.workspace = true
1925
strum.workspace = true
2026
syn.workspace = true
2127
quote.workspace = true
2228

2329
[dev-dependencies]
2430
rstest.workspace = true
31+
schemars.workspace = true
32+
serde.workspace = true
33+
serde_json.workspace = true
34+
serde_yaml.workspace = true
2535
trybuild.workspace = true

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

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ pub(crate) struct ContainerAttributes {
1919
#[darling(multiple, rename = "version")]
2020
pub(crate) versions: SpannedValue<Vec<VersionAttributes>>,
2121

22-
#[darling(default)]
23-
pub(crate) options: ContainerOptions,
22+
#[darling(rename = "k8s")]
23+
pub(crate) kubernetes_attrs: Option<KubernetesAttributes>,
24+
25+
#[darling(default, rename = "options")]
26+
pub(crate) common_option_attrs: OptionAttributes,
2427
}
2528

2629
impl ContainerAttributes {
@@ -43,7 +46,7 @@ impl ContainerAttributes {
4346

4447
// Ensure that versions are defined in sorted (ascending) order to keep
4548
// code consistent.
46-
if !self.options.allow_unsorted.is_present() {
49+
if !self.common_option_attrs.allow_unsorted.is_present() {
4750
let original = self.versions.deref().clone();
4851
self.versions
4952
.sort_by(|lhs, rhs| lhs.name.partial_cmp(&rhs.name).unwrap_or(Ordering::Equal));
@@ -71,20 +74,28 @@ impl ContainerAttributes {
7174
// place.
7275

7376
// Ensure every version is unique and isn't declared multiple times.
74-
let duplicates = self
77+
let duplicate_versions = self
7578
.versions
7679
.iter()
7780
.duplicates_by(|e| e.name)
7881
.map(|e| e.name)
7982
.join(", ");
8083

81-
if !duplicates.is_empty() {
84+
if !duplicate_versions.is_empty() {
8285
return Err(Error::custom(format!(
83-
"attribute macro `#[versioned()]` contains duplicate versions: {duplicates}",
86+
"attribute macro `#[versioned()]` contains duplicate versions: {duplicate_versions}",
8487
))
8588
.with_span(&self.versions.span()));
8689
}
8790

91+
// Ensure that the 'k8s' feature is enabled when the 'k8s()'
92+
// attribute is used.
93+
if self.kubernetes_attrs.is_some() && cfg!(not(feature = "k8s")) {
94+
return Err(Error::custom(
95+
"the `#[versioned(k8s())]` attribute can only be used when the `k8s` feature is enabled",
96+
));
97+
}
98+
8899
Ok(self)
89100
}
90101
}
@@ -101,29 +112,50 @@ impl ContainerAttributes {
101112
pub(crate) struct VersionAttributes {
102113
pub(crate) deprecated: Flag,
103114
pub(crate) name: Version,
104-
pub(crate) skip: Option<SkipOptions>,
115+
pub(crate) skip: Option<CommonSkipAttributes>,
105116
pub(crate) doc: Option<String>,
106117
}
107118

108-
/// This struct contains supported container options.
119+
/// This struct contains supported option attributes.
109120
///
110-
/// Supported options are:
121+
/// Supported attributes are:
111122
///
112123
/// - `allow_unsorted`, which allows declaring versions in unsorted order,
113124
/// instead of enforcing ascending order.
114125
/// - `skip` option to skip generating various pieces of code.
115126
#[derive(Clone, Debug, Default, FromMeta)]
116-
pub(crate) struct ContainerOptions {
127+
pub(crate) struct OptionAttributes {
117128
pub(crate) allow_unsorted: Flag,
118-
pub(crate) skip: Option<SkipOptions>,
129+
pub(crate) skip: Option<CommonSkipAttributes>,
119130
}
120131

121-
/// This struct contains supported skip options.
132+
/// This struct contains supported Kubernetes attributes.
122133
///
123-
/// Supported options are:
134+
/// Supported attributes are:
135+
///
136+
/// - `kind`, which allows overwriting the kind field of the CRD. This defaults
137+
/// to the struct name (without the 'Spec' suffix).
138+
/// - `group`, which sets the CRD group, usually the domain of the company.
139+
#[derive(Clone, Debug, FromMeta)]
140+
pub(crate) struct KubernetesAttributes {
141+
pub(crate) skip: Option<KubernetesSkipAttributes>,
142+
pub(crate) kind: Option<String>,
143+
pub(crate) group: String,
144+
}
145+
146+
#[derive(Clone, Debug, FromMeta)]
147+
pub(crate) struct KubernetesSkipAttributes {
148+
pub(crate) merged_crd: Flag,
149+
}
150+
151+
/// This struct contains supported common skip attributes.
152+
///
153+
/// Supported attributes are:
124154
///
125155
/// - `from` flag, which skips generating [`From`] implementations when provided.
126156
#[derive(Clone, Debug, Default, FromMeta)]
127-
pub(crate) struct SkipOptions {
157+
pub(crate) struct CommonSkipAttributes {
158+
/// Whether the [`From`] implementation generation should be skipped for all
159+
/// versions of this container.
128160
pub(crate) from: Flag,
129161
}

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

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::ops::Deref;
22

33
use proc_macro2::TokenStream;
4+
use quote::format_ident;
45
use syn::{Attribute, Ident, Visibility};
56

67
use crate::{attrs::common::ContainerAttributes, codegen::common::ContainerVersion};
@@ -32,6 +33,25 @@ where
3233
fn generate_tokens(&self) -> TokenStream;
3334
}
3435

36+
/// Provides extra functionality on top of [`Ident`]s.
37+
pub(crate) trait IdentExt {
38+
/// Removes the 'Spec' suffix from the [`Ident`].
39+
fn as_cleaned_kubernetes_ident(&self) -> Ident;
40+
41+
/// Transforms the [`Ident`] into one usable in the [`From`] impl.
42+
fn as_from_impl_ident(&self) -> Ident;
43+
}
44+
45+
impl IdentExt for Ident {
46+
fn as_cleaned_kubernetes_ident(&self) -> Ident {
47+
format_ident!("{}", self.to_string().trim_end_matches("Spec"))
48+
}
49+
50+
fn as_from_impl_ident(&self) -> Ident {
51+
format_ident!("__sv_{}", self.to_string().to_lowercase())
52+
}
53+
}
54+
3555
/// This struct bundles values from [`DeriveInput`][1].
3656
///
3757
/// [`DeriveInput`][1] cannot be used directly when constructing a
@@ -58,24 +78,95 @@ pub(crate) struct VersionedContainer<I> {
5878
/// definition with appropriate items.
5979
pub(crate) versions: Vec<ContainerVersion>,
6080

81+
/// The original attributes that were added to the container.
82+
pub(crate) original_attributes: Vec<Attribute>,
83+
84+
/// The visibility of the versioned container. Used to forward the
85+
/// visibility during code generation.
86+
pub(crate) visibility: Visibility,
87+
6188
/// List of items defined in the original container. How, and if, an item
6289
/// should generate code, is decided by the currently generated version.
6390
pub(crate) items: Vec<I>,
6491

65-
/// The ident, or name, of the versioned container.
66-
pub(crate) ident: Ident,
92+
/// Different options which influence code generation.
93+
pub(crate) options: VersionedContainerOptions,
6794

68-
/// The visibility of the versioned container. Used to forward the
69-
/// visibility during code generation.
70-
pub(crate) visibility: Visibility,
95+
/// A collection of container idents used for different purposes.
96+
pub(crate) idents: VersionedContainerIdents,
97+
}
7198

72-
/// The original attributes that were added to the container.
73-
pub(crate) original_attributes: Vec<Attribute>,
99+
impl<I> VersionedContainer<I> {
100+
/// Creates a new versioned Container which contains common data shared
101+
/// across structs and enums.
102+
pub(crate) fn new(
103+
input: ContainerInput,
104+
attributes: ContainerAttributes,
105+
versions: Vec<ContainerVersion>,
106+
items: Vec<I>,
107+
) -> Self {
108+
let ContainerInput {
109+
original_attributes,
110+
visibility,
111+
ident,
112+
} = input;
113+
114+
let skip_from = attributes
115+
.common_option_attrs
116+
.skip
117+
.map_or(false, |s| s.from.is_present());
118+
119+
let kubernetes_options = attributes.kubernetes_attrs.map(|a| KubernetesOptions {
120+
skip_merged_crd: a.skip.map_or(false, |s| s.merged_crd.is_present()),
121+
group: a.group,
122+
kind: a.kind,
123+
});
74124

75-
/// The name of the container used in `From` implementations.
76-
pub(crate) from_ident: Ident,
125+
let options = VersionedContainerOptions {
126+
kubernetes_options,
127+
skip_from,
128+
};
77129

78-
/// Whether the [`From`] implementation generation should be skipped for all
79-
/// versions of this container.
130+
let idents = VersionedContainerIdents {
131+
kubernetes: ident.as_cleaned_kubernetes_ident(),
132+
from: ident.as_from_impl_ident(),
133+
original: ident,
134+
};
135+
136+
VersionedContainer {
137+
original_attributes,
138+
visibility,
139+
versions,
140+
options,
141+
idents,
142+
items,
143+
}
144+
}
145+
}
146+
147+
/// A collection of container idents used for different purposes.
148+
#[derive(Debug)]
149+
pub(crate) struct VersionedContainerIdents {
150+
/// The ident used in the context of Kubernetes specific code. This ident
151+
/// removes the 'Spec' suffix present in the definition container.
152+
pub(crate) kubernetes: Ident,
153+
154+
/// The original ident, or name, of the versioned container.
155+
pub(crate) original: Ident,
156+
157+
/// The ident used in the [`From`] impl.
158+
pub(crate) from: Ident,
159+
}
160+
161+
#[derive(Debug)]
162+
pub(crate) struct VersionedContainerOptions {
163+
pub(crate) kubernetes_options: Option<KubernetesOptions>,
80164
pub(crate) skip_from: bool,
81165
}
166+
167+
#[derive(Debug)]
168+
pub(crate) struct KubernetesOptions {
169+
pub(crate) skip_merged_crd: bool,
170+
pub(crate) kind: Option<String>,
171+
pub(crate) group: String,
172+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ where
207207
let mut actions = BTreeMap::new();
208208

209209
for change in common_attributes.changes.iter().rev() {
210-
dbg!(&ty, &change.since);
211210
let from_ident = if let Some(from) = change.from_name.as_deref() {
212211
format_ident!("{from}")
213212
} else {

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ impl From<&ContainerAttributes> for Vec<ContainerVersion> {
7070
}
7171
}
7272

73-
/// Returns the container ident used in [`From`] implementations.
74-
pub(crate) fn format_container_from_ident(ident: &Ident) -> Ident {
75-
format_ident!("__sv_{ident}", ident = ident.to_string().to_lowercase())
76-
}
77-
7873
/// Removes the deprecated prefix from a field ident.
7974
///
8075
/// See [`DEPRECATED_FIELD_PREFIX`].

0 commit comments

Comments
 (0)