err_per_field_derive/
lib.rs

1//! Derived macro used by err-per-field crate.
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5use quote::quote;
6use syn::{
7    parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Error, Field, Fields, Lit,
8    Meta, NestedMeta, Path,
9};
10
11fn process_submeta<F: FnMut(&Meta)>(attrs: &[Attribute], f: &mut F) {
12    for attr in attrs {
13        let list = if let Ok(Meta::List(list)) = attr.parse_meta() {
14            list
15        } else {
16            continue;
17        };
18        let ident = if let Some(ident) = list.path.get_ident() {
19            ident
20        } else {
21            continue;
22        };
23        if ident != "err_per_field" {
24            continue;
25        }
26        for nested_meta in &list.nested {
27            let submeta = if let NestedMeta::Meta(submeta) = nested_meta {
28                submeta
29            } else {
30                continue;
31            };
32            f(submeta);
33        }
34    }
35}
36
37enum FieldWrapper {
38    Result(Path),
39    Option,
40    Raw,
41}
42
43#[proc_macro_derive(ErrPerField, attributes(err_per_field))]
44pub fn err_per_field(input: TokenStream) -> TokenStream {
45    let input = parse_macro_input!(input as DeriveInput);
46
47    let mut compiler_errors = vec![];
48
49    let DeriveInput {
50        vis: container_vis,
51        ident: container_ident,
52        generics: container_generics,
53        data: container_data,
54        ..
55    } = &input;
56
57    // Build wrapper struct name
58    let wrapper_struct_ident = Ident::new(
59        &format!("{}PerErrFieldWrapper", container_ident),
60        Span::call_site(),
61    );
62
63    // Build wrapper struct generics
64    let (impl_generics, ty_generics, where_clause) = container_generics.split_for_impl();
65
66    // Iterate struct body
67    let struct_data = if let Data::Struct(struct_data) = container_data {
68        struct_data
69    } else {
70        return TokenStream::from(
71            Error::new(input.span(), "Unsupported data type").to_compile_error(),
72        );
73    };
74    let fields = if let Fields::Named(fields) = &struct_data.fields {
75        fields
76    } else {
77        return TokenStream::from(
78            Error::new(input.span(), "Unsupported data type").to_compile_error(),
79        );
80    };
81    let mut wrapper_body = vec![];
82    let mut inner_body = vec![];
83    let mut matched_field_patterns = vec![];
84    for field in &fields.named {
85        let Field {
86            attrs: field_attrs,
87            vis: field_vis,
88            ident: field_name,
89            ty: field_ty,
90            ..
91        } = &field;
92        inner_body.push(field_name);
93        let mut field_wrapper = FieldWrapper::Raw;
94        process_submeta(field_attrs, &mut |submeta| match submeta {
95            Meta::Path(meta_path) => {
96                let ident = if let Some(ident) = meta_path.get_ident() {
97                    ident
98                } else {
99                    compiler_errors
100                        .push(Error::new(meta_path.span(), "Unknown key").to_compile_error());
101                    return;
102                };
103                if ident != "maybe_none" {
104                    compiler_errors.push(
105                        Error::new(ident.span(), &format!("Unknown key {}", ident))
106                            .to_compile_error(),
107                    );
108                    return;
109                }
110                field_wrapper = FieldWrapper::Option;
111            }
112            Meta::NameValue(meta_name_value) => {
113                let ident = if let Some(ident) = meta_name_value.path.get_ident() {
114                    ident
115                } else {
116                    return;
117                };
118                if ident != "maybe_error" {
119                    compiler_errors.push(
120                        Error::new(ident.span(), &format!("Unknown key {}", ident))
121                            .to_compile_error(),
122                    );
123                    return;
124                }
125                let str_lit = if let Lit::Str(str_lit) = &meta_name_value.lit {
126                    str_lit
127                } else {
128                    compiler_errors.push(
129                        Error::new(meta_name_value.lit.span(), "Unsupported value type")
130                            .to_compile_error(),
131                    );
132                    return;
133                };
134                let path = if let Ok(path) = syn::parse_str::<Path>(&str_lit.value()) {
135                    path
136                } else {
137                    compiler_errors.push(
138                        Error::new(
139                            meta_name_value.lit.span(),
140                            &format!("Unable to convert {} to a valid path.", str_lit.value()),
141                        )
142                        .to_compile_error(),
143                    );
144                    return;
145                };
146                field_wrapper = FieldWrapper::Result(path);
147            }
148            _ => {}
149        });
150        let wrapper_body_part = match &field_wrapper {
151            FieldWrapper::Raw => {
152                quote! { #field_vis #field_name: #field_ty }
153            }
154            FieldWrapper::Option => {
155                quote! { #field_vis #field_name: ::core::option::Option<#field_ty> }
156            }
157            FieldWrapper::Result(path) => {
158                quote! { #field_vis #field_name: ::core::result::Result<#field_ty, #path> }
159            }
160        };
161        let matched_field_pattern = match &field_wrapper {
162            FieldWrapper::Raw => {
163                quote! { #field_name }
164            }
165            FieldWrapper::Option => {
166                quote! { #field_name: Some(#field_name) }
167            }
168            FieldWrapper::Result(_) => {
169                quote! { #field_name: Ok(#field_name) }
170            }
171        };
172        wrapper_body.push(wrapper_body_part);
173        matched_field_patterns.push(matched_field_pattern);
174    }
175
176    let expanded = quote! {
177        #(#compiler_errors)*
178        /// Wrapper struct generated by `err_per_field` crate. DO NOT USE DIRECTLY!!!
179        #[doc(hidden)]
180        #[derive(Debug)]
181        #container_vis struct #wrapper_struct_ident #container_generics {
182            #(#wrapper_body),*
183        }
184
185        impl #impl_generics err_per_field::ErrPerField for #container_ident #ty_generics #where_clause {
186            type Wrapper = #wrapper_struct_ident #ty_generics;
187        }
188
189        impl #impl_generics ::core::convert::TryFrom<#wrapper_struct_ident #ty_generics> for #container_ident #ty_generics #where_clause {
190            type Error = #wrapper_struct_ident #ty_generics;
191            fn try_from(value: #wrapper_struct_ident #ty_generics) -> Result<Self, Self::Error> {
192                if let #wrapper_struct_ident {
193                    #(#matched_field_patterns),*
194                } = value {
195                    Ok(#container_ident {
196                        #(#inner_body),*
197                    })
198                } else {
199                    Err(value)
200                }
201            }
202        }
203    };
204
205    TokenStream::from(expanded)
206}