1use serde::{Deserialize, Serialize};
5use std::fmt;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct FieldError {
12 pub field: String,
14 pub message: String,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub struct VldServerError {
21 pub message: String,
23 pub fields: Vec<FieldError>,
25}
26
27impl VldServerError {
28 pub fn validation(fields: Vec<FieldError>) -> Self {
30 let count = fields.len();
31 Self {
32 message: format!(
33 "Validation failed: {} field{} invalid",
34 count,
35 if count == 1 { "" } else { "s" }
36 ),
37 fields,
38 }
39 }
40
41 pub fn internal(msg: impl Into<String>) -> Self {
43 Self {
44 message: msg.into(),
45 fields: Vec::new(),
46 }
47 }
48
49 pub fn field_error(&self, field: &str) -> Option<&str> {
51 self.fields
52 .iter()
53 .find(|f| f.field == field)
54 .map(|f| f.message.as_str())
55 }
56
57 pub fn field_errors(&self, field: &str) -> Vec<&str> {
59 self.fields
60 .iter()
61 .filter(|f| f.field == field)
62 .map(|f| f.message.as_str())
63 .collect()
64 }
65
66 pub fn has_field_error(&self, field: &str) -> bool {
68 self.fields.iter().any(|f| f.field == field)
69 }
70
71 pub fn error_fields(&self) -> Vec<&str> {
73 let mut seen = std::collections::HashSet::new();
74 self.fields
75 .iter()
76 .map(|f| f.field.as_str())
77 .filter(|f| seen.insert(*f))
78 .collect()
79 }
80
81 pub fn from_json(s: &str) -> Option<Self> {
83 serde_json::from_str(s).ok()
84 }
85
86 pub fn from_server_message(s: &str) -> Option<Self> {
101 let (start, end) = (s.find('{')?, s.rfind('}')?);
102 Self::from_json(s.get(start..=end)?)
103 }
104}
105
106impl fmt::Display for VldServerError {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match serde_json::to_string(self) {
109 Ok(json) => write!(f, "{}", json),
110 Err(_) => write!(f, "{}", self.message),
111 }
112 }
113}
114
115impl std::error::Error for VldServerError {}
116
117impl From<vld::error::VldError> for VldServerError {
118 fn from(error: vld::error::VldError) -> Self {
119 Self::validation(field_errors(&error))
120 }
121}
122
123fn field_errors(error: &vld::error::VldError) -> Vec<FieldError> {
124 crate::flatten_issues(error)
125 .into_iter()
126 .map(|(field, message)| FieldError { field, message })
127 .collect()
128}
129
130fn ser_error(e: serde_json::Error) -> String {
131 format!("Serialization error: {}", e)
132}
133
134pub fn validate<S, T>(data: &T) -> Result<(), VldServerError>
136where
137 S: vld::schema::VldParse,
138 T: serde::Serialize,
139{
140 let json = serde_json::to_value(data).map_err(|e| VldServerError::internal(ser_error(e)))?;
141 validate_value::<S>(&json)
142}
143
144pub fn validate_value<S>(json: &serde_json::Value) -> Result<(), VldServerError>
146where
147 S: vld::schema::VldParse,
148{
149 S::vld_parse_value(json).map_err(VldServerError::from)?;
150 Ok(())
151}
152
153pub fn check_field<V, S>(value: &V, schema: &S) -> Option<String>
157where
158 V: serde::Serialize,
159 S: vld::schema::VldSchema,
160{
161 check_field_all(value, schema).into_iter().next()
162}
163
164pub fn check_field_all<V, S>(value: &V, schema: &S) -> Vec<String>
166where
167 V: serde::Serialize,
168 S: vld::schema::VldSchema,
169{
170 let json = match serde_json::to_value(value) {
171 Ok(j) => j,
172 Err(e) => return vec![ser_error(e)],
173 };
174 match schema.parse_value(&json) {
175 Ok(_) => vec![],
176 Err(e) => e.issues.iter().map(|i| i.message.clone()).collect(),
177 }
178}
179
180pub fn check_all_fields<S, T>(data: &T) -> Vec<FieldError>
185where
186 S: vld::schema::VldParse,
187 T: serde::Serialize,
188{
189 let json = match serde_json::to_value(data) {
190 Ok(j) => j,
191 Err(e) => {
192 return vec![FieldError {
193 field: String::new(),
194 message: ser_error(e),
195 }];
196 }
197 };
198 match S::vld_parse_value(&json) {
199 Ok(_) => vec![],
200 Err(e) => field_errors(&e),
201 }
202}
203
204#[doc(hidden)]
205pub fn __arg_errors<V, S>(name: &str, value: &V, schema: &S, out: &mut Vec<FieldError>)
206where
207 V: serde::Serialize,
208 S: vld::schema::VldSchema,
209{
210 let json = match serde_json::to_value(value) {
211 Ok(j) => j,
212 Err(e) => {
213 out.push(FieldError {
214 field: name.to_string(),
215 message: ser_error(e),
216 });
217 return;
218 }
219 };
220 if let Err(e) = schema.parse_value(&json) {
221 for issue in &e.issues {
222 let sub = crate::format_path(&issue.path);
223 let field = if sub.is_empty() {
224 name.to_string()
225 } else if sub.starts_with('[') {
226 format!("{name}{sub}")
227 } else {
228 format!("{name}.{sub}")
229 };
230 out.push(FieldError {
231 field,
232 message: issue.message.clone(),
233 });
234 }
235 }
236}
237
238#[macro_export]
242macro_rules! validate_args {
243 ($($field:ident => $schema:expr),* $(,)?) => {{
244 let mut __vld_field_errors = ::std::vec::Vec::new();
245 $(
246 $crate::form::__arg_errors(stringify!($field), &$field, &($schema), &mut __vld_field_errors);
247 )*
248 if __vld_field_errors.is_empty() {
249 ::std::result::Result::Ok::<(), $crate::form::VldServerError>(())
250 } else {
251 ::std::result::Result::Err($crate::form::VldServerError::validation(__vld_field_errors))
252 }
253 }};
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 vld::schema! {
261 #[derive(Debug)]
262 struct Inner { email: String => vld::string().email() }
263 }
264 vld::schema! {
265 #[derive(Debug)]
266 struct Outer {
267 name: String => vld::string().min(2),
268 user: Inner => vld::nested(Inner::parse_value),
269 }
270 }
271
272 #[test]
273 fn paths_match_validate_args() {
274 let err: VldServerError =
275 Outer::parse_value(&serde_json::json!({"name": "A", "user": {"email": "bad"}}))
276 .unwrap_err()
277 .into();
278 assert!(err.field_error("name").is_some());
279 assert!(err.field_error("user.email").is_some());
280
281 let name = "A".to_string();
282 let err = crate::validate_args! { name => vld::string().min(2) }.unwrap_err();
283 assert!(err.field_error("name").is_some());
284 }
285
286 #[test]
287 fn error_fields_dedup_non_adjacent() {
288 let f = |n: &str| FieldError {
289 field: n.into(),
290 message: "x".into(),
291 };
292 let e = VldServerError::validation(vec![f("a"), f("b"), f("a")]);
293 assert_eq!(e.error_fields(), vec!["a", "b"]);
294 }
295
296 struct Bad;
297 impl serde::Serialize for Bad {
298 fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
299 Err(serde::ser::Error::custom("nope"))
300 }
301 }
302
303 #[test]
304 fn serialization_failure_is_an_error() {
305 assert!(check_field(&Bad, &vld::string()).is_some());
306 assert!(!check_field_all(&Bad, &vld::string()).is_empty());
307 assert!(!check_all_fields::<Inner, _>(&Bad).is_empty());
308 }
309}