scalar_cms/
validations.rs1use std::{fmt::Display, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 db::{ContentActions, ValidationContext},
7 DatabaseConnection, Document,
8};
9
10#[derive(Debug, Serialize)]
12#[serde(transparent)]
13pub struct Valid<T: Document>(T);
14
15impl<T: Document + Sync> Valid<T> {
16 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#[derive(Debug, Serialize)]
58#[serde(untagged)]
59pub enum ValidationError {
60 Single(Reason),
62 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 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}