1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Expr, Fields, Lit, Meta};
4
5#[proc_macro_derive(Validate, attributes(vld, into_params))]
49pub fn derive_validate(input: TokenStream) -> TokenStream {
50 let input = parse_macro_input!(input as DeriveInput);
51 let name = &input.ident;
52
53 let rename_all = get_serde_rename_all(&input.attrs);
55 let parameter_in_expr = match get_into_params_parameter_in(&input.attrs).as_deref() {
56 Some("query") => quote! { Some("query") },
57 Some("path") => quote! { Some("path") },
58 Some("header") => quote! { Some("header") },
59 Some("cookie") => quote! { Some("cookie") },
60 _ => quote! { None },
61 };
62
63 let fields = match &input.data {
64 Data::Struct(data) => match &data.fields {
65 Fields::Named(fields) => &fields.named,
66 _ => panic!("Validate can only be derived for structs with named fields"),
67 },
68 _ => panic!("Validate can only be derived for structs"),
69 };
70
71 let mut field_names = Vec::new();
72 let mut field_types = Vec::new();
73 let mut field_schemas = Vec::new();
74 let mut field_json_keys = Vec::new();
75
76 for field in fields {
77 let fname = field.ident.as_ref().unwrap();
78 let ftype = &field.ty;
79 field_names.push(fname.clone());
80 field_types.push(ftype.clone());
81
82 let json_key = get_serde_rename(&field.attrs).unwrap_or_else(|| {
84 if let Some(ref convention) = rename_all {
85 rename_field(&fname.to_string(), convention)
86 } else {
87 fname.to_string()
88 }
89 });
90 field_json_keys.push(json_key);
91
92 let schema_tokens = field
93 .attrs
94 .iter()
95 .find(|attr| attr.path().is_ident("vld"))
96 .map(|attr| attr.parse_args::<proc_macro2::TokenStream>().unwrap())
97 .unwrap_or_else(|| panic!("Field `{}` is missing #[vld(...)] attribute", fname));
98
99 field_schemas.push(schema_tokens);
100 }
101
102 let expanded = quote! {
103 impl #name {
104 pub fn vld_parse<__VldInputT: ::vld::input::VldInput + ?Sized>(
109 input: &__VldInputT,
110 ) -> ::std::result::Result<#name, ::vld::error::VldError> {
111 let __vld_json = <__VldInputT as ::vld::input::VldInput>::to_json_value(input)?;
112 Self::parse_value(&__vld_json)
113 }
114
115 pub fn parse_value(
117 __vld_json: &::vld::serde_json::Value,
118 ) -> ::std::result::Result<#name, ::vld::error::VldError> {
119 use ::vld::schema::VldSchema as _;
120
121 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
122 ::vld::error::VldError::single(
123 ::vld::error::IssueCode::InvalidType {
124 expected: ::std::string::String::from("object"),
125 received: ::vld::error::value_type_name(__vld_json),
126 },
127 ::std::format!(
128 "Expected object, received {}",
129 ::vld::error::value_type_name(__vld_json)
130 ),
131 )
132 })?;
133
134 let mut __vld_errors = ::vld::error::VldError::new();
135
136 #(
137 #[allow(non_snake_case)]
138 let #field_names: ::std::option::Option<#field_types> = {
139 let __vld_field_schema = { #field_schemas };
140 let __vld_field_value = __vld_obj
141 .get(#field_json_keys)
142 .unwrap_or(&::vld::serde_json::Value::Null);
143 match __vld_field_schema.parse_value(__vld_field_value) {
144 ::std::result::Result::Ok(v) => ::std::option::Option::Some(v),
145 ::std::result::Result::Err(e) => {
146 __vld_errors = ::vld::error::VldError::merge(
147 __vld_errors,
148 ::vld::error::VldError::with_prefix(
149 e,
150 ::vld::error::PathSegment::Field(
151 ::std::string::String::from(#field_json_keys),
152 ),
153 ),
154 );
155 ::std::option::Option::None
156 }
157 }
158 };
159 )*
160
161 if !::vld::error::VldError::is_empty(&__vld_errors) {
162 return ::std::result::Result::Err(__vld_errors);
163 }
164
165 ::std::result::Result::Ok(#name {
166 #( #field_names: #field_names.unwrap(), )*
167 })
168 }
169
170 pub fn validate_fields<__VldInputT: ::vld::input::VldInput + ?Sized>(
172 input: &__VldInputT,
173 ) -> ::std::result::Result<
174 ::std::vec::Vec<::vld::error::FieldResult>,
175 ::vld::error::VldError,
176 > {
177 let __vld_json = <__VldInputT as ::vld::input::VldInput>::to_json_value(input)?;
178 Self::validate_fields_value(&__vld_json)
179 }
180
181 pub fn validate_fields_value(
183 __vld_json: &::vld::serde_json::Value,
184 ) -> ::std::result::Result<
185 ::std::vec::Vec<::vld::error::FieldResult>,
186 ::vld::error::VldError,
187 > {
188 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
189 ::vld::error::VldError::single(
190 ::vld::error::IssueCode::InvalidType {
191 expected: ::std::string::String::from("object"),
192 received: ::vld::error::value_type_name(__vld_json),
193 },
194 ::std::format!(
195 "Expected object, received {}",
196 ::vld::error::value_type_name(__vld_json)
197 ),
198 )
199 })?;
200
201 let mut __vld_results: ::std::vec::Vec<::vld::error::FieldResult> =
202 ::std::vec::Vec::new();
203
204 #(
205 {
206 let __vld_field_schema = { #field_schemas };
207 let __vld_field_value = __vld_obj
208 .get(#field_json_keys)
209 .unwrap_or(&::vld::serde_json::Value::Null);
210
211 let __vld_result = ::vld::object::DynSchema::dyn_parse(
212 &__vld_field_schema,
213 __vld_field_value,
214 );
215
216 __vld_results.push(::vld::error::FieldResult {
217 name: ::std::string::String::from(#field_json_keys),
218 input: __vld_field_value.clone(),
219 result: __vld_result,
220 });
221 }
222 )*
223
224 ::std::result::Result::Ok(__vld_results)
225 }
226
227 pub fn parse_lenient<__VldInputT: ::vld::input::VldInput + ?Sized>(
229 input: &__VldInputT,
230 ) -> ::std::result::Result<
231 ::vld::error::ParseResult<#name>,
232 ::vld::error::VldError,
233 > {
234 let __vld_json = <__VldInputT as ::vld::input::VldInput>::to_json_value(input)?;
235 Self::parse_lenient_value(&__vld_json)
236 }
237
238 pub fn parse_lenient_value(
240 __vld_json: &::vld::serde_json::Value,
241 ) -> ::std::result::Result<
242 ::vld::error::ParseResult<#name>,
243 ::vld::error::VldError,
244 > {
245 use ::vld::schema::VldSchema as _;
246
247 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
248 ::vld::error::VldError::single(
249 ::vld::error::IssueCode::InvalidType {
250 expected: ::std::string::String::from("object"),
251 received: ::vld::error::value_type_name(__vld_json),
252 },
253 ::std::format!(
254 "Expected object, received {}",
255 ::vld::error::value_type_name(__vld_json)
256 ),
257 )
258 })?;
259
260 let mut __vld_results: ::std::vec::Vec<::vld::error::FieldResult> =
261 ::std::vec::Vec::new();
262
263 #(
264 #[allow(non_snake_case)]
265 let #field_names: #field_types = {
266 let __vld_field_schema = { #field_schemas };
267 let __vld_field_value = __vld_obj
268 .get(#field_json_keys)
269 .unwrap_or(&::vld::serde_json::Value::Null);
270
271 match __vld_field_schema.parse_value(__vld_field_value) {
272 ::std::result::Result::Ok(v) => {
273 let __json_repr = ::vld::serde_json::to_value(&v)
274 .unwrap_or_else(|_| __vld_field_value.clone());
275 __vld_results.push(::vld::error::FieldResult {
276 name: ::std::string::String::from(#field_json_keys),
277 input: __vld_field_value.clone(),
278 result: ::std::result::Result::Ok(__json_repr),
279 });
280 v
281 }
282 ::std::result::Result::Err(e) => {
283 __vld_results.push(::vld::error::FieldResult {
284 name: ::std::string::String::from(#field_json_keys),
285 input: __vld_field_value.clone(),
286 result: ::std::result::Result::Err(e),
287 });
288 <#field_types as ::std::default::Default>::default()
289 }
290 }
291 };
292 )*
293
294 let __vld_struct = #name {
295 #( #field_names, )*
296 };
297
298 ::std::result::Result::Ok(
299 ::vld::error::ParseResult::new(__vld_struct, __vld_results)
300 )
301 }
302 }
303
304 impl ::vld::schema::VldParse for #name {
305 fn vld_parse_value(
306 value: &::vld::serde_json::Value,
307 ) -> ::std::result::Result<Self, ::vld::error::VldError> {
308 Self::parse_value(value)
309 }
310 }
311
312 ::vld::__vld_if_openapi! {
313 impl #name {
314 pub fn json_schema() -> ::vld::serde_json::Value {
318 use ::vld::json_schema::JsonSchema as _;
319 let mut __vld_properties = ::vld::serde_json::Map::new();
320 let mut __vld_required: ::std::vec::Vec<::std::string::String> =
321 ::std::vec::Vec::new();
322
323 #(
324 {
325 let __vld_field_schema = { #field_schemas };
326 __vld_properties.insert(
327 ::std::string::String::from(#field_json_keys),
328 __vld_field_schema.json_schema(),
329 );
330 __vld_required.push(
331 ::std::string::String::from(#field_json_keys),
332 );
333 }
334 )*
335
336 ::vld::serde_json::json!({
337 "type": "object",
338 "required": __vld_required,
339 "properties": ::vld::serde_json::Value::Object(__vld_properties),
340 })
341 }
342
343 pub fn to_openapi_document() -> ::vld::serde_json::Value {
347 ::vld::json_schema::to_openapi_document(
348 stringify!(#name),
349 &Self::json_schema(),
350 )
351 }
352 }
353
354 impl ::vld::json_schema::OpenApiParameterIn for #name {
355 fn parameter_in() -> Option<&'static str> {
356 #parameter_in_expr
357 }
358 }
359 }
360 };
361
362 TokenStream::from(expanded)
363}
364
365fn get_serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
371 for attr in attrs {
372 if !attr.path().is_ident("serde") {
373 continue;
374 }
375 if let Ok(nested) = attr
376 .parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
377 {
378 for meta in &nested {
379 if let Meta::NameValue(nv) = meta {
380 if nv.path.is_ident("rename_all") {
381 if let Expr::Lit(lit) = &nv.value {
382 if let Lit::Str(s) = &lit.lit {
383 return Some(s.value());
384 }
385 }
386 }
387 }
388 }
389 }
390 }
391 None
392}
393
394fn get_into_params_parameter_in(attrs: &[syn::Attribute]) -> Option<String> {
396 for attr in attrs {
397 if !attr.path().is_ident("into_params") {
398 continue;
399 }
400 if let Ok(nested) = attr
401 .parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
402 {
403 for meta in &nested {
404 if let Meta::NameValue(nv) = meta {
405 if nv.path.is_ident("parameter_in") {
406 if let Expr::Path(path) = &nv.value {
407 let ident = path.path.get_ident()?;
408 return Some(ident.to_string().to_ascii_lowercase());
409 }
410 }
411 }
412 }
413 }
414 }
415 None
416}
417
418fn get_serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
420 for attr in attrs {
421 if !attr.path().is_ident("serde") {
422 continue;
423 }
424 if let Ok(nested) = attr
425 .parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
426 {
427 for meta in &nested {
428 if let Meta::NameValue(nv) = meta {
429 if nv.path.is_ident("rename") {
430 if let Expr::Lit(lit) = &nv.value {
431 if let Lit::Str(s) = &lit.lit {
432 return Some(s.value());
433 }
434 }
435 }
436 }
437 }
438 }
439 }
440 None
441}
442
443fn rename_field(name: &str, convention: &str) -> String {
445 match convention {
446 "camelCase" => to_camel_case(name),
447 "PascalCase" => to_pascal_case(name),
448 "snake_case" => name.to_string(),
449 "SCREAMING_SNAKE_CASE" => name.to_uppercase(),
450 "kebab-case" => name.replace('_', "-"),
451 "SCREAMING-KEBAB-CASE" => name.replace('_', "-").to_uppercase(),
452 _ => name.to_string(),
453 }
454}
455
456fn to_camel_case(s: &str) -> String {
457 let mut result = String::new();
458 let mut capitalize_next = false;
459 for ch in s.chars() {
460 if ch == '_' {
461 capitalize_next = true;
462 } else if capitalize_next {
463 result.extend(ch.to_uppercase());
464 capitalize_next = false;
465 } else {
466 result.push(ch);
467 }
468 }
469 result
470}
471
472fn to_pascal_case(s: &str) -> String {
473 let mut result = String::new();
474 let mut capitalize_next = true;
475 for ch in s.chars() {
476 if ch == '_' {
477 capitalize_next = true;
478 } else if capitalize_next {
479 result.extend(ch.to_uppercase());
480 capitalize_next = false;
481 } else {
482 result.push(ch);
483 }
484 }
485 result
486}