Skip to main content

noyalib/
validated.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2026 Noyalib. All rights reserved.
3
4//! Declarative validation via [`garde`] or [`validator`].
5//!
6//! Provides [`Validated<T>`] (for `garde`) and [`ValidatedValidator<T>`]
7//! (for `validator`), which are transparent wrappers that run validation
8//! rules after deserialisation.
9//!
10//! # Example (garde)
11//!
12//! ```rust
13//! use noyalib::Validated;
14//! use garde::Validate;
15//!
16//! #[derive(serde::Deserialize, Validate)]
17//! struct Server {
18//!     #[garde(length(min = 1, max = 255))]
19//!     host: String,
20//!     #[garde(range(min = 1024, max = 65535))]
21//!     port: u16,
22//! }
23//!
24//! let yaml = "host: db.local\nport: 5432\n";
25//! let wrapped: Validated<Server> = noyalib::from_str(yaml).unwrap();
26//! assert_eq!(wrapped.0.host, "db.local");
27//! ```
28
29use crate::prelude::*;
30use core::ops::{Deref, DerefMut};
31
32/// A newtype that validates its inner value using the [`garde`] crate.
33///
34/// Requires the `garde` feature.
35///
36/// # Examples
37///
38/// ```
39/// use noyalib::Validated;
40/// use garde::Validate;
41///
42/// #[derive(serde::Deserialize, Validate)]
43/// struct Port {
44///     #[garde(range(min = 1024))]
45///     n: u16,
46/// }
47/// let v: Validated<Port> = noyalib::from_str("n: 8080\n").unwrap();
48/// assert_eq!(v.0.n, 8080);
49/// ```
50#[cfg(feature = "garde")]
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub struct Validated<T>(pub T);
53
54/// A newtype that validates its inner value using the [`validator`] crate.
55///
56/// Requires the `validator` feature.
57///
58/// # Examples
59///
60/// ```
61/// use noyalib::ValidatedValidator;
62/// use validator::Validate;
63///
64/// #[derive(serde::Deserialize, Validate)]
65/// struct Port {
66///     #[validate(range(min = 1024))]
67///     n: u16,
68/// }
69/// let v: ValidatedValidator<Port> = noyalib::from_str("n: 8080\n").unwrap();
70/// assert_eq!(v.0.n, 8080);
71/// ```
72#[cfg(feature = "validator")]
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub struct ValidatedValidator<T>(pub T);
75
76#[cfg(feature = "garde")]
77impl<T> Validated<T> {
78    /// Consume the wrapper and return the inner value.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use noyalib::Validated;
84    /// let v: Validated<u32> = Validated(42);
85    /// assert_eq!(v.into_inner(), 42);
86    /// ```
87    #[inline]
88    pub fn into_inner(self) -> T {
89        self.0
90    }
91}
92
93#[cfg(feature = "validator")]
94impl<T> ValidatedValidator<T> {
95    /// Consume the wrapper and return the inner value.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use noyalib::ValidatedValidator;
101    /// let v: ValidatedValidator<u32> = ValidatedValidator(42);
102    /// assert_eq!(v.into_inner(), 42);
103    /// ```
104    #[inline]
105    pub fn into_inner(self) -> T {
106        self.0
107    }
108}
109
110#[cfg(feature = "garde")]
111impl<T> Deref for Validated<T> {
112    type Target = T;
113    #[inline]
114    fn deref(&self) -> &T {
115        &self.0
116    }
117}
118
119#[cfg(feature = "validator")]
120impl<T> Deref for ValidatedValidator<T> {
121    type Target = T;
122    #[inline]
123    fn deref(&self) -> &T {
124        &self.0
125    }
126}
127
128#[cfg(feature = "garde")]
129impl<T> DerefMut for Validated<T> {
130    #[inline]
131    fn deref_mut(&mut self) -> &mut T {
132        &mut self.0
133    }
134}
135
136#[cfg(feature = "validator")]
137impl<T> DerefMut for ValidatedValidator<T> {
138    #[inline]
139    fn deref_mut(&mut self) -> &mut T {
140        &mut self.0
141    }
142}
143
144#[cfg(feature = "garde")]
145impl<T> From<T> for Validated<T> {
146    #[inline]
147    fn from(v: T) -> Self {
148        Self(v)
149    }
150}
151
152#[cfg(feature = "validator")]
153impl<T> From<T> for ValidatedValidator<T> {
154    #[inline]
155    fn from(v: T) -> Self {
156        Self(v)
157    }
158}
159
160#[cfg(feature = "garde")]
161impl<'de, T> serde_core::Deserialize<'de> for Validated<T>
162where
163    T: serde_core::Deserialize<'de> + garde::Validate<Context = ()>,
164{
165    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166    where
167        D: serde_core::Deserializer<'de>,
168    {
169        use serde_core::de::Error as _;
170        let inner = T::deserialize(deserializer)?;
171        match inner.validate_with(&()) {
172            Ok(()) => Ok(Self(inner)),
173            Err(report) => Err(D::Error::custom(format_report(&report))),
174        }
175    }
176}
177
178#[cfg(feature = "validator")]
179impl<'de, T> serde_core::Deserialize<'de> for ValidatedValidator<T>
180where
181    T: serde_core::Deserialize<'de> + validator::Validate,
182{
183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184    where
185        D: serde_core::Deserializer<'de>,
186    {
187        use serde_core::de::Error as _;
188        let inner = T::deserialize(deserializer)?;
189        match inner.validate() {
190            Ok(()) => Ok(Self(inner)),
191            Err(errors) => Err(D::Error::custom(format_validator_errors(&errors))),
192        }
193    }
194}
195
196#[cfg(feature = "garde")]
197impl<T: serde_core::Serialize> serde_core::Serialize for Validated<T> {
198    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199    where
200        S: serde_core::Serializer,
201    {
202        self.0.serialize(serializer)
203    }
204}
205
206#[cfg(feature = "validator")]
207impl<T: serde_core::Serialize> serde_core::Serialize for ValidatedValidator<T> {
208    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209    where
210        S: serde_core::Serializer,
211    {
212        self.0.serialize(serializer)
213    }
214}
215
216/// Format a garde report as a compact single-line message.
217#[cfg(feature = "garde")]
218fn format_report(report: &garde::Report) -> String {
219    use core::fmt::Write as _;
220    let mut out = String::with_capacity(64);
221    out.push_str("validation failed: ");
222    let mut first = true;
223    for (path, error) in report.iter() {
224        if !first {
225            out.push_str("; ");
226        }
227        first = false;
228        let _ = write!(out, "{path}: {error}");
229    }
230    if first {
231        out.push_str("<no details>");
232    }
233    out
234}
235
236/// Format validator errors as a compact single-line message.
237#[cfg(feature = "validator")]
238fn format_validator_errors(errors: &validator::ValidationErrors) -> String {
239    use core::fmt::Write as _;
240    let mut out = String::with_capacity(64);
241    out.push_str("validation failed: ");
242    let mut first = true;
243    for (field, errs) in errors.field_errors() {
244        if !first {
245            out.push_str("; ");
246        }
247        first = false;
248        let _ = write!(out, "{field}: ");
249        let mut f_first = true;
250        for e in errs {
251            if !f_first {
252                out.push_str(", ");
253            }
254            f_first = false;
255            let _ = write!(out, "{e}");
256        }
257    }
258    if first {
259        out.push_str("<no details>");
260    }
261    out
262}