context_mapper_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::TokenTree;
4use quote::{quote, ToTokens};
5use std::collections::BTreeMap;
6use std::collections::HashMap;
7use syn::parse_macro_input;
8use syn::punctuated::Punctuated;
9use syn::token::PathSep;
10use syn::DeriveInput;
11use syn::Field;
12use syn::Fields;
13use syn::Ident;
14use syn::PathSegment;
15use syn::Token;
16
17#[derive(Clone, Debug)]
18struct Member {
19    ident: Ident,
20    name: String,
21    rename: HashMap<syn::Path, String>,
22    converter: HashMap<syn::Path, syn::Path>,
23    skip_contexts: Vec<syn::Path>,
24    move_contexts: Vec<syn::Path>,
25}
26
27type Members = BTreeMap<String, Member>;
28
29#[derive(Debug)]
30enum TraitNaming {
31    /// Existing traits have to be (&self) -> HashMap<String, ContextType>
32    ExistingGeneric {
33        path: syn::Path,
34        method_name: Ident,
35    },
36    Existing {
37        path: syn::Path,
38        method_name: Ident,
39    },
40    // Use generate_mapper_trait!(name_of_the_trait, method_name)
41    // Use generate_mapper_trait_generic!(name_of_the_trait, method_name)
42    //
43    // Generate {
44    //     /// By default IntoType
45    //     name: Option<Ident>,
46    //     /// By default into_type_map
47    //     method_name: Option<Ident>,
48    // },
49    // GenerateGeneric {
50    //     /// By default IntoType
51    //     name: Option<Ident>,
52    //     /// By default into_type_map
53    //     method_name: Option<Ident>,
54    // },
55}
56
57impl Default for TraitNaming {
58    fn default() -> Self {
59        Self::ExistingGeneric {
60            path: syn::parse2(quote! {::context_mapper::IntoType}).expect("1"),
61            method_name: syn::parse2(quote! {into_type_map}).expect("2"),
62        }
63    }
64}
65
66#[derive(Debug)]
67struct CommonFields {
68    ctx: syn::Path,
69    ctx_type: syn::Type,
70    converter: syn::Path,
71    move_type: bool,
72}
73
74#[derive(Debug)]
75struct Trait {
76    ctx: syn::Path,
77    ctx_type: syn::Type,
78    converter: syn::Path,
79    move_type: bool,
80    naming: TraitNaming,
81}
82
83#[derive(Debug)]
84struct Function {
85    ctx: syn::Path,
86    ctx_type: syn::Type,
87    converter: syn::Path,
88    move_type: bool,
89    naming: Ident,
90    visibility: syn::Visibility,
91}
92
93#[derive(Debug, Default)]
94struct ContextConfig {
95    /// By default traits are using IntoType from the context-mapper crate
96    pub traits: BTreeMap<String, Trait>,
97    pub functions: BTreeMap<String, Function>,
98    pub impls: BTreeMap<String, Function>
99}
100
101fn read_trait_naming(inpname: &str, input: &proc_macro2::TokenStream) -> TraitNaming {
102    let params = group_attributes(
103        input.clone(),
104        &[],
105        &["path", "method_name"]
106            .into_iter()
107            .map(|x| x.to_string())
108            .collect::<Vec<String>>(),
109    );
110
111    let path: Option<syn::Path> = params
112        .iter()
113        .filter(|x| x.0 == "path")
114        .last()
115        .map(|x| syn::parse2(x.1.clone()).expect("name should be parseable to ident"));
116
117    let method_name: Option<syn::Ident> = params
118        .iter()
119        .filter(|x| x.0 == "method_name")
120        .last()
121        .map(|x| syn::parse2(x.1.clone()).expect("method_name should be parseable to ident"));
122
123    match inpname {
124        "simple" => TraitNaming::Existing {
125            path: path.expect("Expected path"),
126            method_name: method_name.expect("Excepected mehtod_name"),
127        },
128        "generic" => TraitNaming::ExistingGeneric {
129            path: path.expect("Expected path"),
130            method_name: method_name.expect("Expected method_name"),
131        },
132        _ => unreachable!("Unknown naming"),
133    }
134}
135
136fn read_common_fields(groups: &[(String, proc_macro2::TokenStream)]) -> CommonFields {
137    let ctx: syn::Path = groups
138        .iter()
139        .filter(|x| x.0 == "context")
140        .last()
141        .map(|x| syn::parse2(x.1.clone()).expect("context mapping error"))
142        .unwrap_or(syn::parse2(quote! { default }).expect("?"));
143
144    let ctx_type: syn::Type = groups
145        .iter()
146        .filter(|x| x.0 == "type")
147        .last()
148        .map(|x| syn::parse2(x.1.clone()).expect("type mapping error"))
149        .unwrap_or(syn::parse2(quote! { std::string::String }).expect("?"));
150
151    let converter: syn::Path = groups
152        .iter()
153        .filter(|x| x.0 == "converter")
154        .last()
155        .map(|x| syn::parse2(x.1.clone()).expect("converter mapping errro"))
156        .unwrap_or(syn::parse2(quote! { std::string::ToString::to_string }).expect("?"));
157
158    let move_type = groups
159        .iter()
160        .filter_map(|x| {
161            if x.0 == "move" {
162                let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
163                Some(b.value)
164            } else {
165                None
166            }
167        })
168        .last()
169        .unwrap_or(false);
170
171    CommonFields {
172        ctx,
173        ctx_type,
174        converter,
175        move_type,
176    }
177}
178
179impl ContextConfig {
180    pub fn add_trait(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
181        let grp_attrs: Vec<String> = ["simple", "generic"]
182            .into_iter()
183            .map(|x| x.into())
184            .collect();
185
186        let groups = group_attributes(
187            input,
188            &grp_attrs,
189            &["context", "type", "converter", "move"]
190                .into_iter()
191                .map(|x| x.into())
192                .collect::<Vec<String>>(),
193        );
194
195        let naming_base = groups
196            .iter()
197            .filter(|x| grp_attrs.contains(&x.0))
198            .last()
199            .clone();
200
201        let naming = naming_base
202            .map(|x| read_trait_naming(&x.0, &x.1))
203            .unwrap_or(TraitNaming::default());
204
205        let common_fields = read_common_fields(&groups);
206
207        self.traits.insert(
208            common_fields.ctx.to_token_stream().to_string(),
209            Trait {
210                ctx: common_fields.ctx,
211                ctx_type: common_fields.ctx_type,
212                converter: common_fields.converter,
213                naming,
214                move_type: common_fields.move_type,
215            },
216        );
217
218        self
219    }
220    fn add_fun_base(input: proc_macro2::TokenStream) -> Function {
221        let groups = group_attributes(
222            input,
223            &[],
224            &[
225                "context",
226                "type",
227                "converter",
228                "move",
229                "naming",
230                "fn",
231                "visibility",
232                "vis",
233            ]
234            .into_iter()
235            .map(|x| x.into())
236            .collect::<Vec<String>>(),
237        );
238
239        let common_fields = read_common_fields(&groups);
240
241        let naming: syn::Ident = groups
242            .iter()
243            .filter(|x| x.0 == "naming" || x.0 == "fn")
244            .last()
245            .map(|x| syn::parse2(x.1.clone()).expect("naming mapping error"))
246            .expect("naming field undefined for funcion mapping");
247
248        let visibility: syn::Visibility = groups
249            .iter()
250            .filter(|x| x.0 == "visibility" || x.0 == "vis")
251            .last()
252            .map(|x| syn::parse2(x.1.clone()).expect("visibility mapping error"))
253            .unwrap_or(syn::Visibility::Inherited);
254
255        Function {
256            ctx: common_fields.ctx,
257            ctx_type: common_fields.ctx_type,
258            converter: common_fields.converter,
259            move_type: common_fields.move_type,
260            naming,
261            visibility,
262        }
263    }
264    pub fn add_function(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
265        let base = Self::add_fun_base(input);
266
267        self.functions.insert(
268            base.ctx.to_token_stream().to_string(),
269            base
270        );
271
272        self
273    }
274
275    pub fn add_impl(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
276        let base = Self::add_fun_base(input);
277
278        self.impls.insert(
279            base.ctx.to_token_stream().to_string(),
280            base
281        );
282
283        self
284    }
285}
286
287fn path_is_allowed(ident: syn::Path, allowed: &[String]) -> bool {
288    allowed.contains(&ident.to_token_stream().to_string())
289}
290
291fn group_attributes(
292    input: proc_macro2::TokenStream,
293    allowed_group_idents: &[String],
294    allowed_eq_idents: &[String],
295) -> Vec<(String, proc_macro2::TokenStream)> {
296    let mut groups: Vec<(String, proc_macro2::TokenStream)> = Vec::new();
297
298    let mut last_eq = false;
299    let mut last_path: syn::Path = syn::Path {
300        leading_colon: None,
301        segments: Punctuated::new(),
302    };
303    let mut last_stream: Vec<TokenTree> = Vec::new();
304    let iterator = input.into_iter();
305
306    for tk in iterator {
307        if last_eq {
308            match tk.clone() {
309                TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_) => {
310                    last_stream.push(tk);
311                    continue;
312                }
313                TokenTree::Punct(x) if x.to_string() != "," => {
314                    last_stream.push(x.into());
315                    continue;
316                }
317                _ => last_eq = false,
318            }
319        }
320        match tk {
321            TokenTree::Group(group) => {
322                if path_is_allowed(last_path.clone(), &allowed_group_idents) {
323                    let last_path = last_path.clone().to_token_stream().to_string();
324                    groups.push((last_path, group.stream()));
325                } else {
326                    let loc = &group.span().source_text().unwrap();
327                    let last_ident = last_path.into_token_stream().to_string();
328                    panic!("Unknown identifier detected ({last_ident}); allowed: {allowed_group_idents:?}\n{loc}");
329                }
330            }
331            TokenTree::Ident(ident) => {
332                last_path.segments.push(PathSegment::from(ident));
333            }
334            TokenTree::Punct(punct) => match punct.as_char() {
335                ',' => {
336                    if !last_stream.is_empty() {
337                        groups.push((
338                            last_path.clone().into_token_stream().to_string(),
339                            proc_macro2::TokenStream::from_iter(last_stream.clone().into_iter()),
340                        ));
341                    } else {
342                        if !last_path.segments.is_empty()
343                            && path_is_allowed(last_path.clone(), &allowed_eq_idents)
344                        {
345                            groups.push((
346                                last_path.clone().into_token_stream().to_string(),
347                                quote! {true},
348                            ));
349                        }
350                    }
351                    last_stream.clear();
352                    last_path.segments.clear()
353                }
354                '.' | ':' => {
355                    if !last_path.segments.trailing_punct() {
356                        last_path.segments.push_punct(PathSep::default())
357                    }
358                }
359                '=' => {
360                    if path_is_allowed(last_path.clone(), &allowed_eq_idents) {
361                        last_eq = true
362                    } else {
363                        let lp = last_path.to_token_stream().to_string();
364                        panic!("Identifier {lp:?} not allowed. Allowed:
365                       {allowed_eq_idents:?}")
366                    }
367                }
368                _ => panic!("Unknown punctuation detected"),
369            },
370            TokenTree::Literal(_) => panic!("Literal not allowed in the context definitions"),
371        }
372    }
373    if !last_stream.is_empty() {
374        groups.push((
375            last_path.clone().into_token_stream().to_string(),
376            proc_macro2::TokenStream::from_iter(last_stream.clone().into_iter()),
377        ));
378    } else {
379        if !last_path.segments.is_empty() && path_is_allowed(last_path.clone(), &allowed_eq_idents)
380        {
381            groups.push((
382                last_path.clone().into_token_stream().to_string(),
383                quote! {true},
384            ));
385        }
386    }
387
388    groups
389}
390
391fn read_struct_config(input: &DeriveInput) -> ContextConfig {
392    let mut result = ContextConfig::default();
393
394    for attribute in input.attrs.clone().into_iter().filter_map(|x| {
395        if x.path().is_ident("context_mapper") {
396            Some(x.meta.require_list().and_then(|y| Ok(y.tokens.clone())))
397        } else {
398            None
399        }
400    }) {
401        let groups = group_attributes(
402            attribute.expect("Expected list of attributes"),
403            &["trait", "function", "impl"]
404                .into_iter()
405                .map(|x| x.into())
406                .collect::<Vec<String>>(),
407            &[],
408        );
409
410        for group in groups {
411            match group.0.as_ref() {
412                "trait" => result.add_trait(group.1),
413                "function" => result.add_function(group.1),
414                "impl" => result.add_impl(group.1),
415                _ => unreachable!("Attribtues not parsed properly"),
416            };
417        }
418    }
419
420    result
421}
422
423fn read_member(member: &Field) -> Member {
424    let ident = member.ident.clone().unwrap();
425
426    let mut result = Member {
427        ident: ident.clone(),
428        name: ident.to_string(),
429        rename: Default::default(),
430        skip_contexts: Default::default(),
431        converter: Default::default(),
432        move_contexts: Default::default(),
433    };
434
435    for config in member.attrs.clone().into_iter().filter_map(|x| {
436        if x.path().is_ident("context_attribute") {
437            Some(x.meta.require_list().and_then(|y| Ok(y.tokens.clone())))
438        } else {
439            None
440        }
441    }) {
442        let groups = group_attributes(
443            config.expect("Expected list of attribtues for {ident}"),
444            &["context".to_string()],
445            &[],
446        );
447
448        for group in groups {
449            let attrs = group_attributes(
450                group.1,
451                &[],
452                &["name", "skip", "converter", "rename", "move"]
453                    .into_iter()
454                    .map(|x| x.into())
455                    .collect::<Vec<String>>(),
456            );
457
458            let name: syn::Path = syn::parse2(
459                attrs
460                    .iter()
461                    .filter_map(|x| {
462                        if x.0 == "name" {
463                            Some(x.1.clone())
464                        } else {
465                            None
466                        }
467                    })
468                    .last()
469                    .unwrap_or(quote! { default }),
470            )
471            .unwrap();
472
473            let skip = attrs
474                .iter()
475                .filter_map(|x| {
476                    if x.0 == "skip" {
477                        let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
478                        Some(b.value)
479                    } else {
480                        None
481                    }
482                })
483                .last()
484                .unwrap_or(false);
485
486            let converter: Option<syn::Path> = attrs
487                .iter()
488                .filter(|x| x.0 == "converter")
489                .last()
490                .map(|x| syn::parse2(x.1.clone()).expect("converter mapping errro"));
491
492            let rename = attrs
493                .iter()
494                .filter_map(|x| {
495                    if x.0 == "rename" {
496                        let b: syn::LitStr = syn::parse2(x.1.clone()).expect("Rename expected str");
497                        Some(b.value())
498                    } else {
499                        None
500                    }
501                })
502                .last();
503
504            let move_type = attrs
505                .iter()
506                .filter_map(|x| {
507                    if x.0 == "move" {
508                        let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
509                        Some(b.value)
510                    } else {
511                        None
512                    }
513                })
514                .last()
515                .unwrap_or(false);
516
517            if skip {
518                result.skip_contexts.push(name.clone());
519            }
520            if let Some(converter) = converter {
521                result.converter.insert(name.clone(), converter);
522            }
523            if let Some(rename) = rename {
524                result.rename.insert(name.clone(), rename);
525            }
526            if move_type {
527                result.move_contexts.push(name);
528            }
529        }
530    }
531
532    result
533}
534
535fn read_members(input: &DeriveInput) -> Members {
536    let mut members = Members::new();
537
538    match &input.data {
539        syn::Data::Struct(x) => match &x.fields {
540            Fields::Named(named) => {
541                for field in named.named.iter() {
542                    let member = read_member(field);
543                    members.insert(member.name.clone(), member);
544                }
545            }
546            Fields::Unnamed(_) => panic!("Unnamed fields are currently unsupported"),
547            Fields::Unit => panic!("Unit structs are unsupported"),
548        },
549        _ => panic!("Currently only structs are supported"),
550    }
551
552    members
553}
554
555fn generate_impls(
556    name: &Ident,
557    config: &ContextConfig,
558    members: &Members,
559) -> proc_macro2::TokenStream {
560    let mut impls: Vec<proc_macro2::TokenStream> = Vec::new();
561
562    for trt in config.impls.iter() {
563        let ctx = &trt.1.ctx;
564        let ctx_type = &trt.1.ctx_type;
565        let naming = &trt.1.naming;
566        let visibility = &trt.1.visibility;
567
568        let members_code = members.iter().fold(
569            quote! {
570                let mut result = std::collections::HashMap::new();
571            },
572            |acc, member| {
573                if member.1.skip_contexts.contains(ctx) {
574                    acc
575                } else {
576                    let field = &member.1.ident;
577                    let name = member
578                        .1
579                        .rename
580                        .get(ctx)
581                        .cloned()
582                        .unwrap_or(member.1.name.clone());
583                    let converter = member
584                        .1
585                        .converter
586                        .get(ctx)
587                        .cloned()
588                        .unwrap_or(trt.1.converter.clone());
589
590                    let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
591                        quote! {}
592                    } else {
593                        quote! {&}
594                    };
595
596                    quote! {
597                        #acc
598                        result.insert(#name.to_string(), #converter(#move_type self.#field));
599                    }
600                }
601            },
602        );
603        let members_code = quote! {
604            #members_code
605            result
606        };
607
608        impls.push(quote! {
609            #visibility fn #naming(&self) -> std::collections::HashMap<String, #ctx_type> {
610                #members_code
611            }
612        })
613    }
614
615    let partial = impls.into_iter().fold(quote! {}, |acc, x| {
616        quote! {
617            #acc
618            #x
619        }
620    });
621
622    quote! {
623        impl #name {
624            #partial
625        }
626    }
627}
628
629fn generate_functions(
630    name: &Ident,
631    config: &ContextConfig,
632    members: &Members,
633) -> proc_macro2::TokenStream {
634    let mut functions: Vec<proc_macro2::TokenStream> = Vec::new();
635
636    for trt in config.functions.iter() {
637        let ctx = &trt.1.ctx;
638        let ctx_type = &trt.1.ctx_type;
639        let naming = &trt.1.naming;
640        let visibility = &trt.1.visibility;
641
642        let members_code = members.iter().fold(
643            quote! {
644                let mut result = std::collections::HashMap::new();
645            },
646            |acc, member| {
647                if member.1.skip_contexts.contains(ctx) {
648                    acc
649                } else {
650                    let field = &member.1.ident;
651                    let name = member
652                        .1
653                        .rename
654                        .get(ctx)
655                        .cloned()
656                        .unwrap_or(member.1.name.clone());
657                    let converter = member
658                        .1
659                        .converter
660                        .get(ctx)
661                        .cloned()
662                        .unwrap_or(trt.1.converter.clone());
663
664                    let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
665                        quote! {}
666                    } else {
667                        quote! {&}
668                    };
669
670                    quote! {
671                        #acc
672                        result.insert(#name.to_string(), #converter(#move_type arg.#field));
673                    }
674                }
675            },
676        );
677        let members_code = quote! {
678            #members_code
679            result
680        };
681
682        functions.push(quote! {
683            #visibility fn #naming(arg: &#name) -> std::collections::HashMap<String, #ctx_type> {
684                #members_code
685            }
686        })
687    }
688
689    functions.into_iter().fold(quote! {}, |acc, x| {
690        quote! {
691            #acc
692            #x
693        }
694    })
695}
696
697fn generate_traits_impl(
698    name: &Ident,
699    config: &ContextConfig,
700    members: &Members,
701) -> proc_macro2::TokenStream {
702    let mut impls: Vec<proc_macro2::TokenStream> = Vec::new();
703
704    for trt in config.traits.iter() {
705        let ctx = &trt.1.ctx;
706        let ctx_type = &trt.1.ctx_type;
707
708        let members_code = members.iter().fold(
709            quote! {
710                let mut result = std::collections::HashMap::new();
711            },
712            |acc, member| {
713                if member.1.skip_contexts.contains(ctx) {
714                    acc
715                } else {
716                    let field = &member.1.ident;
717                    let name = member
718                        .1
719                        .rename
720                        .get(ctx)
721                        .cloned()
722                        .unwrap_or(member.1.name.clone());
723                    let converter = member
724                        .1
725                        .converter
726                        .get(ctx)
727                        .cloned()
728                        .unwrap_or(trt.1.converter.clone());
729
730                    let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
731                        quote! {}
732                    } else {
733                        quote! {&}
734                    };
735
736                    quote! {
737                        #acc
738                        result.insert(#name.to_string(), #converter(#move_type self.#field));
739                    }
740                }
741            },
742        );
743        let members_code = quote! {
744            #members_code
745            result
746        };
747
748        match &trt.1.naming {
749            TraitNaming::ExistingGeneric { path, method_name } => impls.push(quote! {
750                impl #path<#ctx_type> for #name {
751                    fn #method_name(&self) -> std::collections::HashMap<String, #ctx_type> {
752                        #members_code
753                    }
754                }
755            }),
756            TraitNaming::Existing { path, method_name } => impls.push(quote! {
757                impl #path for #name {
758                    fn #method_name(&self) -> std::collections::HashMap<String, #ctx_type> {
759                        #members_code
760                    }
761                }
762            }),
763        }
764    }
765
766    impls.into_iter().fold(quote! {}, |acc, x| {
767        quote! {
768            #acc
769            #x
770        }
771    })
772}
773
774/// Generate mapping with the different contexts
775///
776/// # General usage
777/// **NOTE** This is NOT a working example, just a pseudocode
778///
779/// ## Mappers configuration
780/// ```
781/// #[derive(ContextMapper)]
782/// #[context_mapper(
783///
784///     /// Indicates that mapper will be using a trait
785///     trait(
786///         // Name of the context. Defaults to `default`
787///         context = path::of::the::context::default,
788///
789///         // Result type of the mapping. Defaults to `std::string::String`
790///         type = usize,
791///
792///         // Conversion function/method. Defaults to `std::string::ToString`
793///         converter = my_function_with_proper_signature,
794///
795///         // Indicates how the type should be passed to the function. Flag
796///         // If false (or not defined): passed by reference
797///         // Else: value is moved
798///         move
799///
800///         /// Traits require either simple or generic naming
801///         simple(
802///             path = path::to::the::trait,
803///             method_name = name_of_the_method
804///         )
805///         generic(
806///             path = path::to::the::trait,
807///             method_name = name_of_the_method
808///         )
809///
810///     )
811///
812///     function(
813///         context = path::of::the::context::default,
814///         type = usize,
815///         converter = my_function_with_proper_signature,
816///         move
817///
818///         /// Name of the generated function. Both are identical
819///         naming = my_new_function
820///         fn = my_new_function
821///
822///         /// Optional. Visibility of the generated function. Both are identical
823///         vis = pub(crate)
824///         visibility = pub(crate)
825///
826///     )
827///
828///     impls(
829///         context = path::of::the::context::default,
830///         type = usize,
831///         converter = my_function_with_proper_signature,
832///         move
833///
834///         // As in `function`
835///         naming = my_new_function
836///         fn = my_new_function
837///
838///         /// As in `function`
839///         vis = pub(crate)
840///         visibility = pub(crate)
841///     )
842/// )]
843/// ```
844///
845/// ## Attribtues configuration
846///
847/// ```
848/// #[context_attribute(
849///     context(
850///         name = path::to::the::context,
851///         converter = you::can::override::Mapper::function,
852///
853///         /// You can change mapper key for the certain context
854///         rename = "new mapper key",
855///
856///         /// Indicates that field should be skipped in the given context. Flag
857///         skip,
858///
859///         /// You can override context `move` flag
860///         move
861///     )
862/// )]
863/// ```
864///
865/// # Basic e xample
866/// ```
867/// use context_mapper::ContextMapper;
868/// use context_mapper::IntoType;
869/// #[derive(ContextMapper, Serialize)]
870/// #[context_mapper(trait())]
871/// #[context_mapper(
872///     trait(
873///         context=other_context,
874///         type=usize,
875///         converter=my_function,
876///     )
877/// )]
878/// pub struct X {
879///     #[context_attribute(
880///         context(
881///             name=other_context,
882///             converter=my_other_function,
883///             rename="my_other_name",
884///         )
885///     )]
886///     pub x: String,
887///
888///     #[context_attribute(
889///         context(
890///             name=other_context,
891///             move
892///         )
893///     )]
894///     pub y: usize,
895///     pub y0: i32,
896///
897///     #[context_attribute(context(name=other_context, skip))]
898///     pub z: i32
899/// }
900/// ```
901/// That generates the following
902/// ```
903/// impl ::context_mapper::IntoType<std::string::String> for X {
904///     fn into_type_map(&self) -> std::collections::HashMap<String, std::string::String> {
905///         let mut result = std::collections::HashMap::new();
906///         result.insert("x".to_string(), std::string::ToString::to_string(&self.x));
907///         result.insert("y".to_string(), std::string::ToString::to_string(&self.y));
908///         result.insert("y0".to_string(), std::string::ToString::to_string(&self.y0));
909///         result.insert("z".to_string(), std::string::ToString::to_string(&self.z));
910///         result
911///     }
912/// }
913/// impl ::context_mapper::IntoType<usize> for X {
914///     fn into_type_map(&self) -> std::collections::HashMap<String, usize> {
915///         let mut result = std::collections::HashMap::new();
916///         result.insert("my_other_name".to_string(), my_other_function(&self.x));
917///         result.insert("y".to_string(), my_function(self.y));
918///         result.insert("y0".to_string(), my_function(&self.y0));
919///         result
920///     }
921/// }
922///
923/// ```
924///
925#[proc_macro_derive(ContextMapper, attributes(context_mapper, context_attribute))]
926pub fn context_mapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
927    let input = parse_macro_input!(input as DeriveInput);
928    let name = input.ident.clone();
929
930    let config = read_struct_config(&input);
931    let members = read_members(&input);
932
933    let traits_impl = generate_traits_impl(&name, &config, &members);
934    let functs = generate_functions(&name, &config, &members);
935    let impls = generate_impls(&name, &config, &members);
936
937    let expanded = quote! {
938        #traits_impl
939        #functs
940        #impls
941    };
942
943    proc_macro::TokenStream::from(expanded)
944}