Skip to content

Commit a12559d

Browse files
authored
Merge pull request #1850 from ngergs/kube-derive-cr-root-attr
feat: add #[kube(attr="...")] attribute helper to set attribute helper on the CR root type
2 parents a44dd1c + 6fce7d4 commit a12559d

File tree

7 files changed

+127
-1
lines changed

7 files changed

+127
-1
lines changed

examples/crd_derive.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use serde::{Deserialize, Serialize};
2121
status = "FooStatus",
2222
derive = "PartialEq",
2323
derive = "Default",
24+
attr = "allow(deprecated)",
25+
attr = "cfg_attr(docsrs,doc(cfg(feature = \"latest\")))",
2426
shortname = "f",
2527
scale(
2628
spec_replicas_path = ".spec.replicas",

kube-derive/src/custom_resource.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use darling::{
77
use proc_macro2::{Ident, Literal, Span, TokenStream};
88
use quote::{ToTokens, TokenStreamExt as _};
99
use serde::Deserialize;
10-
use syn::{parse_quote, Data, DeriveInput, Expr, Path, Visibility};
10+
use syn::{parse_quote, Data, DeriveInput, Expr, Meta, Path, Visibility};
1111

1212
/// Values we can parse from #[kube(attrs)]
1313
#[derive(Debug, FromDeriveInput)]
@@ -27,6 +27,8 @@ struct KubeAttrs {
2727
namespaced: bool,
2828
#[darling(multiple, rename = "derive")]
2929
derives: Vec<String>,
30+
#[darling(multiple, rename = "attr")]
31+
attributes: Vec<KubeRootMeta>,
3032
schema: Option<SchemaMode>,
3133
status: Option<Path>,
3234
#[darling(multiple, rename = "category")]
@@ -322,6 +324,44 @@ impl Scale {
322324
}
323325
}
324326

327+
/// Attribute meta that should be added to the root of the custom resource.
328+
/// Wrapper around `Meta` to implement custom validation logic for `darling`.
329+
/// The validation rejects attributes for `derive`, `serde` and `schemars`.
330+
/// For `derive` there is `#[kube(derive=...)]` which does specialized handling
331+
/// and for `serde` and `schemars` allowing to set attributes could result in conflicts
332+
/// or unexpected behaviour with respect to other parts of the generated code.
333+
#[derive(Debug)]
334+
struct KubeRootMeta(Meta);
335+
336+
impl ToTokens for KubeRootMeta {
337+
fn to_tokens(&self, tokens: &mut TokenStream) {
338+
self.0.to_tokens(tokens);
339+
}
340+
}
341+
342+
impl FromMeta for KubeRootMeta {
343+
fn from_string(value: &str) -> darling::Result<Self> {
344+
/// Attributes that are not allowed to be set via `#[kube(attr=...)]`.
345+
const NOT_ALLOWED_ATTRIBUTES: [&str; 3] = ["derive", "serde", "schemars"];
346+
347+
let meta = syn::parse_str::<Meta>(value)?;
348+
if let Some(ident) = meta.path().get_ident() {
349+
if NOT_ALLOWED_ATTRIBUTES.iter().any(|el| ident == el) {
350+
if ident == "derive" {
351+
return Err(darling::Error::custom(
352+
r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set derives, you likely want to use `kube(derive = "...")`."#,
353+
));
354+
}
355+
return Err(darling::Error::custom(format!(
356+
r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set the attributes {NOT_ALLOWED_ATTRIBUTES:?} as they might lead to unexpected behaviour.`"#,
357+
)));
358+
}
359+
}
360+
361+
Ok(Self(meta))
362+
}
363+
}
364+
325365
pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
326366
let derive_input: DeriveInput = match syn::parse2(input) {
327367
Err(err) => return err.to_compile_error(),
@@ -351,6 +391,7 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
351391
version,
352392
doc,
353393
namespaced,
394+
attributes,
354395
derives,
355396
schema: schema_mode,
356397
status,
@@ -476,6 +517,7 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
476517
#[automatically_derived]
477518
#[allow(missing_docs)]
478519
#[derive(#(#derive_paths),*)]
520+
#(#[#attributes])*
479521
#[serde(rename_all = "camelCase")]
480522
#[serde(crate = #quoted_serde)]
481523
#schemars_attribute
@@ -902,6 +944,36 @@ mod tests {
902944
assert!(kube_attrs.namespaced);
903945
}
904946

947+
#[test]
948+
// The error cases are handled in tests/test_ui together with validating that the error messages are helpful
949+
fn test_parse_attribute_ok() {
950+
let input = quote! {
951+
#[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)]
952+
#[kube(group = "clux.dev", version = "v1", kind = "Foo", namespaced)]
953+
#[kube(attr="hello")]
954+
#[kube(attr="hello2")]
955+
struct FooSpec { foo: String }
956+
};
957+
let input = syn::parse2(input).unwrap();
958+
let kube_attrs = KubeAttrs::from_derive_input(&input).unwrap();
959+
assert_eq!(kube_attrs.group, "clux.dev".to_string());
960+
assert_eq!(kube_attrs.version, "v1".to_string());
961+
assert_eq!(kube_attrs.kind, "Foo".to_string());
962+
assert!(kube_attrs.namespaced);
963+
964+
let expected_attrs = ["hello", "hello2"];
965+
assert_eq!(kube_attrs.attributes.len(), expected_attrs.len());
966+
for (i, attr) in kube_attrs
967+
.attributes
968+
.into_iter()
969+
.map(|el| el.to_token_stream().to_string())
970+
.enumerate()
971+
{
972+
assert_eq!(attr, expected_attrs[i],);
973+
}
974+
}
975+
976+
905977
#[test]
906978
fn test_derive_crd() {
907979
let path = env::current_dir().unwrap().join("tests").join("crd_enum_test.rs");

kube-derive/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ mod resource;
113113
/// Adding `#[kube(derive = "PartialEq")]` is required if you want your generated
114114
/// top level type to be able to `#[derive(PartialEq)]`
115115
///
116+
/// ## `#[kube(attr = "attribute")]`
117+
/// Adding `#[kube(attr = "attribute")]` is required if you want your generated
118+
/// top level type to have the `#[attribute]` type level attribute added.
119+
/// The attributes will be added after the `#[derive(...)]` attribute of the root type.
120+
/// The added attributes can either attribute macros or derive macro helper attributes.
121+
///
122+
/// Does not allow to set the `derive`, `serde` or `schemars` derive helper attributes.
123+
/// To add `derive`s the correct way to set it is `#[kube(derive = "Trait")]`.
124+
/// Setting derive helper attributes for `serde` and `schemars` is not supported as
125+
/// it might yield unexpected behaviour when interacting with the other generated code.
126+
///
127+
/// ```ignore
128+
/// #[kube(
129+
/// attr="allow(deprecated)",
130+
/// attr="cfg_attr(docsrs,doc(cfg(feature = \"latest\")))",
131+
/// )]
132+
/// ```
133+
///
116134
/// ## `#[kube(schema = "mode")]`
117135
/// Defines whether the `JsonSchema` of the top level generated type should be used when generating a `CustomResourceDefinition`.
118136
///
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![allow(missing_docs)]
2+
3+
use kube_derive::CustomResource;
4+
5+
#[derive(CustomResource)]
6+
#[kube(group = "clux.dev", version = "v1", kind = "FooEnum")]
7+
#[kube(attr = "derive(Serialize)")]
8+
struct FooSpec {
9+
int: u32,
10+
}
11+
12+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: #[derive(CustomResource)] `kube(attr = "...")` does not support to set derives, you likely want to use `kube(derive = "...")`.
2+
--> tests/ui/attr_derive_not_allowed.rs:7:15
3+
|
4+
7 | #[kube(attr = "derive(Serialize)")]
5+
| ^^^^^^^^^^^^^^^^^^^
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![allow(missing_docs)]
2+
3+
use kube_derive::CustomResource;
4+
5+
#[derive(CustomResource)]
6+
#[kube(group = "clux.dev", version = "v1", kind = "FooEnum")]
7+
#[kube(attr = "serde(rename_all=\"snake_case\")")]
8+
struct FooSpec {
9+
int: u32,
10+
}
11+
12+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: #[derive(CustomResource)] `kube(attr = "...")` does not support to set the attributes ["derive", "serde", "schemars"] as they might lead to unexpected behaviour.`
2+
--> tests/ui/attr_serde_not_allowed.rs:7:15
3+
|
4+
7 | #[kube(attr = "serde(rename_all=\"snake_case\")")]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)