Skip to main content

vld/
schema.rs

1use serde_json::Value;
2use std::marker::PhantomData;
3
4use crate::combinators::{
5    ZCatch, ZDescribe, ZIntersection, ZPipe, ZRefine, ZSuperRefine, ZTransform, ZUnion2,
6};
7use crate::error::VldError;
8use crate::input::VldInput;
9use crate::modifiers::{ZDefault, ZNullable, ZNullish, ZOptional};
10
11/// Core validation schema trait.
12///
13/// Every validator in `vld` implements this trait. The associated type `Output`
14/// defines what Rust type will be produced after successful parsing.
15///
16/// # Example
17/// ```
18/// use vld::prelude::*;
19///
20/// let schema = vld::string().min(3);
21/// let result = schema.parse(r#""hello""#);
22/// assert!(result.is_ok());
23/// ```
24pub trait VldSchema: Sized {
25    /// The Rust type produced by this schema after successful parsing.
26    type Output;
27
28    /// Parse and validate a `serde_json::Value`.
29    fn parse_value(&self, value: &Value) -> Result<Self::Output, VldError>;
30
31    /// Parse from any supported input (JSON string, file path, `serde_json::Value`, etc.)
32    fn parse<I: VldInput + ?Sized>(&self, input: &I) -> Result<Self::Output, VldError> {
33        let json = input.to_json_value()?;
34        self.parse_value(&json)
35    }
36
37    /// Validate an existing Rust value against this schema.
38    ///
39    /// The value is serialized to JSON via `serde`, then validated.
40    /// Returns the parsed output on success.
41    ///
42    /// Requires the `serialize` feature.
43    ///
44    /// # Example
45    /// ```ignore
46    /// use vld::prelude::*;
47    ///
48    /// let schema = vld::array(vld::number().int().positive()).min_len(1);
49    /// let data = vec![1, 2, 3];
50    /// assert!(schema.validate(&data).is_ok());
51    /// ```
52    #[cfg(feature = "serialize")]
53    fn validate<T: serde::Serialize>(&self, value: &T) -> Result<Self::Output, VldError> {
54        let json = serde_json::to_value(value).map_err(|e| {
55            VldError::single(
56                crate::error::IssueCode::ParseError,
57                format!("Serialization error: {}", e),
58            )
59        })?;
60        self.parse_value(&json)
61    }
62
63    /// Check if an existing Rust value passes this schema's validation.
64    ///
65    /// Requires the `serialize` feature.
66    ///
67    /// # Example
68    /// ```ignore
69    /// use vld::prelude::*;
70    ///
71    /// let schema = vld::string().email();
72    /// assert!(schema.is_valid(&"user@example.com"));
73    /// ```
74    #[cfg(feature = "serialize")]
75    fn is_valid<T: serde::Serialize>(&self, value: &T) -> bool {
76        self.validate(value).is_ok()
77    }
78
79    /// Make this field optional. Missing or null values become `None`.
80    fn optional(self) -> ZOptional<Self> {
81        ZOptional::new(self)
82    }
83
84    /// Allow null values. Null becomes `None`.
85    fn nullable(self) -> ZNullable<Self> {
86        ZNullable::new(self)
87    }
88
89    /// Provide a default value when the field is missing or null.
90    fn with_default(self, value: Self::Output) -> ZDefault<Self>
91    where
92        Self::Output: Clone,
93    {
94        ZDefault::new(self, value)
95    }
96
97    /// Add a custom refinement check without changing the output type.
98    fn refine<F>(self, check: F, message: &str) -> ZRefine<Self, F>
99    where
100        F: Fn(&Self::Output) -> bool,
101    {
102        ZRefine::new(self, check, message)
103    }
104
105    /// Transform the output value after successful parsing.
106    fn transform<F, U>(self, f: F) -> ZTransform<Self, F, U>
107    where
108        F: Fn(Self::Output) -> U,
109    {
110        ZTransform::new(self, f)
111    }
112
113    /// Make this field nullish (both optional and nullable).
114    fn nullish(self) -> ZNullish<Self> {
115        ZNullish::new(self)
116    }
117
118    /// Return a fallback value on ANY validation error.
119    fn catch(self, fallback: Self::Output) -> ZCatch<Self>
120    where
121        Self::Output: Clone,
122    {
123        ZCatch::new(self, fallback)
124    }
125
126    /// Chain this schema's output into another schema.
127    ///
128    /// The output of `self` is serialized to JSON, then parsed by `next`.
129    fn pipe<S: VldSchema>(self, next: S) -> ZPipe<Self, S>
130    where
131        Self::Output: serde::Serialize,
132    {
133        ZPipe::new(self, next)
134    }
135
136    /// Attach a human-readable description/label to this schema.
137    ///
138    /// The description is stored as metadata and does not affect validation.
139    fn describe(self, description: &str) -> ZDescribe<Self> {
140        ZDescribe::new(self, description)
141    }
142
143    /// Add a custom refinement that can produce multiple errors.
144    ///
145    /// Unlike `refine()` which returns a single bool, `super_refine` receives
146    /// a mutable `VldError` collector and can push multiple issues.
147    fn super_refine<F>(self, check: F) -> ZSuperRefine<Self, F>
148    where
149        F: Fn(&Self::Output, &mut VldError),
150    {
151        ZSuperRefine::new(self, check)
152    }
153
154    /// Create a union: this schema **or** another. Returns `Either<Self::Output, B::Output>`.
155    fn or<B: VldSchema>(self, other: B) -> ZUnion2<Self, B> {
156        ZUnion2::new(self, other)
157    }
158
159    /// Create an intersection: input must satisfy **both** schemas.
160    fn and<B: VldSchema>(self, other: B) -> ZIntersection<Self, B> {
161        ZIntersection::new(self, other)
162    }
163
164    /// Override the error message for this schema.
165    ///
166    /// On validation failure **all** issues produced by the inner schema
167    /// will have their message replaced with the provided string.
168    ///
169    /// Similar to Zod's `.message("...")`.
170    ///
171    /// # Example
172    /// ```
173    /// use vld::prelude::*;
174    ///
175    /// let schema = vld::string().min(3).message("Too short");
176    /// let err = schema.parse(r#""ab""#).unwrap_err();
177    /// assert_eq!(err.issues[0].message, "Too short");
178    /// ```
179    fn message(self, msg: impl Into<String>) -> crate::combinators::ZMessage<Self> {
180        crate::combinators::ZMessage::new(self, msg)
181    }
182}
183
184/// Trait for types that can be parsed from a `serde_json::Value`.
185///
186/// Auto-implemented by the [`schema!`](crate::schema!) macro and
187/// [`#[derive(Validate)]`](crate::Validate) derive macro.
188///
189/// Used by framework integration crates (e.g., `vld-axum`, `vld-actix`)
190/// to provide type-safe request body extraction.
191pub trait VldParse: Sized {
192    /// Parse and validate a `serde_json::Value` into this type.
193    fn vld_parse_value(value: &serde_json::Value) -> Result<Self, crate::error::VldError>;
194}
195
196/// Schema for parsing nested structures. Created via [`vld::nested()`](crate::nested)
197/// or the [`vld::nested!`](crate::nested!) macro.
198pub struct NestedSchema<T, F>
199where
200    F: Fn(&Value) -> Result<T, VldError>,
201{
202    parse_fn: F,
203    #[allow(dead_code)]
204    pub(crate) name: Option<&'static str>,
205    /// Returns the full JSON Schema of the nested type (for OpenAPI component registration).
206    #[allow(dead_code)]
207    pub(crate) json_schema_fn: Option<fn() -> serde_json::Value>,
208    _phantom: PhantomData<T>,
209}
210
211impl<T, F> NestedSchema<T, F>
212where
213    F: Fn(&Value) -> Result<T, VldError>,
214{
215    pub fn new(f: F) -> Self {
216        Self {
217            parse_fn: f,
218            name: None,
219            json_schema_fn: None,
220            _phantom: PhantomData,
221        }
222    }
223
224    pub fn new_named(
225        f: F,
226        name: &'static str,
227        json_schema_fn: Option<fn() -> serde_json::Value>,
228    ) -> Self {
229        Self {
230            parse_fn: f,
231            name: Some(name),
232            json_schema_fn,
233            _phantom: PhantomData,
234        }
235    }
236}
237
238impl<T, F> VldSchema for NestedSchema<T, F>
239where
240    F: Fn(&Value) -> Result<T, VldError>,
241{
242    type Output = T;
243    fn parse_value(&self, value: &Value) -> Result<T, VldError> {
244        (self.parse_fn)(value)
245    }
246}