Skip to main content

scalar_cms/
validations.rs

1use std::{fmt::Display, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    db::{ContentActions, ValidationContext},
7    DatabaseConnection, Document,
8};
9
10/// A wrapper type to indicate that the inner type is valid.
11#[derive(Debug, Serialize)]
12#[serde(transparent)]
13pub struct Valid<T: Document>(T);
14
15impl<T: Document + Sync> Valid<T> {
16    /// Validates the input, then returns a Valid<T>.
17    ///
18    /// # Errors
19    ///
20    /// This function will return an error if validation fails.
21    pub async fn new<DB: DatabaseConnection + ContentActions<T> + Sync>(
22        val: T,
23        ctx: ValidationContext<'_, DB, T>,
24    ) -> Result<Self, ValidationError> {
25        val.validate(ctx).await?;
26        Ok(Self(val))
27    }
28
29    pub fn inner(self) -> T {
30        self.0
31    }
32}
33
34macro_rules! wrapped_string {
35    ($ty:ident) => {
36        #[derive(Serialize, Debug)]
37        pub struct $ty(pub Arc<str>);
38
39        impl Display for $ty {
40            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41                f.write_str(&self.0)
42            }
43        }
44
45        impl<T: Into<Arc<str>>> From<T> for $ty {
46            fn from(val: T) -> Self {
47                Self(val.into())
48            }
49        }
50    };
51}
52
53wrapped_string!(Reason);
54wrapped_string!(Field);
55
56/// validatoin error
57#[derive(Debug, Serialize)]
58#[serde(untagged)]
59pub enum ValidationError {
60    /// a single type is invalid (e.g [`NonZeroI32`] is 0, email is invalid, etc.)
61    Single(Reason),
62    /// a struct/document of validated types is invalid for one or more reasons
63    Composite(Vec<ErroredField>),
64}
65
66#[derive(Debug, Serialize)]
67pub struct ErroredField {
68    pub field: Field,
69    pub error: ValidationError,
70}
71
72#[diagnostic::on_unimplemented(
73    note = "all document fields are validated by default",
74    note = "if validation isn't necesarry, use #[validate(skip)]"
75)]
76#[trait_variant::make(Send + Sized)]
77pub trait Validate {
78    /// Validates the thing.
79    ///
80    /// # Errors
81    ///
82    /// This function will return an error if validation fails.
83    async fn validate<DB: DatabaseConnection + ContentActions<D> + Sync, D: Document + Sync>(
84        &self,
85        ctx: ValidationContext<'_, DB, D>,
86    ) -> Result<(), ValidationError>;
87}
88
89impl<T: Validate + Sync> Validate for Option<T> {
90    async fn validate<DB: DatabaseConnection + ContentActions<D> + Sync, D: Document + Sync>(
91        &self,
92        ctx: ValidationContext<'_, DB, D>,
93    ) -> Result<(), ValidationError> {
94        match self.as_ref() {
95            Some(inner) => inner.validate(ctx).await,
96            None => Ok(()),
97        }
98    }
99}
100
101macro_rules! validator {
102    ($ty:ty, $inner:ty, $expr:block, $v:ident) => {
103        impl crate::editor_field::ToEditorField for $ty {
104            fn to_editor_field(
105                default: Option<impl Into<$ty>>,
106                name: &'static str,
107                title: &'static str,
108                placeholder: Option<&'static str>,
109                validator: Option<&'static str>,
110                component_key: Option<&'static str>,
111            ) -> crate::EditorField
112            where
113                Self: std::marker::Sized,
114            {
115                <$inner>::to_editor_field(
116                    default.map(|v| v.into().0),
117                    name,
118                    title,
119                    placeholder,
120                    validator,
121                    component_key,
122                )
123            }
124        }
125
126        impl From<$ty> for $inner {
127            fn from(val: $ty) -> Self {
128                val.0
129            }
130        }
131
132        impl Validate for $ty {
133            async fn validate<DB: DatabaseConnection + ContentActions<D> + Sync, D: Document>(
134                &self,
135                _ctx: ValidationContext<'_, DB, D>,
136            ) -> Result<(), ValidationError> {
137                let $v = self;
138                $expr
139            }
140        }
141    };
142}
143
144#[derive(Debug, Serialize, Deserialize)]
145pub struct NonZeroI32(pub i32);
146
147validator! {NonZeroI32, i32, {
148    match v.0 {
149        0 => Err(ValidationError::Single("value must not be zero".into())),
150        _ => Ok(()),
151    }
152}, v}