renox_core/validation/
value.rs1use crate::db::{DbValue, ToDbValue};
2
3#[derive(Debug, Clone, PartialEq)]
5#[non_exhaustive]
6pub enum Inspected {
7 Missing,
9 Text(String),
11 Number(f64),
13 Bool(bool),
15 Items(usize),
17 #[non_exhaustive]
21 File {
22 kilobytes: f64,
24 extension: String,
26 image: bool,
28 dimensions: Option<(u32, u32)>,
30 },
31 Date(chrono::NaiveDateTime),
33}
34
35pub trait FieldValue {
38 fn inspect(&self) -> Inspected;
40 fn db_value(&self) -> DbValue;
42}
43
44impl FieldValue for str {
45 fn inspect(&self) -> Inspected {
46 if self.trim().is_empty() {
47 Inspected::Missing
48 } else {
49 Inspected::Text(self.to_owned())
50 }
51 }
52
53 fn db_value(&self) -> DbValue {
54 self.to_db_value()
55 }
56}
57
58impl FieldValue for String {
59 fn inspect(&self) -> Inspected {
60 self.as_str().inspect()
61 }
62
63 fn db_value(&self) -> DbValue {
64 self.to_db_value()
65 }
66}
67
68macro_rules! number {
69 ($($t:ty),*) => {$(
70 impl FieldValue for $t {
71 fn inspect(&self) -> Inspected {
72 Inspected::Number(*self as f64)
73 }
74
75 fn db_value(&self) -> DbValue {
76 self.to_db_value()
77 }
78 }
79 )*};
80}
81
82number!(i8, i16, i32, i64, u8, u16, u32, f32, f64);
83
84impl FieldValue for bool {
85 fn inspect(&self) -> Inspected {
86 Inspected::Bool(*self)
87 }
88
89 fn db_value(&self) -> DbValue {
90 self.to_db_value()
91 }
92}
93
94impl FieldValue for chrono::NaiveDate {
95 fn inspect(&self) -> Inspected {
96 Inspected::Date(self.and_time(chrono::NaiveTime::MIN))
97 }
98
99 fn db_value(&self) -> DbValue {
100 self.to_db_value()
101 }
102}
103
104impl FieldValue for chrono::NaiveDateTime {
105 fn inspect(&self) -> Inspected {
106 Inspected::Date(*self)
107 }
108
109 fn db_value(&self) -> DbValue {
110 self.to_db_value()
111 }
112}
113
114impl FieldValue for chrono::DateTime<chrono::Utc> {
115 fn inspect(&self) -> Inspected {
116 Inspected::Date(self.naive_utc())
117 }
118
119 fn db_value(&self) -> DbValue {
120 self.to_db_value()
121 }
122}
123
124impl<T: FieldValue> FieldValue for Option<T> {
125 fn inspect(&self) -> Inspected {
126 self.as_ref()
127 .map_or(Inspected::Missing, FieldValue::inspect)
128 }
129
130 fn db_value(&self) -> DbValue {
131 self.as_ref().map_or(DbValue::Null, FieldValue::db_value)
132 }
133}
134
135impl<T> FieldValue for Vec<T> {
136 fn inspect(&self) -> Inspected {
137 if self.is_empty() {
138 Inspected::Missing
139 } else {
140 Inspected::Items(self.len())
141 }
142 }
143
144 fn db_value(&self) -> DbValue {
145 DbValue::Null
146 }
147}
148
149impl<T: FieldValue + ?Sized> FieldValue for &T {
150 fn inspect(&self) -> Inspected {
151 (**self).inspect()
152 }
153
154 fn db_value(&self) -> DbValue {
155 (**self).db_value()
156 }
157}
158
159impl FieldValue for crate::upload::Upload {
160 fn inspect(&self) -> Inspected {
161 if self.bytes().is_empty() {
164 return Inspected::File {
165 kilobytes: 0.0,
166 extension: String::new(),
167 image: false,
168 dimensions: None,
169 };
170 }
171 let extension = match self.sniffed_type() {
172 Some("image/png") => "png".to_owned(),
173 Some("image/jpeg") => "jpg".to_owned(),
174 Some("image/gif") => "gif".to_owned(),
175 Some("image/webp") => "webp".to_owned(),
176 Some("application/pdf") => "pdf".to_owned(),
177 _ => self.extension().unwrap_or_default(),
178 };
179 Inspected::File {
180 kilobytes: self.size() as f64 / 1024.0,
181 extension,
182 image: self.is_image(),
183 dimensions: self.dimensions(),
184 }
185 }
186
187 fn db_value(&self) -> DbValue {
188 DbValue::Null
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::upload::Upload;
196 use crate::validation::Validator;
197
198 #[test]
199 fn a_text_file_named_png_is_not_an_image() {
200 let fake = Some(Upload::new(
201 "fake.png",
202 "image/png",
203 axum::body::Bytes::from_static(b"not an image\n"),
204 ));
205 assert!(matches!(
206 FieldValue::inspect(&fake),
207 Inspected::File { image: false, .. }
208 ));
209 let mut v = Validator::new();
210 v.field("photo", &fake).label("picture").image().max(2048);
211 assert_eq!(
212 v.errors.first("photo"),
213 Some("The picture must be an image.")
214 );
215 }
216}