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