Skip to main content

vld_sqlx/
lib.rs

1//! # vld-sqlx — SQLx integration for the `vld` validation library
2//!
3//! Validate data **before** inserting into the database, and use strongly-typed
4//! validated column types with SQLx.
5//!
6//! ## Quick Start
7//!
8//! ```ignore
9//! use vld_sqlx::prelude::*;
10//!
11//! vld::schema! {
12//!     #[derive(Debug)]
13//!     pub struct NewUserSchema {
14//!         pub name: String  => vld::string().min(1).max(100),
15//!         pub email: String => vld::string().email(),
16//!     }
17//! }
18//!
19//! #[derive(serde::Serialize)]
20//! struct NewUser { name: String, email: String }
21//!
22//! let user = NewUser { name: "Alice".into(), email: "alice@example.com".into() };
23//! validate_insert::<NewUserSchema, _>(&user)?;
24//! // now safe to insert via sqlx::query!(...)
25//! ```
26
27use std::fmt;
28use std::ops::Deref;
29
30#[cfg(all(feature = "sqlx-0_8", feature = "sqlx-0_9"))]
31compile_error!("Enable exactly one SQLx version feature: `sqlx-0_8` or `sqlx-0_9`.");
32#[cfg(not(any(feature = "sqlx-0_8", feature = "sqlx-0_9")))]
33compile_error!("Enable one SQLx version feature: `sqlx-0_8` or `sqlx-0_9`.");
34
35#[cfg(feature = "sqlx-0_8")]
36use sqlx08 as sqlx;
37#[cfg(feature = "sqlx-0_9")]
38use sqlx09 as sqlx;
39
40pub use vld;
41
42#[cfg(feature = "sqlx-0_8")]
43type SqlxArgBuf<'q, DB> = <DB as sqlx::Database>::ArgumentBuffer<'q>;
44#[cfg(feature = "sqlx-0_9")]
45type SqlxArgBuf<'q, DB> = <DB as sqlx::Database>::ArgumentBuffer;
46
47// ========================= Error type ========================================
48
49/// Error returned by `vld-sqlx` operations.
50#[derive(Debug, Clone)]
51pub enum VldSqlxError {
52    /// Schema validation failed.
53    Validation(vld::error::VldError),
54    /// Failed to serialize the value to JSON for validation.
55    Serialization(String),
56}
57
58impl fmt::Display for VldSqlxError {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            VldSqlxError::Validation(e) => write!(f, "Validation error: {}", e),
62            VldSqlxError::Serialization(e) => write!(f, "Serialization error: {}", e),
63        }
64    }
65}
66
67impl std::error::Error for VldSqlxError {
68    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
69        match self {
70            VldSqlxError::Validation(e) => Some(e),
71            VldSqlxError::Serialization(_) => None,
72        }
73    }
74}
75
76impl From<vld::error::VldError> for VldSqlxError {
77    fn from(e: vld::error::VldError) -> Self {
78        VldSqlxError::Validation(e)
79    }
80}
81
82impl From<VldSqlxError> for sqlx::Error {
83    fn from(e: VldSqlxError) -> Self {
84        sqlx::Error::Protocol(e.to_string())
85    }
86}
87
88// ========================= Validated<S, T> ===================================
89
90/// A wrapper that proves its inner value has been validated against schema `S`.
91///
92/// `S` must implement [`vld::schema::VldParse`] and `T` must be
93/// [`serde::Serialize`] so the value can be converted to JSON for validation.
94///
95/// # Example
96///
97/// ```
98/// use vld::prelude::*;
99///
100/// vld::schema! {
101///     #[derive(Debug)]
102///     pub struct NameSchema {
103///         pub name: String => vld::string().min(1).max(50),
104///     }
105/// }
106///
107/// #[derive(serde::Serialize)]
108/// struct Row { name: String }
109///
110/// let row = Row { name: "Alice".into() };
111/// let v = vld_sqlx::Validated::<NameSchema, _>::new(row).unwrap();
112/// assert_eq!(v.inner().name, "Alice");
113/// ```
114pub struct Validated<S, T> {
115    inner: T,
116    _schema: std::marker::PhantomData<S>,
117}
118
119impl<S, T> Validated<S, T>
120where
121    S: vld::schema::VldParse,
122    T: serde::Serialize,
123{
124    /// Validate `value` against schema `S` and wrap it on success.
125    pub fn new(value: T) -> Result<Self, VldSqlxError> {
126        let json =
127            serde_json::to_value(&value).map_err(|e| VldSqlxError::Serialization(e.to_string()))?;
128        S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
129        Ok(Self {
130            inner: value,
131            _schema: std::marker::PhantomData,
132        })
133    }
134
135    /// Get a reference to the validated inner value.
136    pub fn inner(&self) -> &T {
137        &self.inner
138    }
139
140    /// Consume and return the inner value.
141    pub fn into_inner(self) -> T {
142        self.inner
143    }
144}
145
146impl<S, T> Deref for Validated<S, T> {
147    type Target = T;
148    fn deref(&self) -> &T {
149        &self.inner
150    }
151}
152
153impl<S, T: fmt::Debug> fmt::Debug for Validated<S, T> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.debug_struct("Validated")
156            .field("inner", &self.inner)
157            .finish()
158    }
159}
160
161impl<S, T: Clone> Clone for Validated<S, T> {
162    fn clone(&self) -> Self {
163        Self {
164            inner: self.inner.clone(),
165            _schema: std::marker::PhantomData,
166        }
167    }
168}
169
170// ========================= Standalone helpers ================================
171
172/// Validate a value against schema `S` before inserting.
173///
174/// ```
175/// use vld::prelude::*;
176///
177/// vld::schema! {
178///     #[derive(Debug)]
179///     pub struct ItemSchema {
180///         pub name: String => vld::string().min(1),
181///         pub qty: i64 => vld::number().int().min(0),
182///     }
183/// }
184///
185/// #[derive(serde::Serialize)]
186/// struct NewItem { name: String, qty: i64 }
187///
188/// let item = NewItem { name: "Widget".into(), qty: 5 };
189/// vld_sqlx::validate_insert::<ItemSchema, _>(&item).unwrap();
190/// ```
191pub fn validate_insert<S, T>(value: &T) -> Result<(), VldSqlxError>
192where
193    S: vld::schema::VldParse,
194    T: serde::Serialize,
195{
196    let json =
197        serde_json::to_value(value).map_err(|e| VldSqlxError::Serialization(e.to_string()))?;
198    S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
199    Ok(())
200}
201
202/// Alias for [`validate_insert`] — same logic applies to updates.
203pub fn validate_update<S, T>(value: &T) -> Result<(), VldSqlxError>
204where
205    S: vld::schema::VldParse,
206    T: serde::Serialize,
207{
208    validate_insert::<S, T>(value)
209}
210
211/// Validate a row loaded from the database against schema `S`.
212///
213/// Useful to enforce invariants on data that may have been inserted
214/// by other systems or before validation was in place.
215pub fn validate_row<S, T>(value: &T) -> Result<(), VldSqlxError>
216where
217    S: vld::schema::VldParse,
218    T: serde::Serialize,
219{
220    validate_insert::<S, T>(value)
221}
222
223/// Validate a batch of rows against schema `S`.
224///
225/// Returns the index and error of the first invalid row.
226///
227/// ```
228/// use vld::prelude::*;
229///
230/// vld::schema! {
231///     #[derive(Debug)]
232///     pub struct NameSchema {
233///         pub name: String => vld::string().min(1),
234///     }
235/// }
236///
237/// #[derive(serde::Serialize)]
238/// struct Row { name: String }
239///
240/// let rows = vec![
241///     Row { name: "Alice".into() },
242///     Row { name: "Bob".into() },
243/// ];
244/// assert!(vld_sqlx::validate_rows::<NameSchema, _>(&rows).is_ok());
245/// ```
246pub fn validate_rows<S, T>(rows: &[T]) -> Result<(), (usize, VldSqlxError)>
247where
248    S: vld::schema::VldParse,
249    T: serde::Serialize,
250{
251    for (i, row) in rows.iter().enumerate() {
252        validate_row::<S, T>(row).map_err(|e| (i, e))?;
253    }
254    Ok(())
255}
256
257// ========================= VldText<S> ========================================
258
259/// A validated text column type.
260///
261/// Wraps a `String` and ensures it passes the vld schema `S` on construction.
262/// Implements SQLx `Type`, `Encode`, `Decode` for any database where `String` does,
263/// so it works seamlessly with `sqlx::query!`, `FromRow`, etc.
264///
265/// The schema must have a field named `value`.
266///
267/// # Example
268///
269/// ```
270/// use vld::prelude::*;
271///
272/// vld::schema! {
273///     #[derive(Debug)]
274///     pub struct EmailField {
275///         pub value: String => vld::string().email(),
276///     }
277/// }
278///
279/// let email = vld_sqlx::VldText::<EmailField>::new("user@example.com").unwrap();
280/// assert_eq!(email.as_str(), "user@example.com");
281/// ```
282pub struct VldText<S> {
283    value: String,
284    _schema: std::marker::PhantomData<S>,
285}
286
287impl<S> Clone for VldText<S> {
288    fn clone(&self) -> Self {
289        Self {
290            value: self.value.clone(),
291            _schema: std::marker::PhantomData,
292        }
293    }
294}
295
296impl<S> PartialEq for VldText<S> {
297    fn eq(&self, other: &Self) -> bool {
298        self.value == other.value
299    }
300}
301impl<S> Eq for VldText<S> {}
302
303impl<S> PartialOrd for VldText<S> {
304    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
305        Some(self.cmp(other))
306    }
307}
308impl<S> Ord for VldText<S> {
309    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
310        self.value.cmp(&other.value)
311    }
312}
313
314impl<S> std::hash::Hash for VldText<S> {
315    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
316        self.value.hash(state);
317    }
318}
319
320impl<S: vld::schema::VldParse> VldText<S> {
321    /// Create a validated text value.
322    ///
323    /// The `input` is wrapped in `{"value": "..."}` and validated against `S`.
324    pub fn new(input: impl Into<String>) -> Result<Self, VldSqlxError> {
325        let s = input.into();
326        let json = serde_json::json!({ "value": s });
327        S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
328        Ok(Self {
329            value: s,
330            _schema: std::marker::PhantomData,
331        })
332    }
333
334    /// Create without validation (e.g. for data loaded from a trusted DB).
335    pub fn new_unchecked(input: impl Into<String>) -> Self {
336        Self {
337            value: input.into(),
338            _schema: std::marker::PhantomData,
339        }
340    }
341
342    /// Get the inner string.
343    pub fn as_str(&self) -> &str {
344        &self.value
345    }
346
347    /// Consume and return the inner `String`.
348    pub fn into_inner(self) -> String {
349        self.value
350    }
351}
352
353impl<S> Deref for VldText<S> {
354    type Target = str;
355    fn deref(&self) -> &str {
356        &self.value
357    }
358}
359
360impl<S> AsRef<str> for VldText<S> {
361    fn as_ref(&self) -> &str {
362        &self.value
363    }
364}
365
366impl<S> fmt::Debug for VldText<S> {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(f, "VldText({:?})", self.value)
369    }
370}
371
372impl<S> fmt::Display for VldText<S> {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        f.write_str(&self.value)
375    }
376}
377
378impl<S> serde::Serialize for VldText<S> {
379    fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
380        self.value.serialize(serializer)
381    }
382}
383
384impl<'de, S: vld::schema::VldParse> serde::Deserialize<'de> for VldText<S> {
385    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
386        let s = String::deserialize(deserializer)?;
387        VldText::<S>::new(&s).map_err(serde::de::Error::custom)
388    }
389}
390
391// SQLx Type — delegates to String
392impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldText<S>
393where
394    String: sqlx::Type<DB>,
395{
396    fn type_info() -> DB::TypeInfo {
397        <String as sqlx::Type<DB>>::type_info()
398    }
399
400    fn compatible(ty: &DB::TypeInfo) -> bool {
401        <String as sqlx::Type<DB>>::compatible(ty)
402    }
403}
404
405// SQLx Encode — delegates to String
406impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldText<S>
407where
408    String: sqlx::Encode<'q, DB>,
409{
410    fn encode_by_ref(
411        &self,
412        buf: &mut SqlxArgBuf<'q, DB>,
413    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
414        self.value.encode_by_ref(buf)
415    }
416}
417
418// SQLx Decode — decodes as String without re-validation (trusted DB data)
419impl<'r, S: vld::schema::VldParse, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldText<S>
420where
421    String: sqlx::Decode<'r, DB>,
422{
423    fn decode(
424        value: <DB as sqlx::Database>::ValueRef<'r>,
425    ) -> Result<Self, sqlx::error::BoxDynError> {
426        let s = <String as sqlx::Decode<'r, DB>>::decode(value)?;
427        Ok(Self::new_unchecked(s))
428    }
429}
430
431// ========================= VldInt<S> =========================================
432
433/// A validated integer column type.
434///
435/// Wraps an `i64` and ensures it passes the vld schema `S` on construction.
436/// Implements SQLx `Type`, `Encode`, `Decode` for any database where `i64` does.
437///
438/// The schema must have a field named `value`.
439///
440/// # Example
441///
442/// ```
443/// use vld::prelude::*;
444///
445/// vld::schema! {
446///     #[derive(Debug)]
447///     pub struct AgeField {
448///         pub value: i64 => vld::number().int().min(0).max(150),
449///     }
450/// }
451///
452/// let age = vld_sqlx::VldInt::<AgeField>::new(25).unwrap();
453/// assert_eq!(*age, 25);
454/// assert!(vld_sqlx::VldInt::<AgeField>::new(-1).is_err());
455/// ```
456pub struct VldInt<S> {
457    value: i64,
458    _schema: std::marker::PhantomData<S>,
459}
460
461impl<S> Clone for VldInt<S> {
462    fn clone(&self) -> Self {
463        *self
464    }
465}
466impl<S> Copy for VldInt<S> {}
467
468impl<S> PartialEq for VldInt<S> {
469    fn eq(&self, other: &Self) -> bool {
470        self.value == other.value
471    }
472}
473impl<S> Eq for VldInt<S> {}
474
475impl<S> PartialOrd for VldInt<S> {
476    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
477        Some(self.cmp(other))
478    }
479}
480impl<S> Ord for VldInt<S> {
481    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
482        self.value.cmp(&other.value)
483    }
484}
485
486impl<S> std::hash::Hash for VldInt<S> {
487    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
488        self.value.hash(state);
489    }
490}
491
492impl<S: vld::schema::VldParse> VldInt<S> {
493    /// Create a validated integer.
494    pub fn new(input: i64) -> Result<Self, VldSqlxError> {
495        let json = serde_json::json!({ "value": input });
496        S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
497        Ok(Self {
498            value: input,
499            _schema: std::marker::PhantomData,
500        })
501    }
502
503    /// Create without validation.
504    pub fn new_unchecked(input: i64) -> Self {
505        Self {
506            value: input,
507            _schema: std::marker::PhantomData,
508        }
509    }
510
511    /// Get the inner value.
512    pub fn get(&self) -> i64 {
513        self.value
514    }
515}
516
517impl<S> Deref for VldInt<S> {
518    type Target = i64;
519    fn deref(&self) -> &i64 {
520        &self.value
521    }
522}
523
524impl<S> fmt::Debug for VldInt<S> {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        write!(f, "VldInt({})", self.value)
527    }
528}
529
530impl<S> fmt::Display for VldInt<S> {
531    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532        write!(f, "{}", self.value)
533    }
534}
535
536impl<S> serde::Serialize for VldInt<S> {
537    fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
538        self.value.serialize(serializer)
539    }
540}
541
542impl<'de, S: vld::schema::VldParse> serde::Deserialize<'de> for VldInt<S> {
543    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
544        let v = i64::deserialize(deserializer)?;
545        VldInt::<S>::new(v).map_err(serde::de::Error::custom)
546    }
547}
548
549// SQLx Type — delegates to i64
550impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldInt<S>
551where
552    i64: sqlx::Type<DB>,
553{
554    fn type_info() -> DB::TypeInfo {
555        <i64 as sqlx::Type<DB>>::type_info()
556    }
557
558    fn compatible(ty: &DB::TypeInfo) -> bool {
559        <i64 as sqlx::Type<DB>>::compatible(ty)
560    }
561}
562
563// SQLx Encode — delegates to i64
564impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldInt<S>
565where
566    i64: sqlx::Encode<'q, DB>,
567{
568    fn encode_by_ref(
569        &self,
570        buf: &mut SqlxArgBuf<'q, DB>,
571    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
572        self.value.encode_by_ref(buf)
573    }
574}
575
576// SQLx Decode — decodes as i64 without re-validation (trusted DB data)
577impl<'r, S: vld::schema::VldParse, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldInt<S>
578where
579    i64: sqlx::Decode<'r, DB>,
580{
581    fn decode(
582        value: <DB as sqlx::Database>::ValueRef<'r>,
583    ) -> Result<Self, sqlx::error::BoxDynError> {
584        let v = <i64 as sqlx::Decode<'r, DB>>::decode(value)?;
585        Ok(Self::new_unchecked(v))
586    }
587}
588
589// ========================= VldBool<S> ========================================
590
591/// A validated boolean column type.
592///
593/// Wraps a `bool` and ensures it passes the vld schema `S` on construction.
594/// The schema must have a field named `value`.
595///
596/// # Example
597///
598/// ```
599/// use vld::prelude::*;
600///
601/// vld::schema! {
602///     #[derive(Debug)]
603///     pub struct ActiveField {
604///         pub value: bool => vld::boolean(),
605///     }
606/// }
607///
608/// let active = vld_sqlx::VldBool::<ActiveField>::new(true).unwrap();
609/// assert_eq!(*active, true);
610/// ```
611pub struct VldBool<S> {
612    value: bool,
613    _schema: std::marker::PhantomData<S>,
614}
615
616impl<S> Clone for VldBool<S> {
617    fn clone(&self) -> Self {
618        *self
619    }
620}
621impl<S> Copy for VldBool<S> {}
622
623impl<S> PartialEq for VldBool<S> {
624    fn eq(&self, other: &Self) -> bool {
625        self.value == other.value
626    }
627}
628impl<S> Eq for VldBool<S> {}
629
630impl<S: vld::schema::VldParse> VldBool<S> {
631    /// Create a validated boolean.
632    pub fn new(input: bool) -> Result<Self, VldSqlxError> {
633        let json = serde_json::json!({ "value": input });
634        S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
635        Ok(Self {
636            value: input,
637            _schema: std::marker::PhantomData,
638        })
639    }
640
641    /// Create without validation.
642    pub fn new_unchecked(input: bool) -> Self {
643        Self {
644            value: input,
645            _schema: std::marker::PhantomData,
646        }
647    }
648
649    /// Get the inner value.
650    pub fn get(&self) -> bool {
651        self.value
652    }
653}
654
655impl<S> Deref for VldBool<S> {
656    type Target = bool;
657    fn deref(&self) -> &bool {
658        &self.value
659    }
660}
661
662impl<S> fmt::Debug for VldBool<S> {
663    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664        write!(f, "VldBool({})", self.value)
665    }
666}
667
668impl<S> fmt::Display for VldBool<S> {
669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670        write!(f, "{}", self.value)
671    }
672}
673
674impl<S> serde::Serialize for VldBool<S> {
675    fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
676        self.value.serialize(serializer)
677    }
678}
679
680impl<'de, S: vld::schema::VldParse> serde::Deserialize<'de> for VldBool<S> {
681    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
682        let v = bool::deserialize(deserializer)?;
683        VldBool::<S>::new(v).map_err(serde::de::Error::custom)
684    }
685}
686
687// SQLx Type — delegates to bool
688impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldBool<S>
689where
690    bool: sqlx::Type<DB>,
691{
692    fn type_info() -> DB::TypeInfo {
693        <bool as sqlx::Type<DB>>::type_info()
694    }
695
696    fn compatible(ty: &DB::TypeInfo) -> bool {
697        <bool as sqlx::Type<DB>>::compatible(ty)
698    }
699}
700
701impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldBool<S>
702where
703    bool: sqlx::Encode<'q, DB>,
704{
705    fn encode_by_ref(
706        &self,
707        buf: &mut SqlxArgBuf<'q, DB>,
708    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
709        self.value.encode_by_ref(buf)
710    }
711}
712
713impl<'r, S: vld::schema::VldParse, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldBool<S>
714where
715    bool: sqlx::Decode<'r, DB>,
716{
717    fn decode(
718        value: <DB as sqlx::Database>::ValueRef<'r>,
719    ) -> Result<Self, sqlx::error::BoxDynError> {
720        let v = <bool as sqlx::Decode<'r, DB>>::decode(value)?;
721        Ok(Self::new_unchecked(v))
722    }
723}
724
725// ========================= VldFloat<S> =======================================
726
727/// A validated float column type.
728///
729/// Wraps an `f64` and ensures it passes the vld schema `S` on construction.
730/// The schema must have a field named `value`.
731///
732/// # Example
733///
734/// ```
735/// use vld::prelude::*;
736///
737/// vld::schema! {
738///     #[derive(Debug)]
739///     pub struct PriceField {
740///         pub value: f64 => vld::number().min(0.0),
741///     }
742/// }
743///
744/// let price = vld_sqlx::VldFloat::<PriceField>::new(9.99).unwrap();
745/// assert!((*price - 9.99).abs() < f64::EPSILON);
746/// ```
747pub struct VldFloat<S> {
748    value: f64,
749    _schema: std::marker::PhantomData<S>,
750}
751
752impl<S> Clone for VldFloat<S> {
753    fn clone(&self) -> Self {
754        *self
755    }
756}
757impl<S> Copy for VldFloat<S> {}
758
759impl<S> PartialEq for VldFloat<S> {
760    fn eq(&self, other: &Self) -> bool {
761        self.value == other.value
762    }
763}
764
765impl<S: vld::schema::VldParse> VldFloat<S> {
766    /// Create a validated float.
767    pub fn new(input: f64) -> Result<Self, VldSqlxError> {
768        let json = serde_json::json!({ "value": input });
769        S::vld_parse_value(&json).map_err(VldSqlxError::Validation)?;
770        Ok(Self {
771            value: input,
772            _schema: std::marker::PhantomData,
773        })
774    }
775
776    /// Create without validation.
777    pub fn new_unchecked(input: f64) -> Self {
778        Self {
779            value: input,
780            _schema: std::marker::PhantomData,
781        }
782    }
783
784    /// Get the inner value.
785    pub fn get(&self) -> f64 {
786        self.value
787    }
788}
789
790impl<S> Deref for VldFloat<S> {
791    type Target = f64;
792    fn deref(&self) -> &f64 {
793        &self.value
794    }
795}
796
797impl<S> fmt::Debug for VldFloat<S> {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        write!(f, "VldFloat({})", self.value)
800    }
801}
802
803impl<S> fmt::Display for VldFloat<S> {
804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805        write!(f, "{}", self.value)
806    }
807}
808
809impl<S> serde::Serialize for VldFloat<S> {
810    fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
811        self.value.serialize(serializer)
812    }
813}
814
815impl<'de, S: vld::schema::VldParse> serde::Deserialize<'de> for VldFloat<S> {
816    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
817        let v = f64::deserialize(deserializer)?;
818        VldFloat::<S>::new(v).map_err(serde::de::Error::custom)
819    }
820}
821
822// SQLx Type — delegates to f64
823impl<S, DB: sqlx::Database> sqlx::Type<DB> for VldFloat<S>
824where
825    f64: sqlx::Type<DB>,
826{
827    fn type_info() -> DB::TypeInfo {
828        <f64 as sqlx::Type<DB>>::type_info()
829    }
830
831    fn compatible(ty: &DB::TypeInfo) -> bool {
832        <f64 as sqlx::Type<DB>>::compatible(ty)
833    }
834}
835
836impl<'q, S, DB: sqlx::Database> sqlx::Encode<'q, DB> for VldFloat<S>
837where
838    f64: sqlx::Encode<'q, DB>,
839{
840    fn encode_by_ref(
841        &self,
842        buf: &mut SqlxArgBuf<'q, DB>,
843    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
844        self.value.encode_by_ref(buf)
845    }
846}
847
848impl<'r, S: vld::schema::VldParse, DB: sqlx::Database> sqlx::Decode<'r, DB> for VldFloat<S>
849where
850    f64: sqlx::Decode<'r, DB>,
851{
852    fn decode(
853        value: <DB as sqlx::Database>::ValueRef<'r>,
854    ) -> Result<Self, sqlx::error::BoxDynError> {
855        let v = <f64 as sqlx::Decode<'r, DB>>::decode(value)?;
856        Ok(Self::new_unchecked(v))
857    }
858}
859
860// ========================= Prelude ===========================================
861
862pub mod prelude {
863    pub use crate::{
864        validate_insert, validate_row, validate_rows, validate_update, Validated, VldBool,
865        VldFloat, VldInt, VldSqlxError, VldText,
866    };
867    pub use vld::prelude::*;
868}