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