1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 parse_macro_input,
5 Data::Enum,
6 DeriveInput,
7 Ident
8};
9
10#[proc_macro_derive(ConditionType, attributes(dependent))]
35pub fn derive(input: TokenStream) -> TokenStream {
36 let ast = parse_macro_input!(input as DeriveInput);
37
38 let name = &ast.ident;
39 let variants = match ast.data {
40 Enum(syn::DataEnum { ref variants, .. }) => variants,
41 _ => panic!("ConditionType may only be derived on enums")
42 };
43
44 let required_variants = ["Ready", "Succeeded"];
45 let dependents = variants.clone().into_iter()
46 .filter(|v| v.attrs.iter().any(|a| a.path.segments.iter().any(|p| p.ident == "dependent")))
47 .map(|v| v.ident);
48 let dependents_again = dependents.clone();
49
50 let variant_strings: Vec<String> = variants.iter().map(|v| v.ident.to_string()).collect();
51 let dependent_strings: Vec<String> = dependents.clone().map(|d| d.to_string()).collect();
52
53 if let Some(required) = dependent_strings.iter().find(|d| required_variants.contains(&d.as_str())) {
54 panic!("{} variant may not be a dependent", required)
55 }
56
57 if required_variants.iter().all(|req| variant_strings.iter().any(|v| v == *req)) {
59 panic!("ConditionType may only contain one of either Ready or Succeeded variants")
60 }
61
62 let happy = variants.iter().find(|v| required_variants.contains(&v.ident.to_string().as_str()))
63 .expect("ConditionType must contain either Ready or Succeeded variant");
64 let capitalized = variants.iter()
65 .map(|v| v.ident.clone())
66 .filter(|v| !required_variants.contains(&v.to_string().as_str()));
67 let lower_case = capitalized.clone()
68 .map(|v| Ident::new(&v.to_string().to_lowercase(), v.span()));
69 let lower_case_doc = capitalized.clone().map(|c| format!("Returns the `{c}` variant of the [`ConditionType`]"));
70 let lower_case_again = lower_case.clone();
71 let lower_case_again_again = lower_case.clone();
72
73 let mark = lower_case.clone().map(|l| Ident::new(&format!("mark_{l}"), l.span()));
74 let mark_with_reason = lower_case.clone().map(|l| Ident::new(&format!("mark_{l}_with_reason"), l.span()));
75 let mark_not = lower_case.clone().map(|l| Ident::new(&format!("mark_not_{l}"), l.span()));
76
77 let condition_type_name = Ident::new(&format!("{name}Type"), name.span());
78 let condition_type_doc = format!("A [`ConditionType`] that implement this trait duck types to [`{name}`].");
79 let manager_name = Ident::new(&format!("{name}Manager"), name.span());
80 let manager_doc = format!("Allows a status to manage [`{name}`].");
81
82 quote! {
83 #[doc = #condition_type_doc]
84 pub trait #condition_type_name: ::knative_conditions::ConditionType {
85 #(
86 #[doc = #lower_case_doc]
87 fn #lower_case() -> Self;
88 )*
89 }
90
91 #[automatically_derived]
92 impl #condition_type_name for #name {
93 #(
94 fn #lower_case_again() -> Self {
95 #name::#capitalized
96 }
97 )*
98 }
99
100 #[automatically_derived]
101 impl ::knative_conditions::ConditionType for #name {
102 fn happy() -> Self {
103 #name::#happy
104 }
105
106 fn dependents() -> &'static [Self] {
107 &[#(#name::#dependents_again),*]
108 }
109 }
110
111 #[automatically_derived]
112 impl Default for #name {
113 fn default() -> Self {
114 #name::#happy
115 }
116 }
117
118 #[doc = #manager_doc]
119 pub trait #manager_name<S>: ::knative_conditions::ConditionAccessor<S>
120 where S: #condition_type_name {
121 #(
122 fn #mark(&mut self) {
123 self.manager().mark_true(S::#lower_case_again_again());
124 }
125
126 fn #mark_with_reason(&mut self, reason: &str, message: Option<String>) {
127 self.manager().mark_true_with_reason(S::#lower_case_again_again(), reason, message);
128 }
129
130 fn #mark_not(&mut self, reason: &str, message: Option<String>) {
131 self.manager().mark_false(S::#lower_case_again_again(), reason, message);
132 }
133 )*
134 }
135
136 impl<S: #condition_type_name, T: ::knative_conditions::ConditionAccessor<S> + ?Sized> #manager_name<S> for T {}
137 }.into()
138}
139