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