@@ -7,7 +7,7 @@ use darling::{
77use proc_macro2:: { Ident , Literal , Span , TokenStream } ;
88use quote:: { ToTokens , TokenStreamExt as _} ;
99use 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+
325365pub ( 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" ) ;
0 commit comments