Skip to main content

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}