vld/macros.rs
1/// Helper macro to resolve a field's JSON key.
2///
3/// If a rename literal is provided, use it; otherwise fall back to the field name.
4#[doc(hidden)]
5#[macro_export]
6macro_rules! __vld_resolve_key {
7 ($default:expr) => {
8 $default
9 };
10 ($default:expr, $override:expr) => {
11 $override
12 };
13}
14
15/// Filter `#[into_params(...)]` off struct attributes, then emit attrs + items together.
16#[doc(hidden)]
17#[macro_export]
18macro_rules! __vld_struct_attrs {
19 ( attrs: $(#[ $($attr:tt)* ])*
20 body: { $($body:tt)* }
21 ) => {
22 $crate::__vld_struct_attrs!(@filter [] $(#[ $($attr)* ])*
23 body: { $($body)* }
24 );
25 };
26 (
27 @filter [$($acc:tt)*]
28 #[ into_params ( $($p:tt)* ) ]
29 $(#[ $($rest:tt)* ])*
30 body: { $($body:tt)* }
31 ) => {
32 $crate::__vld_struct_attrs!(@filter [$($acc)*]
33 $(#[ $($rest)* ])*
34 body: { $($body)* }
35 );
36 };
37 (
38 @filter [$($acc:tt)*]
39 #[ $($attr:tt)* ]
40 $(#[ $($rest:tt)* ])*
41 body: { $($body:tt)* }
42 ) => {
43 $crate::__vld_struct_attrs!(@filter [$($acc)* #[ $($attr)* ]]
44 $(#[ $($rest)* ])*
45 body: { $($body)* }
46 );
47 };
48 (
49 @filter [$($acc:tt)*]
50 body: { $($body:tt)* }
51 ) => {
52 $($acc)*
53 $($body)*
54 };
55}
56
57/// Define a validated struct with field-level schemas.
58///
59/// This macro generates:
60/// - A regular Rust struct with the specified fields and types
61/// - A `parse()` method that validates input and constructs the struct
62/// - A `parse_value()` method for direct `serde_json::Value` input
63/// - An implementation of [`VldParse`](crate::schema::VldParse) for use with framework extractors
64///
65/// # Syntax
66///
67/// ```ignore
68/// vld::schema! {
69/// #[derive(Debug, Clone)]
70/// pub struct MyStruct {
71/// pub field_name: FieldType => schema_expression,
72/// pub renamed_field: FieldType as "jsonKey" => schema_expression,
73/// // ...
74/// }
75/// }
76/// ```
77///
78/// Each field has the format: `name: Type [as "json_key"] => schema`.
79/// The optional `as "json_key"` overrides the JSON property name used for parsing.
80///
81/// # OpenAPI parameters
82///
83/// For query/path/header/cookie parameters, add utoipa's attribute on the struct
84/// (same as with `#[derive(Validate)]`), then call [`vld_utoipa::impl_to_schema`] once:
85///
86/// ```ignore
87/// vld::schema! {
88/// #[derive(Debug)]
89/// #[into_params(parameter_in = Query)]
90/// pub struct SearchQuery {
91/// pub q: String => vld::string().min(1),
92/// }
93/// }
94/// impl_to_schema!(SearchQuery);
95/// ```
96///
97/// # Example
98///
99/// ```
100/// use vld::prelude::*;
101///
102/// vld::schema! {
103/// #[derive(Debug)]
104/// pub struct User {
105/// pub name: String => vld::string().min(2).max(50),
106/// pub age: Option<i64> => vld::number().int().min(0).optional(),
107/// pub tags: Vec<String> => vld::array(vld::string()).max_len(5)
108/// .with_default(vec![]),
109/// }
110/// }
111///
112/// let user = User::parse(r#"{"name": "Alice", "age": 30}"#).unwrap();
113/// assert_eq!(user.name, "Alice");
114/// assert_eq!(user.age, Some(30));
115/// assert!(user.tags.is_empty());
116/// ```
117///
118/// # Renamed Fields
119///
120/// ```ignore
121/// vld::schema! {
122/// pub struct ApiResponse {
123/// pub first_name: String as "firstName" => vld::string().min(1),
124/// pub last_name: String as "lastName" => vld::string().min(1),
125/// }
126/// }
127///
128/// // Parses from camelCase JSON:
129/// let r = ApiResponse::parse(r#"{"firstName": "John", "lastName": "Doe"}"#).unwrap();
130/// assert_eq!(r.first_name, "John");
131/// ```
132///
133/// # Nested Structs
134///
135/// Use [`nested()`](crate::nested) to compose schemas:
136///
137/// ```ignore
138/// vld::schema! {
139/// pub struct Address {
140/// pub city: String => vld::string().min(1),
141/// }
142/// }
143///
144/// vld::schema! {
145/// pub struct User {
146/// pub name: String => vld::string(),
147/// pub address: Address => vld::nested(Address::parse_value),
148/// }
149/// }
150/// ```
151#[macro_export]
152macro_rules! schema {
153 (
154 $(#[ $($struct_attr:tt)* ])*
155 $vis:vis struct $name:ident {
156 $(
157 $(#[$field_meta:meta])*
158 $field_vis:vis $field_name:ident : $field_type:ty $(as $rename:literal)? => $schema:expr
159 ),* $(,)?
160 }
161 ) => {
162 $crate::__vld_struct_attrs! {
163 attrs: $(#[ $($struct_attr)* ])*
164 body: {
165 $vis struct $name {
166 $(
167 $(#[$field_meta])*
168 $field_vis $field_name: $field_type,
169 )*
170 }
171
172 impl $name {
173 /// Parse and validate input data into this struct.
174 ///
175 /// Accepts any type implementing [`VldInput`]: JSON strings, file paths,
176 /// `serde_json::Value`, byte slices, etc.
177 pub fn parse<__VldInputT: $crate::input::VldInput + ?Sized>(
178 input: &__VldInputT,
179 ) -> ::std::result::Result<$name, $crate::error::VldError> {
180 let __vld_json = <__VldInputT as $crate::input::VldInput>::to_json_value(input)?;
181 Self::parse_value(&__vld_json)
182 }
183
184 /// Parse and validate directly from a `serde_json::Value`.
185 pub fn parse_value(
186 __vld_json: &$crate::serde_json::Value,
187 ) -> ::std::result::Result<$name, $crate::error::VldError> {
188 use $crate::schema::VldSchema as _;
189
190 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
191 $crate::error::VldError::single(
192 $crate::error::IssueCode::InvalidType {
193 expected: ::std::string::String::from("object"),
194 received: $crate::error::value_type_name(__vld_json),
195 },
196 ::std::format!(
197 "Expected object, received {}",
198 $crate::error::value_type_name(__vld_json)
199 ),
200 )
201 })?;
202
203 let mut __vld_errors = $crate::error::VldError::new();
204
205 $(
206 #[allow(non_snake_case)]
207 let $field_name: ::std::option::Option<$field_type> = {
208 let __vld_field_schema = $schema;
209 let __vld_key = $crate::__vld_resolve_key!(
210 stringify!($field_name) $(, $rename)?
211 );
212 let __vld_field_value = __vld_obj
213 .get(__vld_key)
214 .unwrap_or(&$crate::serde_json::Value::Null);
215 match __vld_field_schema.parse_value(__vld_field_value) {
216 ::std::result::Result::Ok(v) => ::std::option::Option::Some(v),
217 ::std::result::Result::Err(e) => {
218 __vld_errors = $crate::error::VldError::merge(
219 __vld_errors,
220 $crate::error::VldError::with_prefix(
221 e,
222 $crate::error::PathSegment::Field(
223 ::std::string::String::from(__vld_key),
224 ),
225 ),
226 );
227 ::std::option::Option::None
228 }
229 }
230 };
231 )*
232
233 if !$crate::error::VldError::is_empty(&__vld_errors) {
234 return ::std::result::Result::Err(__vld_errors);
235 }
236
237 ::std::result::Result::Ok($name {
238 $(
239 $field_name: $field_name.unwrap(),
240 )*
241 })
242 }
243
244 }
245
246 impl $crate::schema::VldParse for $name {
247 fn vld_parse_value(
248 value: &$crate::serde_json::Value,
249 ) -> ::std::result::Result<Self, $crate::error::VldError> {
250 Self::parse_value(value)
251 }
252 }
253
254
255 $crate::__vld_if_serialize! {
256 impl $name {
257 /// Validate an existing Rust value that can be serialized to JSON.
258 ///
259 /// The value is serialized via `serde`, then validated against the
260 /// schema. Returns `Ok(())` on success, `Err(VldError)` with all
261 /// issues on failure.
262 ///
263 /// Requires the `serialize` feature.
264 pub fn validate<__VldT: $crate::serde::Serialize>(
265 instance: &__VldT,
266 ) -> ::std::result::Result<(), $crate::error::VldError> {
267 let __vld_json = $crate::serde_json::to_value(instance).map_err(|e| {
268 $crate::error::VldError::single(
269 $crate::error::IssueCode::ParseError,
270 ::std::format!("Serialization error: {}", e),
271 )
272 })?;
273 let _ = Self::parse_value(&__vld_json)?;
274 ::std::result::Result::Ok(())
275 }
276
277 /// Check if a value is valid against the schema.
278 ///
279 /// Shorthand for `validate(instance).is_ok()`.
280 ///
281 /// Requires the `serialize` feature.
282 pub fn is_valid<__VldT: $crate::serde::Serialize>(instance: &__VldT) -> bool {
283 Self::validate(instance).is_ok()
284 }
285 }
286 }
287
288 $crate::__vld_if_openapi! {
289 impl $name {
290 /// Generate a JSON Schema / OpenAPI 3.1 representation of this struct.
291 ///
292 /// Requires the `openapi` feature.
293 pub fn json_schema() -> $crate::serde_json::Value {
294 use $crate::json_schema::JsonSchema as _;
295 use $crate::schema::VldSchema as _;
296 let mut __vld_properties = $crate::serde_json::Map::new();
297 let mut __vld_required: ::std::vec::Vec<::std::string::String> =
298 ::std::vec::Vec::new();
299
300 $(
301 {
302 let __vld_field_schema = $schema;
303 let __vld_key = $crate::__vld_resolve_key!(
304 stringify!($field_name) $(, $rename)?
305 );
306 __vld_properties.insert(
307 ::std::string::String::from(__vld_key),
308 __vld_field_schema.json_schema(),
309 );
310 __vld_required.push(
311 ::std::string::String::from(__vld_key),
312 );
313 }
314 )*
315
316 $crate::serde_json::json!({
317 "type": "object",
318 "required": __vld_required,
319 "properties": $crate::serde_json::Value::Object(__vld_properties),
320 })
321 }
322
323 /// Wrap `json_schema()` in a minimal OpenAPI 3.1 document.
324 ///
325 /// Requires the `openapi` feature.
326 pub fn to_openapi_document() -> $crate::serde_json::Value {
327 $crate::json_schema::to_openapi_document(stringify!($name), &Self::json_schema())
328 }
329
330 /// Collect `(name, json_schema_fn)` pairs for all nested schemas
331 /// used by fields of this struct into `out`.
332 ///
333 /// Recurses transitively (with name dedup) so grandchild schemas
334 /// are registered for OpenAPI components.
335 #[doc(hidden)]
336 pub fn __vld_collect_nested_schemas(
337 __vld_out: &mut ::std::vec::Vec<$crate::json_schema::NestedSchemaEntry>,
338 ) {
339 use $crate::json_schema::CollectNestedSchemas as _;
340 use $crate::schema::VldSchema as _;
341
342 $(
343 {
344 let __vld_field_schema = $schema;
345 __vld_field_schema.collect_nested_schemas(__vld_out);
346 }
347 )*
348 }
349
350 /// Collect `(name, json_schema_fn)` pairs for all nested schemas
351 /// used by fields of this struct.
352 ///
353 /// Used by `vld-utoipa`'s `impl_to_schema!` to automatically register
354 /// nested types as OpenAPI components.
355 #[doc(hidden)]
356 pub fn __vld_nested_schemas()
357 -> ::std::vec::Vec<$crate::json_schema::NestedSchemaEntry>
358 {
359 let mut __vld_out: ::std::vec::Vec<$crate::json_schema::NestedSchemaEntry> =
360 ::std::vec::Vec::new();
361 Self::__vld_collect_nested_schemas(&mut __vld_out);
362 __vld_out
363 }
364 }
365
366 impl $crate::json_schema::OpenApiParameterIn for $name {
367 fn parameter_in() -> Option<&'static str> {
368 $crate::schema!(@param_in $(#[ $($struct_attr)* ])*)
369 }
370 }
371 }
372 }
373 }
374 };
375
376 // Parse `#[into_params(parameter_in = ...)]` for [`OpenApiParameterIn`].
377 (@param_in) => {
378 None::<&'static str>
379 };
380 (@param_in #[ into_params ( parameter_in = Query ) ] $(#[ $($rest:tt)* ])*) => {
381 Some("query")
382 };
383 (@param_in #[ into_params ( parameter_in = Path ) ] $(#[ $($rest:tt)* ])*) => {
384 Some("path")
385 };
386 (@param_in #[ into_params ( parameter_in = Header ) ] $(#[ $($rest:tt)* ])*) => {
387 Some("header")
388 };
389 (@param_in #[ into_params ( parameter_in = Cookie ) ] $(#[ $($rest:tt)* ])*) => {
390 Some("cookie")
391 };
392 (@param_in #[ $($skip:tt)* ] $(#[ $($rest:tt)* ])*) => {
393 $crate::schema!(@param_in $(#[ $($rest)* ])*)
394 };
395}
396
397/// Generate `validate_fields()` and `parse_lenient()` methods for a struct
398/// previously defined with [`schema!`].
399///
400/// Syntax mirrors `schema!`, but without visibility/attributes:
401///
402/// ```ignore
403/// vld::impl_validate_fields!(User {
404/// name: String => vld::string().min(2),
405/// age: i64 => vld::number().int(),
406/// });
407/// ```
408///
409/// Fields can also use `as "json_key"` to match a renamed JSON property:
410///
411/// ```ignore
412/// vld::impl_validate_fields!(User {
413/// first_name: String as "firstName" => vld::string().min(2),
414/// });
415/// ```
416///
417/// Generated methods:
418///
419/// - **`validate_fields(input)`** — validate each field, return `Vec<FieldResult>`
420/// - **`parse_lenient(input)`** — build the struct even if some fields fail
421/// (uses `Default` for invalid fields), returns [`ParseResult<Self>`](crate::error::ParseResult)
422///
423/// The returned [`ParseResult`](crate::error::ParseResult) can be inspected,
424/// converted to JSON, or saved to a file at any time via `.save_to_file(path)`.
425///
426/// **Requires:**
427/// - Field output types: `serde::Serialize`
428/// - For `parse_lenient`: field types also need `Default`
429/// - For `save_to_file` / `to_json_string`: the struct needs `serde::Serialize`
430#[macro_export]
431macro_rules! impl_validate_fields {
432 (
433 $name:ident {
434 $( $field_name:ident : $field_type:ty $(as $rename:literal)? => $schema:expr ),* $(,)?
435 }
436 ) => {
437 impl $name {
438 /// Validate each field individually and return per-field results.
439 ///
440 /// Unlike `parse()`, this does **not** fail fast — every field is
441 /// validated and you see which fields passed and which failed.
442 pub fn validate_fields<__VldInputT: $crate::input::VldInput + ?Sized>(
443 input: &__VldInputT,
444 ) -> ::std::result::Result<
445 ::std::vec::Vec<$crate::error::FieldResult>,
446 $crate::error::VldError,
447 > {
448 let __vld_json = <__VldInputT as $crate::input::VldInput>::to_json_value(input)?;
449 Self::validate_fields_value(&__vld_json)
450 }
451
452 /// Validate each field individually from a `serde_json::Value`.
453 pub fn validate_fields_value(
454 __vld_json: &$crate::serde_json::Value,
455 ) -> ::std::result::Result<
456 ::std::vec::Vec<$crate::error::FieldResult>,
457 $crate::error::VldError,
458 > {
459 use $crate::schema::VldSchema as _;
460
461 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
462 $crate::error::VldError::single(
463 $crate::error::IssueCode::InvalidType {
464 expected: ::std::string::String::from("object"),
465 received: $crate::error::value_type_name(__vld_json),
466 },
467 ::std::format!(
468 "Expected object, received {}",
469 $crate::error::value_type_name(__vld_json)
470 ),
471 )
472 })?;
473
474 let mut __vld_results: ::std::vec::Vec<$crate::error::FieldResult> =
475 ::std::vec::Vec::new();
476
477 $(
478 {
479 let __vld_field_schema = $schema;
480 let __vld_key = $crate::__vld_resolve_key!(
481 stringify!($field_name) $(, $rename)?
482 );
483 let __vld_field_value = __vld_obj
484 .get(__vld_key)
485 .unwrap_or(&$crate::serde_json::Value::Null);
486
487 let __vld_result = $crate::object::DynSchema::dyn_parse(
488 &__vld_field_schema,
489 __vld_field_value,
490 );
491
492 __vld_results.push($crate::error::FieldResult {
493 name: ::std::string::String::from(__vld_key),
494 input: __vld_field_value.clone(),
495 result: __vld_result,
496 });
497 }
498 )*
499
500 ::std::result::Result::Ok(__vld_results)
501 }
502
503 /// Parse leniently: build the struct even when some fields fail.
504 ///
505 /// - Valid fields get their parsed value.
506 /// - Invalid fields fall back to `Default::default()`.
507 ///
508 /// Returns a [`ParseResult`](crate::error::ParseResult) that wraps
509 /// the struct and per-field diagnostics. You can inspect it, convert
510 /// to JSON, or save to a file whenever you need.
511 pub fn parse_lenient<__VldInputT: $crate::input::VldInput + ?Sized>(
512 input: &__VldInputT,
513 ) -> ::std::result::Result<
514 $crate::error::ParseResult<$name>,
515 $crate::error::VldError,
516 > {
517 let __vld_json = <__VldInputT as $crate::input::VldInput>::to_json_value(input)?;
518 Self::parse_lenient_value(&__vld_json)
519 }
520
521 /// Parse leniently from a `serde_json::Value`.
522 pub fn parse_lenient_value(
523 __vld_json: &$crate::serde_json::Value,
524 ) -> ::std::result::Result<
525 $crate::error::ParseResult<$name>,
526 $crate::error::VldError,
527 > {
528 use $crate::schema::VldSchema as _;
529
530 let __vld_obj = __vld_json.as_object().ok_or_else(|| {
531 $crate::error::VldError::single(
532 $crate::error::IssueCode::InvalidType {
533 expected: ::std::string::String::from("object"),
534 received: $crate::error::value_type_name(__vld_json),
535 },
536 ::std::format!(
537 "Expected object, received {}",
538 $crate::error::value_type_name(__vld_json)
539 ),
540 )
541 })?;
542
543 let mut __vld_results: ::std::vec::Vec<$crate::error::FieldResult> =
544 ::std::vec::Vec::new();
545
546 $(
547 #[allow(non_snake_case)]
548 let $field_name: $field_type = {
549 let __vld_field_schema = $schema;
550 let __vld_key = $crate::__vld_resolve_key!(
551 stringify!($field_name) $(, $rename)?
552 );
553 let __vld_field_value = __vld_obj
554 .get(__vld_key)
555 .unwrap_or(&$crate::serde_json::Value::Null);
556
557 match __vld_field_schema.parse_value(__vld_field_value) {
558 ::std::result::Result::Ok(v) => {
559 let __json_repr = $crate::serde_json::to_value(&v)
560 .unwrap_or_else(|_| __vld_field_value.clone());
561 __vld_results.push($crate::error::FieldResult {
562 name: ::std::string::String::from(__vld_key),
563 input: __vld_field_value.clone(),
564 result: ::std::result::Result::Ok(__json_repr),
565 });
566 v
567 }
568 ::std::result::Result::Err(e) => {
569 __vld_results.push($crate::error::FieldResult {
570 name: ::std::string::String::from(__vld_key),
571 input: __vld_field_value.clone(),
572 result: ::std::result::Result::Err(e),
573 });
574 <$field_type as ::std::default::Default>::default()
575 }
576 }
577 };
578 )*
579
580 let __vld_struct = $name {
581 $( $field_name, )*
582 };
583
584 ::std::result::Result::Ok(
585 $crate::error::ParseResult::new(__vld_struct, __vld_results)
586 )
587 }
588 }
589 };
590}
591
592/// Combined macro: generates the struct, `parse()`, **and** `validate_fields()` /
593/// `parse_lenient()` in a single declaration — no need to repeat field schemas.
594///
595/// This is equivalent to calling `schema!` + `impl_validate_fields!` together.
596///
597/// **Extra requirements** compared to `schema!`:
598/// - All field types must implement `serde::Serialize` (for per-field JSON output)
599/// - All field types must implement `Default` (for lenient fallback values)
600///
601/// # Example
602///
603/// ```ignore
604/// vld::schema_validated! {
605/// #[derive(Debug, serde::Serialize)]
606/// pub struct User {
607/// pub name: String => vld::string().min(2),
608/// pub age: Option<i64> => vld::number().int().optional(),
609/// }
610/// }
611///
612/// // Has parse(), validate_fields(), parse_lenient(), etc.
613/// let result = User::parse_lenient(r#"{"name":"X"}"#).unwrap();
614/// result.save_to_file(std::path::Path::new("out.json")).unwrap();
615/// ```
616#[macro_export]
617macro_rules! schema_validated {
618 (
619 $(#[ $($struct_attr:tt)* ])*
620 $vis:vis struct $name:ident {
621 $(
622 $(#[$field_meta:meta])*
623 $field_vis:vis $field_name:ident : $field_type:ty $(as $rename:literal)? => $schema:expr
624 ),* $(,)?
625 }
626 ) => {
627 // 1. Generate the struct + parse/parse_value (same as schema!)
628 $crate::schema! {
629 $(#[ $($struct_attr)* ])*
630 $vis struct $name {
631 $(
632 $(#[$field_meta])*
633 $field_vis $field_name : $field_type $(as $rename)? => $schema
634 ),*
635 }
636 }
637
638 // 2. Generate validate_fields + parse_lenient (same as impl_validate_fields!)
639 $crate::impl_validate_fields!($name {
640 $( $field_name : $field_type $(as $rename)? => $schema ),*
641 });
642 };
643}
644
645/// Attach validation rules to an **existing** struct.
646///
647/// Unlike [`schema!`] which creates the struct, this macro takes a struct you
648/// already have and generates `validate()` and `is_valid()` instance methods.
649///
650/// The struct must implement `serde::Serialize`.
651///
652/// The struct does **not** need `#[derive(Serialize)]` or `#[derive(Debug)]` —
653/// each field is serialized individually (standard types like `String`, `f64`,
654/// `Vec<T>` already implement `Serialize`).
655///
656/// # Example
657///
658/// ```
659/// use vld::prelude::*;
660///
661/// // No Serialize or Debug required on the struct itself
662/// struct Product {
663/// name: String,
664/// price: f64,
665/// tags: Vec<String>,
666/// }
667///
668/// vld::impl_rules!(Product {
669/// name => vld::string().min(2).max(100),
670/// price => vld::number().positive(),
671/// tags => vld::array(vld::string().min(1)).max_len(10),
672/// });
673///
674/// let p = Product {
675/// name: "Widget".into(),
676/// price: 9.99,
677/// tags: vec!["sale".into()],
678/// };
679/// assert!(p.is_valid());
680///
681/// let bad = Product {
682/// name: "X".into(),
683/// price: -1.0,
684/// tags: vec![],
685/// };
686/// assert!(!bad.is_valid());
687/// let err = bad.validate().unwrap_err();
688/// assert!(err.issues.len() >= 2);
689/// ```
690#[macro_export]
691macro_rules! impl_rules {
692 (
693 $name:ident {
694 $( $field:ident => $schema:expr ),* $(,)?
695 }
696 ) => {
697 impl $name {
698 /// Validate this instance against the declared rules.
699 ///
700 /// Each field is serialized to JSON individually and checked
701 /// against its schema. All errors are accumulated.
702 pub fn validate(&self) -> ::std::result::Result<(), $crate::error::VldError> {
703 use $crate::schema::VldSchema as _;
704 let mut __vld_errors = $crate::error::VldError::new();
705
706 $(
707 {
708 let __vld_field_json = $crate::serde_json::to_value(&self.$field)
709 .map_err(|e| {
710 $crate::error::VldError::single(
711 $crate::error::IssueCode::ParseError,
712 ::std::format!(
713 "Serialization error for field '{}': {}",
714 stringify!($field), e
715 ),
716 )
717 });
718 match __vld_field_json {
719 ::std::result::Result::Ok(ref __vld_val) => {
720 let __vld_field_schema = $schema;
721 if let ::std::result::Result::Err(e) =
722 __vld_field_schema.parse_value(__vld_val)
723 {
724 __vld_errors = $crate::error::VldError::merge(
725 __vld_errors,
726 $crate::error::VldError::with_prefix(
727 e,
728 $crate::error::PathSegment::Field(
729 ::std::string::String::from(stringify!($field)),
730 ),
731 ),
732 );
733 }
734 }
735 ::std::result::Result::Err(e) => {
736 __vld_errors = $crate::error::VldError::merge(
737 __vld_errors,
738 $crate::error::VldError::with_prefix(
739 e,
740 $crate::error::PathSegment::Field(
741 ::std::string::String::from(stringify!($field)),
742 ),
743 ),
744 );
745 }
746 }
747 }
748 )*
749
750 if __vld_errors.is_empty() {
751 ::std::result::Result::Ok(())
752 } else {
753 ::std::result::Result::Err(__vld_errors)
754 }
755 }
756
757 /// Check if this instance passes all validation rules.
758 pub fn is_valid(&self) -> bool {
759 self.validate().is_ok()
760 }
761 }
762 };
763}
764
765/// Generate `impl Default` for a struct created by [`schema!`].
766///
767/// Use this instead of `#[derive(Default)]` to automatically generate a
768/// `Default` implementation bounded on all field types implementing `Default`.
769///
770/// # Example
771///
772/// ```
773/// use vld::prelude::*;
774///
775/// vld::schema! {
776/// #[derive(Debug)]
777/// pub struct Config {
778/// pub host: String => vld::string().with_default("localhost".into()),
779/// pub port: Option<i64> => vld::number().int().optional(),
780/// pub tags: Vec<String> => vld::array(vld::string()).with_default(vec![]),
781/// }
782/// }
783///
784/// vld::impl_default!(Config { host, port, tags });
785///
786/// let cfg = Config::default();
787/// assert_eq!(cfg.host, ""); // String::default()
788/// assert_eq!(cfg.port, None); // Option::default()
789/// assert!(cfg.tags.is_empty()); // Vec::default()
790/// ```
791#[macro_export]
792macro_rules! impl_default {
793 ($name:ident { $($field:ident),* $(,)? }) => {
794 impl ::std::default::Default for $name {
795 fn default() -> Self {
796 Self {
797 $( $field: ::std::default::Default::default(), )*
798 }
799 }
800 }
801 };
802}
803
804/// Create a union schema from 2 or more schemas.
805///
806/// Dispatches to `vld::union()` for 2 schemas and `vld::union3()` for 3.
807/// For 4+ schemas, unions are nested automatically.
808///
809/// # Examples
810///
811/// ```rust
812/// use vld::prelude::*;
813///
814/// // 2 schemas
815/// let s = vld::union!(vld::string(), vld::number().int());
816/// assert!(s.parse(r#""hello""#).is_ok());
817/// assert!(s.parse("42").is_ok());
818///
819/// // 3 schemas
820/// let s = vld::union!(vld::string(), vld::number().int(), vld::boolean());
821/// assert!(s.parse("true").is_ok());
822/// ```
823#[macro_export]
824macro_rules! union {
825 // 2 schemas
826 ($a:expr, $b:expr $(,)?) => {
827 $crate::union($a, $b)
828 };
829 // 3 schemas
830 ($a:expr, $b:expr, $c:expr $(,)?) => {
831 $crate::union3($a, $b, $c)
832 };
833 // 4 schemas — nest as union(union(a, b), union(c, d))
834 ($a:expr, $b:expr, $c:expr, $d:expr $(,)?) => {
835 $crate::union($crate::union($a, $b), $crate::union($c, $d))
836 };
837 // 5 schemas
838 ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr $(,)?) => {
839 $crate::union($crate::union3($a, $b, $c), $crate::union($d, $e))
840 };
841 // 6 schemas
842 ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr $(,)?) => {
843 $crate::union($crate::union3($a, $b, $c), $crate::union3($d, $e, $f))
844 };
845}