1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Error, Expr, Field, Fields, Meta, parse_macro_input};
4
5fn get_field_attrs<'a>(field: &'a Field) -> (bool, Option<&'a Expr>) {
6 let mut default_value = None;
7 let mut is_optional = false;
8 for attr in &field.attrs {
9 match &attr.meta {
10 Meta::NameValue(nv) => {
11 let Some(attr_ident) = nv.path.get_ident() else {
12 continue;
13 };
14
15 if attr_ident != "default_value" {
16 continue;
17 }
18
19 default_value = Some(&nv.value);
20 break;
21 },
22 Meta::Path(path) => {
23 let Some(ident) = path.get_ident() else {
24 continue;
25 };
26
27 if ident == "optional" {
28 is_optional = true;
29 break;
30 }
31 }
32 _ => ()
33 }
34 }
35
36 (is_optional, default_value)
37}
38
39#[proc_macro_derive(Cryptic, attributes(default_value, optional))]
40pub fn cryptic_derived(input: TokenStream) -> TokenStream {
46 let input = parse_macro_input!(input as DeriveInput);
47 let obj_name = input.ident;
48
49 let implementation = match &input.data {
50 Data::Struct(data_struct) => {
51 match &data_struct.fields {
52 Fields::Named(named_fields) => {
53 let field_construction = named_fields.named.iter().map(|f| {
54 let (is_optional, default_value) = get_field_attrs(f);
55 let ident = f.ident.as_ref().unwrap();
56 match default_value {
57 Some(def) => {
58 quote! {
59 #ident: mapping
60 .remove(stringify!(#ident))
61 .map(|i|
62 ::crypt_configs::Cryptic::cryptic(i))
63 .unwrap_or(Ok(match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse the default value for {}", stringify!(#ident)) })
64 )?,
65 }
66 }
67
68 None => {
69 if is_optional {
70 quote! {
71 #ident: match mapping.remove(stringify!(#ident)) {
72 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
73 None => None,
74 },
75 }
76 } else {
77 quote! {
78 #ident: match mapping.remove(stringify!(#ident)) {
79 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
80 None => return Err(::crypt_configs::CryptError::cannot_find_ident(stringify!(#ident))),
81 },
82 }
83 }
84 }
85 }
86 });
87
88 quote! {
89 let mut mapping: ::std::collections::HashMap<String, ::crypt_configs::TracedObject> = ::crypt_configs::Cryptic::cryptic(object)?;
90 Ok(Self {
91 #(#field_construction)*
92 })
93 }
94 },
95
96 Fields::Unnamed(unnammed_fields) => {
97 let field_construction = unnammed_fields.unnamed.iter().map(|f| {
98 let (is_optional, default_value) = get_field_attrs(f);
99 match default_value {
100 Some(def) => {
101 quote! {
102 match it.next() {
103 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
104 None => match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse default value") }
105 },
106 }
107 }
108
109 None => {
110 if is_optional {
111 quote! {
112 match it.next() {
113 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
114 None => None,
115 },
116 }
117 } else {
118 quote! {
119 match it.next() {
120 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
121 None => return Err(::crypt_configs::CryptError::not_enough_items())
122 },
123 }
124 }
125 }
126 }
127 });
128
129 quote! {
130 let items: Vec<::crypt_configs::TracedObject> = Cryptic::cryptic(object)?;
131 let mut it = items.into_iter();
132
133 Ok(Self(
134 #(#field_construction)*
135 ))
136 }
137 },
138
139 Fields::Unit =>
140 return TokenStream::from(Error::new(obj_name.span(), "Struct must have at least one attribute").to_compile_error())
141 }
142 },
143
144 Data::Enum(data_enum) => {
145 let variant_bodies = data_enum.variants.iter().map(|var| {
146 let var_ident = &var.ident;
147 match &var.fields {
148 Fields::Named(named_fields) => {
149 let field_construction = named_fields.named.iter().map(|f| {
150 let (is_optional, default_value) = get_field_attrs(f);
151 let ident = f.ident.as_ref().unwrap();
152 match default_value {
153 Some(def) => {
154 quote! {
155 #ident: mapping
156 .remove(stringify!(#ident))
157 .map(|i|
158 ::crypt_configs::Cryptic::cryptic(i))
159 .unwrap_or(Ok(match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse the default value for {}", stringify!(#ident)) })
160 )?,
161 }
162 }
163
164 None => {
165 if is_optional {
166 quote! {
167 #ident: match mapping.remove(stringify!(#ident)) {
168 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
169 None => None,
170 },
171 }
172 } else {
173 quote! {
174 #ident: match mapping.remove(stringify!(#ident)) {
175 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
176 None => return Err(::crypt_configs::CryptError::cannot_find_ident(stringify!(#ident))),
177 },
178 }
179 }
180 }
181 }
182 });
183
184 quote! {
185 {
186 let mut mapping: ::std::collections::HashMap<String, ::crypt_configs::TracedObject> = ::crypt_configs::Cryptic::cryptic(object)?;
187 Ok(Self::#var_ident {
188 #(#field_construction)*
189 })
190 }
191 }
192 },
193
194 Fields::Unnamed(unnammed_fields) => {
195 let field_construction = unnammed_fields.unnamed.iter().map(|f| {
196 let (is_optional, default_value) = get_field_attrs(f);
197 match default_value {
198 Some(def) => {
199 quote! {
200 match it.next() {
201 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
202 None => match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse default value") }
203 },
204 }
205 }
206
207 None => {
208 if is_optional {
209 quote! {
210 match it.next() {
211 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
212 None => None,
213 },
214 }
215 } else {
216 quote! {
217 match it.next() {
218 Some(v) => ::crypt_configs::Cryptic::cryptic(v)?,
219 None => return Err(::crypt_configs::CryptError::not_enough_items())
220 },
221 }
222 }
223 }
224 }
225 });
226
227 quote! {
228 {
229 let items: Vec<::crypt_configs::TracedObject> = Cryptic::cryptic(object)?;
230 let mut it = items.into_iter();
231
232 Ok(Self::#var_ident(
233 #(#field_construction)*
234 ))
235 }
236 }
237 },
238
239 Fields::Unit => quote! {
240 Ok(Self::#var_ident),
241 }
242 }
243 });
244
245 let variant_patterns = data_enum.variants.iter().map(|var| {
246 let var_ident = &var.ident;
247 quote! {stringify!(#var_ident)}
248 });
249
250 quote! {
251 let obj: ::crypt_configs::Tagged<String, ::crypt_configs::TracedObject> = ::crypt_configs::Cryptic::cryptic(object)?;
252 let object = obj.object;
253 match obj.tag.as_str() {
254 #(
255 #variant_patterns => #variant_bodies
256 )*
257
258 _ => Err(::crypt_configs::CryptError::invalid_enum_variant(obj.tag))
259 }
260 }
261 },
262
263 Data::Union(_) => {
264 return TokenStream::from(Error::new(obj_name.span(), "Only structs and enums can derive Cryptic").to_compile_error())
265 }
266 };
267
268 TokenStream::from(quote! {
269 impl ::crypt_configs::Cryptic for #obj_name {
270 fn cryptic(object: ::crypt_configs::TracedObject) -> Result<Self, ::crypt_configs::CryptError> {
271 #implementation
272 }
273 }
274 })
275}