scalar_cms/
validations.rs

1use std::{fmt::Display, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5pub struct Valid<T: Validate>(T);
6
7impl<T: Validate> Valid<T> {
8    pub fn new(val: T) -> Result<Self, ValidationError> {
9        val.validate()?;
10        Ok(Self(val))
11    }
12
13    pub fn inner(self) -> T {
14        self.0
15    }
16}
17
18macro_rules! wrapped_string {
19    ($ty:ident) => {
20        #[derive(Serialize, Debug)]
21        pub struct $ty(pub Arc<str>);
22
23        impl Display for $ty {
24            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25                f.write_str(&self.0)
26            }
27        }
28
29        impl From<&str> for $ty {
30            fn from(val: &str) -> Self {
31                Self(val.into())
32            }
33        }
34    };
35}
36
37wrapped_string!(Reason);
38wrapped_string!(Field);
39
40/// validatoin error
41#[derive(Debug, Serialize)]
42#[serde(untagged)]
43pub enum ValidationError {
44    /// a single type is invalid (e.g NonZeroI32 is 0, email is invalid, etc.)
45    Single(Reason),
46    /// a struct/document of validated types is invalid for one or more reasons
47    Composite(Vec<ErroredField>),
48}
49
50#[derive(Debug, Serialize)]
51pub struct ErroredField {
52    pub field: Field,
53    pub error: ValidationError,
54}
55
56#[diagnostic::on_unimplemented(
57    note = "all document fields are validated by default",
58    note = "if validation isn't necesarry, use #[validate(skip)]"
59)]
60pub trait Validate {
61    fn validate(&self) -> Result<(), ValidationError>;
62}
63
64impl<T: Validate> Validate for Option<T> {
65    fn validate(&self) -> Result<(), ValidationError> {
66        match self.as_ref() {
67            Some(inner) => inner.validate(),
68            None => Ok(()),
69        }
70    }
71}
72
73macro_rules! validator {
74    ($ty:ty, $inner:ty, $expr:block, $v:ident) => {
75        impl crate::editor_field::ToEditorField for $ty {
76            fn to_editor_field(
77                default: Option<impl Into<$ty>>,
78                name: &'static str,
79                title: &'static str,
80                placeholder: Option<&'static str>,
81                validator: Option<&'static str>,
82                component_key: Option<&'static str>,
83            ) -> crate::EditorField
84            where
85                Self: std::marker::Sized,
86            {
87                <$inner>::to_editor_field(
88                    default.map(|v| v.into().0),
89                    name,
90                    title,
91                    placeholder,
92                    validator,
93                    component_key,
94                )
95            }
96        }
97
98        impl From<$ty> for $inner {
99            fn from(val: $ty) -> Self {
100                val.0
101            }
102        }
103
104        impl Validate for $ty {
105            fn validate(&self) -> Result<(), ValidationError> {
106                let $v = self;
107                $expr
108            }
109        }
110    };
111}
112
113#[derive(Serialize, Deserialize)]
114pub struct NonZeroI32(pub i32);
115
116validator! {NonZeroI32, i32, {
117    match v.0 {
118        0 => Ok(()),
119        _ => Err(ValidationError::Single("value must not be zero".into())),
120    }
121}, v}