1use std::fmt::Display;
4
5use rskit_errors::{AppError, AppResult, ErrorCode};
6
7use crate::{FieldError, validate_email, validate_url, validate_uuid};
8
9#[derive(Debug, Default)]
11pub struct Validator {
12 errors: Vec<FieldError>,
13}
14
15impl Validator {
16 #[must_use]
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 #[must_use]
24 pub fn required(mut self, field: &str, value: &str) -> Self {
25 if value.trim().is_empty() {
26 self.add(field, "is required");
27 }
28 self
29 }
30
31 #[must_use]
33 pub fn min_length(mut self, field: &str, value: &str, min: usize) -> Self {
34 if value.chars().count() < min {
35 self.add(field, format!("must be at least {min} characters"));
36 }
37 self
38 }
39
40 #[must_use]
42 pub fn max_length(mut self, field: &str, value: &str, max: usize) -> Self {
43 if value.chars().count() > max {
44 self.add(field, format!("must be at most {max} characters"));
45 }
46 self
47 }
48
49 #[must_use]
51 pub fn email(mut self, field: &str, value: &str) -> Self {
52 if !validate_email(value) {
53 self.add(field, "must be a valid email address");
54 }
55 self
56 }
57
58 #[must_use]
60 pub fn url(mut self, field: &str, value: &str) -> Self {
61 if !validate_url(value) {
62 self.add(field, "must be a valid URL");
63 }
64 self
65 }
66
67 #[must_use]
69 pub fn pattern(mut self, field: &str, value: &str, re: &str) -> Self {
70 match regex::Regex::new(re) {
71 Ok(regex) => {
72 if !regex.is_match(value) {
73 self.add(field, format!("must match pattern {re}"));
74 }
75 }
76 Err(err) => self.add(field, format!("invalid pattern: {err}")),
77 }
78 self
79 }
80
81 #[must_use]
83 pub fn required_uuid(mut self, field: &str, value: &str) -> Self {
84 if !validate_uuid(value) {
85 self.add(field, "must be a valid UUID");
86 }
87 self
88 }
89
90 #[must_use]
92 pub fn optional_uuid(mut self, field: &str, value: Option<&str>) -> Self {
93 if let Some(v) = value
94 && !validate_uuid(v)
95 {
96 self.add(field, "must be a valid UUID");
97 }
98 self
99 }
100
101 #[must_use]
103 pub fn in_range<T: PartialOrd + Display>(
104 mut self,
105 field: &str,
106 value: T,
107 min: T,
108 max: T,
109 ) -> Self {
110 if value < min || value > max {
111 self.add(field, format!("must be between {min} and {max}"));
112 }
113 self
114 }
115
116 #[must_use]
118 pub fn min_value<T: PartialOrd + Display>(mut self, field: &str, value: T, min: T) -> Self {
119 if value < min {
120 self.add(field, format!("must be at least {min}"));
121 }
122 self
123 }
124
125 #[must_use]
127 pub fn max_value<T: PartialOrd + Display>(mut self, field: &str, value: T, max: T) -> Self {
128 if value > max {
129 self.add(field, format!("must be {max} or less"));
130 }
131 self
132 }
133
134 #[must_use]
136 pub fn before(mut self, field: &str, value: &str, deadline: &str) -> Self {
137 match (
138 chrono::DateTime::parse_from_rfc3339(value),
139 chrono::DateTime::parse_from_rfc3339(deadline),
140 ) {
141 (Ok(v), Ok(d)) if v >= d => {
142 self.add(field, format!("must be before {deadline}"));
143 }
144 (Err(_), _) => self.add(field, "must be a valid datetime"),
145 _ => {}
146 }
147 self
148 }
149
150 #[must_use]
152 pub fn after(mut self, field: &str, value: &str, floor: &str) -> Self {
153 match (
154 chrono::DateTime::parse_from_rfc3339(value),
155 chrono::DateTime::parse_from_rfc3339(floor),
156 ) {
157 (Ok(v), Ok(f)) if v <= f => {
158 self.add(field, format!("must be after {floor}"));
159 }
160 (Err(_), _) => self.add(field, "must be a valid datetime"),
161 _ => {}
162 }
163 self
164 }
165
166 #[must_use]
168 pub fn one_of<T: PartialEq + Display>(mut self, field: &str, value: &T, allowed: &[T]) -> Self {
169 if !allowed.iter().any(|a| a == value) {
170 let list = allowed
171 .iter()
172 .map(|a| a.to_string())
173 .collect::<Vec<_>>()
174 .join(", ");
175 self.add(field, format!("must be one of: {list}"));
176 }
177 self
178 }
179
180 #[must_use]
182 pub fn custom(mut self, condition: bool, field: &str, message: &str) -> Self {
183 if !condition {
184 self.add(field, message);
185 }
186 self
187 }
188
189 #[must_use]
191 pub fn has_errors(&self) -> bool {
192 !self.errors.is_empty()
193 }
194
195 #[must_use]
197 pub fn errors(&self) -> &[FieldError] {
198 &self.errors
199 }
200
201 pub fn validate(self) -> AppResult<()> {
204 if self.errors.is_empty() {
205 return Ok(());
206 }
207 let detail = self
208 .errors
209 .iter()
210 .map(|e| format!("{}: {}", e.field, e.message))
211 .collect::<Vec<_>>()
212 .join("; ");
213 let fields_json =
214 serde_json::to_value(&self.errors).unwrap_or_else(|_| serde_json::Value::Array(vec![]));
215 Err(AppError::new(ErrorCode::InvalidInput, detail).with_detail("fields", fields_json))
216 }
217
218 fn add(&mut self, field: &str, message: impl Into<String>) {
219 self.errors.push(FieldError {
220 field: field.to_owned(),
221 message: message.into(),
222 });
223 }
224}