1use std::fmt;
2
3#[derive(Debug, Clone)]
5#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
6#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
7pub enum PathSegment {
8 Field(String),
10 Index(usize),
12}
13
14impl fmt::Display for PathSegment {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 PathSegment::Field(name) => write!(f, ".{}", name),
18 PathSegment::Index(idx) => write!(f, "[{}]", idx),
19 }
20 }
21}
22
23#[derive(Debug, Clone)]
25#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
26#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
27pub enum StringValidation {
28 Email,
29 Url,
30 UrlStrict,
31 Uri,
32 Uuid,
33 UuidV1,
34 UuidV4,
35 UuidV7,
36 Ip,
37 Slug,
38 Color,
39 Currency,
40 CountryCode,
41 Locale,
42 Cron,
43 Regex,
44 StartsWith,
45 EndsWith,
46 Ipv4,
47 Ipv6,
48 Cidr,
49 Mac,
50 Hex,
51 CreditCard,
52 Phone,
53 PhoneE164,
54 Semver,
55 SemverFull,
56 Jwt,
57 Ascii,
58 Alpha,
59 Alphanumeric,
60 Lowercase,
61 Uppercase,
62 Base64,
63 IsoDate,
64 IsoDatetime,
65 IsoTime,
66 Hostname,
67 Cuid2,
68 Ulid,
69 Nanoid,
70 Emoji,
71}
72
73#[derive(Debug, Clone)]
75#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
76#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
77pub enum IssueCode {
78 InvalidType { expected: String, received: String },
79 TooSmall { minimum: f64, inclusive: bool },
80 TooBig { maximum: f64, inclusive: bool },
81 InvalidString { validation: StringValidation },
82 NotInt,
83 NotFinite,
84 MissingField,
85 UnrecognizedField,
86 IoError,
87 ParseError,
88 Custom { code: String },
89}
90
91#[derive(Debug, Clone)]
93#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
94#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
95pub struct ValidationIssue {
96 pub code: IssueCode,
97 pub message: String,
98 pub path: Vec<PathSegment>,
99 pub received: Option<serde_json::Value>,
101}
102
103#[derive(Debug, Clone)]
107#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
108#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
109pub struct VldError {
110 pub issues: Vec<ValidationIssue>,
111}
112
113impl VldError {
114 pub fn new() -> Self {
116 Self { issues: vec![] }
117 }
118
119 pub fn single(code: IssueCode, message: impl Into<String>) -> Self {
121 Self {
122 issues: vec![ValidationIssue {
123 code,
124 message: message.into(),
125 path: vec![],
126 received: None,
127 }],
128 }
129 }
130
131 pub fn single_with_value(
133 code: IssueCode,
134 message: impl Into<String>,
135 received: &serde_json::Value,
136 ) -> Self {
137 Self {
138 issues: vec![ValidationIssue {
139 code,
140 message: message.into(),
141 path: vec![],
142 received: Some(truncate_value(received)),
143 }],
144 }
145 }
146
147 pub fn with_prefix(mut self, segment: PathSegment) -> Self {
149 for issue in &mut self.issues {
150 issue.path.insert(0, segment.clone());
151 }
152 self
153 }
154
155 pub fn merge(mut self, other: VldError) -> Self {
157 self.issues.extend(other.issues);
158 self
159 }
160
161 pub fn is_empty(&self) -> bool {
163 self.issues.is_empty()
164 }
165
166 pub fn push(&mut self, code: IssueCode, message: impl Into<String>) {
168 self.issues.push(ValidationIssue {
169 code,
170 message: message.into(),
171 path: vec![],
172 received: None,
173 });
174 }
175
176 pub fn push_with_value(
178 &mut self,
179 code: IssueCode,
180 message: impl Into<String>,
181 received: &serde_json::Value,
182 ) {
183 self.issues.push(ValidationIssue {
184 code,
185 message: message.into(),
186 path: vec![],
187 received: Some(truncate_value(received)),
188 });
189 }
190}
191
192pub struct IssueBuilder<'a> {
212 errors: &'a mut VldError,
213 code: IssueCode,
214 message: Option<String>,
215 path: Vec<PathSegment>,
216 received: Option<serde_json::Value>,
217}
218
219impl<'a> IssueBuilder<'a> {
220 pub fn message(mut self, msg: impl Into<String>) -> Self {
222 self.message = Some(msg.into());
223 self
224 }
225
226 pub fn path_field(mut self, name: impl Into<String>) -> Self {
228 self.path.push(PathSegment::Field(name.into()));
229 self
230 }
231
232 pub fn path_index(mut self, idx: usize) -> Self {
234 self.path.push(PathSegment::Index(idx));
235 self
236 }
237
238 pub fn received(mut self, value: &serde_json::Value) -> Self {
240 self.received = Some(truncate_value(value));
241 self
242 }
243
244 pub fn finish(self) {
246 let msg = self
247 .message
248 .unwrap_or_else(|| format!("Validation error: {}", self.code.key()));
249 self.errors.issues.push(ValidationIssue {
250 code: self.code,
251 message: msg,
252 path: self.path,
253 received: self.received,
254 });
255 }
256}
257
258impl VldError {
259 pub fn issue(&mut self, code: IssueCode) -> IssueBuilder<'_> {
276 IssueBuilder {
277 errors: self,
278 code,
279 message: None,
280 path: vec![],
281 received: None,
282 }
283 }
284}
285
286impl Default for VldError {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl fmt::Display for VldError {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 for (i, issue) in self.issues.iter().enumerate() {
295 if i > 0 {
296 writeln!(f)?;
297 }
298 if !issue.path.is_empty() {
299 let path_str: String = issue.path.iter().map(|p| p.to_string()).collect();
300 write!(f, "{}: ", path_str)?;
301 }
302 write!(f, "{}", issue.message)?;
303 if let Some(val) = &issue.received {
304 write!(f, ", received {}", format_value_short(val))?;
305 }
306 }
307 Ok(())
308 }
309}
310
311impl std::error::Error for VldError {}
312
313impl IssueCode {
314 pub fn key(&self) -> &str {
316 match self {
317 IssueCode::InvalidType { .. } => "invalid_type",
318 IssueCode::TooSmall { .. } => "too_small",
319 IssueCode::TooBig { .. } => "too_big",
320 IssueCode::InvalidString { .. } => "invalid_string",
321 IssueCode::NotInt => "not_int",
322 IssueCode::NotFinite => "not_finite",
323 IssueCode::MissingField => "missing_field",
324 IssueCode::UnrecognizedField => "unrecognized_field",
325 IssueCode::IoError => "io_error",
326 IssueCode::ParseError => "parse_error",
327 IssueCode::Custom { code } => code,
328 }
329 }
330
331 pub fn params(&self) -> Vec<(&str, String)> {
335 match self {
336 IssueCode::InvalidType { expected, received } => {
337 vec![
338 ("expected", expected.clone()),
339 ("received", received.clone()),
340 ]
341 }
342 IssueCode::TooSmall { minimum, inclusive } => {
343 vec![
344 ("minimum", minimum.to_string()),
345 ("inclusive", inclusive.to_string()),
346 ]
347 }
348 IssueCode::TooBig { maximum, inclusive } => {
349 vec![
350 ("maximum", maximum.to_string()),
351 ("inclusive", inclusive.to_string()),
352 ]
353 }
354 IssueCode::InvalidString { validation } => {
355 vec![("validation", format!("{:?}", validation))]
356 }
357 _ => vec![],
358 }
359 }
360}
361
362#[doc(hidden)]
364pub fn value_type_name(value: &serde_json::Value) -> String {
365 match value {
366 serde_json::Value::Null => "null",
367 serde_json::Value::Bool(_) => "boolean",
368 serde_json::Value::Number(_) => "number",
369 serde_json::Value::String(_) => "string",
370 serde_json::Value::Array(_) => "array",
371 serde_json::Value::Object(_) => "object",
372 }
373 .to_string()
374}
375
376pub fn format_value_short(value: &serde_json::Value) -> String {
378 match value {
379 serde_json::Value::Null => "null".to_string(),
380 serde_json::Value::Bool(b) => b.to_string(),
381 serde_json::Value::Number(n) => n.to_string(),
382 serde_json::Value::String(s) => {
383 let char_count = s.chars().count();
384 if char_count > 50 {
385 let prefix: String = s.chars().take(47).collect();
386 format!("\"{}...\"", prefix)
387 } else {
388 format!("\"{}\"", s)
389 }
390 }
391 serde_json::Value::Array(arr) => format!("Array(len={})", arr.len()),
392 serde_json::Value::Object(obj) => format!("Object(keys={})", obj.len()),
393 }
394}
395
396#[derive(Debug, Clone)]
400#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
401#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
402pub struct FieldResult {
403 pub name: String,
405 pub input: serde_json::Value,
407 pub result: Result<serde_json::Value, VldError>,
410}
411
412impl FieldResult {
413 pub fn is_ok(&self) -> bool {
415 self.result.is_ok()
416 }
417
418 pub fn is_err(&self) -> bool {
420 self.result.is_err()
421 }
422}
423
424impl std::fmt::Display for FieldResult {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 match &self.result {
427 Ok(v) => write!(f, "✔ {}: {}", self.name, format_value_short(v)),
428 Err(e) => {
429 let received = format_value_short(&self.input);
430 for (i, issue) in e.issues.iter().enumerate() {
431 if i > 0 {
432 writeln!(f)?;
433 }
434 write!(
435 f,
436 "✖ {}: {} (received: {})",
437 self.name, issue.message, received
438 )?;
439 }
440 Ok(())
441 }
442 }
443 }
444}
445
446#[derive(Debug)]
457pub struct ParseResult<T> {
458 pub value: T,
460 field_results: Vec<FieldResult>,
462}
463
464impl<T> ParseResult<T> {
465 pub fn new(value: T, field_results: Vec<FieldResult>) -> Self {
467 Self {
468 value,
469 field_results,
470 }
471 }
472
473 pub fn fields(&self) -> &[FieldResult] {
475 &self.field_results
476 }
477
478 pub fn field(&self, name: &str) -> Option<&FieldResult> {
482 self.field_results.iter().find(|f| f.name == name)
483 }
484
485 pub fn valid_fields(&self) -> Vec<&FieldResult> {
487 self.field_results.iter().filter(|f| f.is_ok()).collect()
488 }
489
490 pub fn error_fields(&self) -> Vec<&FieldResult> {
492 self.field_results.iter().filter(|f| f.is_err()).collect()
493 }
494
495 pub fn is_valid(&self) -> bool {
497 self.field_results.iter().all(|f| f.is_ok())
498 }
499
500 pub fn has_errors(&self) -> bool {
502 self.field_results.iter().any(|f| f.is_err())
503 }
504
505 pub fn valid_count(&self) -> usize {
507 self.field_results.iter().filter(|f| f.is_ok()).count()
508 }
509
510 pub fn error_count(&self) -> usize {
512 self.field_results.iter().filter(|f| f.is_err()).count()
513 }
514
515 pub fn into_value(self) -> T {
517 self.value
518 }
519
520 pub fn into_parts(self) -> (T, Vec<FieldResult>) {
522 (self.value, self.field_results)
523 }
524}
525
526#[cfg(feature = "serialize")]
527impl<T: serde::Serialize> ParseResult<T> {
528 #[cfg(feature = "std")]
532 pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
533 let json = serde_json::to_string_pretty(&self.value)
534 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
535 if let Some(parent) = path.parent() {
536 if !parent.as_os_str().is_empty() {
537 std::fs::create_dir_all(parent)?;
538 }
539 }
540 std::fs::write(path, json)
541 }
542
543 pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
547 serde_json::to_string_pretty(&self.value)
548 }
549
550 pub fn to_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
554 serde_json::to_value(&self.value)
555 }
556}
557
558impl<T: std::fmt::Debug> std::fmt::Display for ParseResult<T> {
559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560 writeln!(
561 f,
562 "ParseResult ({} valid, {} errors):",
563 self.valid_count(),
564 self.error_count()
565 )?;
566 for field in &self.field_results {
567 writeln!(f, " {}", field)?;
568 }
569 Ok(())
570 }
571}
572
573fn truncate_value(value: &serde_json::Value) -> serde_json::Value {
575 match value {
576 serde_json::Value::String(s) if s.len() > 100 => {
577 serde_json::Value::String(format!("{}...", &s[..97]))
578 }
579 serde_json::Value::Array(arr) if arr.len() > 5 => {
580 let mut truncated: Vec<serde_json::Value> = arr[..5].to_vec();
581 truncated.push(serde_json::Value::String(format!(
582 "... ({} more)",
583 arr.len() - 5
584 )));
585 serde_json::Value::Array(truncated)
586 }
587 _ => value.clone(),
588 }
589}