1use proc_macro2::TokenStream;
2use procmeta::prelude::*;
3use quote::quote;
4use syn::LitStr;
5use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Fields, LitInt};
6
7#[derive(MetaParser)]
8pub enum ValidateAttr {
9 #[name("validate")]
10 DefaultValidate,
11
12 #[name("validate")]
13 DefaultValidateWithMsg(LitStr),
14
15 #[name("validate")]
16 Validate(ConstraintTypeAttr),
17
18 #[name("validate")]
19 ValidateWithMsg(ConstraintTypeAttr, LitStr),
20}
21
22#[derive(MetaParser)]
23pub enum NameAttr {
24 Name(LitStr),
25}
26
27#[derive(MetaParser)]
28pub enum ConstraintTypeAttr {
29 Length(LitInt, LitInt),
30
31 Range(Lit, Lit),
32
33 #[name("validator")]
34 Validator,
35
36 #[name("validator")]
37 SpecialValidator(Type),
38
39 #[name("reg")]
40 Regex(LitStr),
41
42 Enumer(Type),
43}
44
45pub struct ParsedFiled<'a> {
46 pub field_name: Option<String>,
47 pub field_desc: Option<String>,
48 pub ident: TokenStream,
49 pub field: &'a Field,
50 pub constraints: Vec<(ConstraintTypeAttr, Option<String>)>,
51}
52
53impl<'a> ParsedFiled<'a> {
54 fn try_from(
55 field_name: Option<String>,
56 ident: TokenStream,
57 field: &'a Field,
58 ) -> syn::Result<Self> {
59 let mut constraints = vec![];
60 let mut field_desc = None;
61 for attr in &field.attrs {
62 let parsed_attr = ValidateAttr::try_from(attr);
63 if let Ok(parsed_attr) = parsed_attr {
64 let constraint = match parsed_attr {
65 ValidateAttr::Validate(constraint) => (constraint, None),
66 ValidateAttr::ValidateWithMsg(constraint, msg) => {
67 (constraint, Some(msg.value()))
68 }
69 ValidateAttr::DefaultValidate => (ConstraintTypeAttr::Validator, None),
70 ValidateAttr::DefaultValidateWithMsg(msg) => {
71 (ConstraintTypeAttr::Validator, Some(msg.value()))
72 }
73 };
74 constraints.push(constraint);
75 continue;
76 }
77 let parsed_attr = NameAttr::try_from(attr);
78 if let Ok(parsed_attr) = parsed_attr {
79 if field_desc.is_some() {
80 return Err(Error::new(Span::call_site(), "desc attr duplicated"));
81 }
82 let NameAttr::Name(desc) = parsed_attr;
83 field_desc = Some(desc.value());
84 }
85 }
86 Ok(Self {
87 ident,
88 field,
89 field_name,
90 field_desc,
91 constraints,
92 })
93 }
94
95 pub fn validate_quote(self) -> Result<TokenStream> {
96 let mut result_token = quote!();
97 let field_ident = self.ident;
98 let field_name = self.field_name.get_token_stream();
99 let field_desc = self.field_desc.get_token_stream();
100 let mut field_ty = self.field.ty.clone();
101 type_add_colon2(&mut field_ty);
102 for constraint in self.constraints {
103 let item_msg = constraint.1;
104 let mut validate_token = match constraint.0 {
105 ConstraintTypeAttr::Length(min, max) => {
106 quote! {
107 let validated = <LengthValidator::<#min, #max> as Validator<#field_ty>>::validate(#field_ident);
108 }
109 }
110 ConstraintTypeAttr::Range(min, max) => {
111 quote! {
112 let validated = <#field_ty as RangeValidator>::validate(#field_ident, #min, #max);
113 }
114 }
115 ConstraintTypeAttr::Validator => {
116 quote! {
117 let validated = <#field_ty as SelfValidator> ::validate(#field_ident);
118 }
119 }
120 ConstraintTypeAttr::SpecialValidator(mut ty) => {
121 type_add_colon2(&mut ty);
122 quote! {
123 let validated = <#ty as Validator<#field_ty>>::validate(#field_ident);
124 }
125 }
126 ConstraintTypeAttr::Regex(regex) => {
127 quote! {
128 let REGEX_EXPRESS = regex!(#regex);
129 let validated = <#field_ty as RegexValidator<#field_ty>>::validate(REGEX_EXPRESS, #field_ident);
130 }
131 }
132 ConstraintTypeAttr::Enumer(mut ty) => {
133 type_add_colon2(&mut ty);
134 quote! {
135 let validated = <#field_ty as EnumValidator<'_, #ty>>::validate(#field_ident);
136 }
137 }
138 };
139 validate_token = match item_msg {
140 Some(msg) => quote! {
141 #validate_token
142 if let Err(e) = validated {
143 return Err(ModelValidatorError {
144 error_ty: Box::new(e.into()),
145 error_message: Some(format!(#msg)),
146 field_ident: #field_name,
147 field_name: #field_desc,
148 });
149 }
150 },
151 None => quote! {
152 #validate_token
153 if let Err(e) = validated {
154 return Err(ModelValidatorError {
155 error_ty: Box::new(e.into()),
156 error_message: None,
157 field_ident: #field_name,
158 field_name: #field_desc,
159 });
160 }
161 },
162 };
163 result_token = quote! {
164 #result_token
165 #validate_token
166 };
167 }
168 Ok(result_token)
169 }
170}
171
172pub fn impl_struct(data: &DataStruct) -> Result<TokenStream> {
173 let mut result_token = quote!();
174 for (index, field) in data.fields.iter().enumerate() {
175 let field_name: String;
176 let ident: TokenStream;
177 match &field.ident {
178 Some(inner) => {
179 field_name = inner.to_string();
180 ident = quote!(&self. #inner);
181 }
182 None => {
183 let index = LitInt::new(&index.to_string(), Span::call_site());
184 ident = quote!(&self. #index);
185 field_name = format!("{index}");
186 }
187 };
188 let parsed_field = ParsedFiled::try_from(Some(field_name), ident, field)?;
189 let field_validate_token = parsed_field.validate_quote()?;
190 result_token = quote! {
191 #result_token
192 #field_validate_token
193 };
194 }
195 Ok(result_token)
196}
197
198pub fn impl_enum(data: &DataEnum) -> Result<TokenStream> {
199 let mut result_token = quote!();
200 for variant in &data.variants {
201 let mut variant_token = quote!();
202 let variant_ident = &variant.ident;
203 let mut pat = quote! {};
204 let is_named_field = !matches!(&variant.fields, Fields::Unnamed(_));
205 for (index, field) in variant.fields.iter().enumerate() {
206 let ident: TokenStream;
207 let field_name: String;
208 match &field.ident {
209 Some(inner) => {
210 ident = quote!(#inner);
211 field_name = format!("{}.{}", variant_ident, ident);
212 }
213 None => {
214 let unamed_ident = get_unname_field_ident(index);
215 ident = quote!(#unamed_ident);
216 field_name = format!("{}.{}", variant_ident, index);
217 }
218 }
219 pat = quote! {
220 #pat #ident,
221 };
222 let parsed_field = ParsedFiled::try_from(Some(field_name), ident, field)?;
223 let field_validate_token = parsed_field.validate_quote()?;
224 variant_token = quote! {
225 #variant_token
226 #field_validate_token
227 };
228 }
229 if is_named_field {
230 pat = quote! {{#pat}}
231 } else {
232 pat = quote! {(#pat)}
233 }
234 variant_token = quote! {
235 Self::#variant_ident #pat => {
236 #variant_token
237 }
238 };
239 result_token = quote! {
240 #result_token
241 #variant_token
242 }
243 }
244
245 result_token = quote! {
246 match self {
247 #result_token
248 }
249 };
250 Ok(result_token)
251}
252
253pub fn expand(input: DeriveInput) -> Result<TokenStream> {
254 let ty = &input.ident;
255 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
256 let body_token = match &input.data {
257 Data::Struct(data) => impl_struct(data)?,
258 Data::Enum(data) => impl_enum(data)?,
259 Data::Union(_) => unimplemented!(),
260 };
261 let result = quote! {
262 impl #impl_generics SelfValidator for #ty #ty_generics #where_clause {
263 #[allow(clippy::useless_format)]
264 fn validate(&self) -> Result<(), ModelValidatorError> {
265 #body_token
266 Ok(())
267 }
268 }
269
270 };
271 Ok(result)
272}