1use std::collections::HashMap;
2
3use darling::{
4 ast::{Data, Fields},
5 FromDeriveInput, FromField,
6};
7use helpers::{FieldIdent, FieldsExt, StructType};
8use indexmap::IndexMap;
9use itertools::Itertools;
10use params::{AttrParams, AttrValue};
11use proc_macro2::{Span, TokenStream};
12use quote::{quote, ToTokens};
13use syn::{ext::IdentExt, DeriveInput, Ident, Type};
14
15mod error;
16mod helpers;
17mod params;
18
19pub use error::Error;
20
21#[derive(FromDeriveInput, Debug)]
23#[darling(attributes(mapstic))]
24struct StructAttrs {
25 #[darling(default)]
27 mapping_type: Option<String>,
28
29 #[darling(default)]
32 params: HashMap<String, AttrValue>,
33
34 data: Data<(), FieldAttrs>,
39}
40
41#[derive(FromField, Debug)]
43#[darling(attributes(mapstic))]
44struct FieldAttrs {
45 ident: Option<Ident>,
47
48 ty: Type,
50
51 #[darling(default)]
53 mapping_type: Option<String>,
54
55 #[darling(default)]
57 skip: bool,
58
59 #[darling(default)]
62 params: HashMap<String, AttrValue>,
63}
64
65impl FieldIdent for FieldAttrs {
66 fn ident(&self) -> Option<&Ident> {
67 self.ident.as_ref()
68 }
69}
70
71pub fn to_mapping(input: TokenStream) -> Result<TokenStream, Error> {
73 let input: DeriveInput = match syn::parse2(input) {
74 Ok(input) => input,
75 Err(e) => return Ok(e.to_compile_error()),
76 };
77
78 let StructAttrs {
80 mapping_type,
81 params,
82 data,
83 } = match StructAttrs::from_derive_input(&input) {
84 Ok(attrs) => attrs,
85 Err(e) => return Ok(e.write_errors()),
86 };
87
88 let DeriveInput {
90 generics, ident, ..
91 } = input;
92 let (impl_generics, ty_generics, where_generics) = generics.split_for_impl();
93
94 let params = AttrParams::from(params);
96
97 if let Some(ty) = mapping_type {
98 Ok(quote! {
105 impl #impl_generics ::mapstic::ToMapping for #ident #ty_generics #where_generics {
106 fn to_mapping() -> ::mapstic::Mapping {
107 ::mapstic::Mapping::scalar(#ty, #params)
108 }
109
110 fn to_mapping_with_params(params: ::mapstic::Params) -> ::mapstic::Mapping {
111 let mut local = #params;
114 local.extend(params);
115
116 ::mapstic::Mapping::scalar(#ty, local)
117 }
118 }
119 })
120 } else {
121 let repr = match data {
123 Data::Struct(fields) => TypeRepr::try_from_fields(fields, params.iter())?,
124 Data::Enum(_) => return Err(Error::Enum),
127 };
128
129 match repr {
130 TypeRepr::Flat(mut field) => {
133 field.params.extend(params);
134
135 let inj = FieldWithInjectedParams {
142 field: &field,
143 ident: Ident::new("params", Span::call_site()),
144 };
145
146 Ok(quote! {
147 impl #impl_generics ::mapstic::ToMapping for #ident #ty_generics #where_generics {
148 fn to_mapping() -> ::mapstic::Mapping {
149 #field
150 }
151
152 fn to_mapping_with_params(mut params: ::mapstic::Params) -> ::mapstic::Mapping {
153 #inj
154 }
155 }
156 })
157 }
158 TypeRepr::Named(fields) => {
160 let param_extend = match params.is_empty() {
161 false => Some(quote! { params.extend(#params); }),
162 true => None,
163 };
164
165 Ok(quote! {
166 impl #impl_generics ::mapstic::ToMapping for #ident #ty_generics #where_generics {
167 fn to_mapping() -> ::mapstic::Mapping {
168 ::mapstic::Mapping::object(#fields.into_iter(), #params)
169 }
170
171 fn to_mapping_with_params(mut params: ::mapstic::Params) -> ::mapstic::Mapping {
172 #param_extend
173 ::mapstic::Mapping::object(#fields.into_iter(), params)
174 }
175 }
176 })
177 }
178 }
179 }
180}
181
182struct NamedFields(IndexMap<String, Field>);
184
185impl ToTokens for NamedFields {
186 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
187 let fields = self.0.iter().map(|(name, field)| {
190 quote! { (#name, #field) }
191 });
192
193 tokens.extend(quote! {
194 [
195 #( #fields ),*
196 ]
197 });
198 }
199}
200
201#[derive(Debug)]
203struct Field {
204 ty: FieldType,
205 params: AttrParams,
206}
207
208impl From<FieldAttrs> for Field {
209 fn from(opts: FieldAttrs) -> Self {
210 Self {
211 ty: match opts.mapping_type {
212 Some(ty) => FieldType::Explicit(ty),
213 None => FieldType::Inferred(opts.ty.into_token_stream()),
214 },
215 params: opts.params.into(),
216 }
217 }
218}
219
220impl ToTokens for Field {
221 fn to_tokens(&self, tokens: &mut TokenStream) {
222 let Field { ty, params } = self;
225
226 tokens.extend(match ty {
227 FieldType::Explicit(ty) => quote! {
228 ::mapstic::Mapping::scalar(#ty, #params)
229 },
230 FieldType::Inferred(ty) => {
231 quote! { <#ty as ::mapstic::ToMapping>::to_mapping_with_params(#params.into()) }
232 }
233 });
234 }
235}
236
237struct FieldWithInjectedParams<'a> {
243 field: &'a Field,
244 ident: Ident,
245}
246
247impl ToTokens for FieldWithInjectedParams<'_> {
248 fn to_tokens(&self, tokens: &mut TokenStream) {
249 let Self {
255 field: Field { ty, params },
256 ident,
257 } = self;
258
259 let params = quote! {
260 let mut local = #params;
261 local.extend(#ident);
262 };
263
264 tokens.extend(match ty {
265 FieldType::Explicit(ty) => quote! {{
266 #params
267 ::mapstic::Mapping::scalar(#ty, local)
268 }},
269 FieldType::Inferred(ty) => quote! {{
270 #params
271 <#ty as ::mapstic::ToMapping>::to_mapping_with_params(local)
272 }},
273 });
274 }
275}
276
277#[derive(Debug)]
280enum FieldType {
281 Explicit(String),
282 Inferred(TokenStream),
283}
284
285enum TypeRepr {
288 Flat(Field),
289 Named(NamedFields),
290}
291
292impl TypeRepr {
293 fn try_from_fields<'a, I, K>(
296 fields: Fields<FieldAttrs>,
297 container_params: I,
298 ) -> Result<Self, Error>
299 where
300 I: Iterator<Item = (K, &'a AttrValue)>,
301 K: ToString,
302 {
303 match fields.into_struct_type() {
304 StructType::Unit => Err(Error::UnitStruct),
305 StructType::TupleMultiple(_) => Err(Error::TupleStruct),
306 StructType::TupleSingle(opts) if opts.skip => Err(Error::AllSkipped),
307 StructType::TupleSingle(opts) => {
308 let mut field = Field::from(opts);
311 field
312 .params
313 .extend(container_params.map(|(k, v)| (k.to_string(), v.clone())));
314
315 Ok(Self::Flat(field))
316 }
317 StructType::Named(fields) => {
318 let types: IndexMap<_, _> = fields
319 .into_iter()
320 .filter(|opts| !opts.skip)
321 .map(|opts| match opts.ident {
322 Some(ref ident) => Ok((ident.unraw().to_string(), Field::from(opts))),
323 None => Err(Error::MixedStruct),
324 })
325 .try_collect()?;
326
327 if types.is_empty() {
328 Err(Error::AllSkipped)
329 } else {
330 Ok(Self::Named(NamedFields(types)))
331 }
332 }
333 }
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use std::str::FromStr;
340
341 use claims::assert_matches;
342
343 use super::*;
344
345 #[test]
346 fn all_skipped_named() {
347 let source = r#"
348 #[derive(ToMapping)]
349 struct Foo {
350 #[mapstic(skip)]
351 a: String,
352
353 #[mapstic(skip)]
354 b: String,
355 }
356 "#;
357 let stream = TokenStream::from_str(source).expect("parse input");
358
359 assert_matches!(to_mapping(stream), Err(Error::AllSkipped));
360 }
361
362 #[test]
363 fn all_skipped_tuple() {
364 let source = r#"
365 #[derive(ToMapping)]
366 struct Foo(#[mapstic(skip)] String);
367 "#;
368 let stream = TokenStream::from_str(source).expect("parse input");
369
370 assert_matches!(to_mapping(stream), Err(Error::AllSkipped));
371 }
372
373 #[test]
374 fn enum_fail() {
375 let source = r#"
376 #[derive(ToMapping)]
377 enum E { A, B }
378 "#;
379 let stream = TokenStream::from_str(source).expect("parse input");
380
381 assert_matches!(to_mapping(stream), Err(Error::Enum));
382 }
383
384 #[test]
385 fn tuple() {
386 let source = r#"
387 #[derive(ToMapping)]
388 struct Foo(String, String);
389 "#;
390 let stream = TokenStream::from_str(source).expect("parse input");
391
392 assert_matches!(to_mapping(stream), Err(Error::TupleStruct));
393 }
394
395 #[test]
396 fn unit() {
397 let source = r#"
398 #[derive(ToMapping)]
399 struct Foo;
400 "#;
401 let stream = TokenStream::from_str(source).expect("parse input");
402
403 assert_matches!(to_mapping(stream), Err(Error::UnitStruct));
404 }
405}