err_per_field_derive/
lib.rs1use 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 let wrapper_struct_ident = Ident::new(
59 &format!("{}PerErrFieldWrapper", container_ident),
60 Span::call_site(),
61 );
62
63 let (impl_generics, ty_generics, where_clause) = container_generics.split_for_impl();
65
66 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 #[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}