Skip to main content

vld/object/
lenient.rs

1//! Results of lenient parsing (`validate_fields()` / `parse_lenient()`).
2
3use crate::error::{VldError, format_value_short};
4
5/// Result of validating a single field.
6///
7/// Returned by `validate_fields()` generated by the [`schema!`](crate::schema!) macro.
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct FieldResult {
10    /// Field name.
11    pub name: String,
12    /// Raw input value (before validation).
13    pub input: serde_json::Value,
14    /// `Ok(json_value)` if the field is valid (output serialized to JSON),
15    /// `Err(error)` if validation failed.
16    pub result: Result<serde_json::Value, VldError>,
17}
18
19impl FieldResult {
20    /// Whether this field passed validation.
21    pub fn is_ok(&self) -> bool {
22        self.result.is_ok()
23    }
24
25    /// Whether this field failed validation.
26    pub fn is_err(&self) -> bool {
27        self.result.is_err()
28    }
29}
30
31impl std::fmt::Display for FieldResult {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match &self.result {
34            Ok(v) => write!(f, "✔ {}: {}", self.name, format_value_short(v)),
35            Err(e) => {
36                let received = format_value_short(&self.input);
37                for (i, issue) in e.issues.iter().enumerate() {
38                    if i > 0 {
39                        writeln!(f)?;
40                    }
41                    write!(
42                        f,
43                        "✖ {}: {} (received: {})",
44                        self.name, issue.message, received
45                    )?;
46                }
47                Ok(())
48            }
49        }
50    }
51}
52
53/// Result of lenient parsing: contains the (possibly partial) struct and
54/// per-field diagnostics.
55///
56/// Created by `parse_lenient()` / `parse_lenient_value()` generated by
57/// [`impl_validate_fields!`](crate::impl_validate_fields).
58///
59/// The struct is always constructed — invalid fields fall back to
60/// `Default::default()`. You can inspect which fields passed/failed via
61/// [`fields()`](Self::fields), and save the result to a file at any time
62/// via `save_to_file()`.
63#[derive(Debug)]
64pub struct ParseResult<T> {
65    /// The constructed struct (invalid fields use `Default`).
66    pub value: T,
67    /// Per-field validation diagnostics.
68    field_results: Vec<FieldResult>,
69}
70
71impl<T> ParseResult<T> {
72    /// Create a new parse result.
73    pub fn new(value: T, field_results: Vec<FieldResult>) -> Self {
74        Self {
75            value,
76            field_results,
77        }
78    }
79
80    /// All per-field results.
81    pub fn fields(&self) -> &[FieldResult] {
82        &self.field_results
83    }
84
85    /// Get a specific field's result by name.
86    ///
87    /// Returns `None` if no field with that name exists.
88    pub fn field(&self, name: &str) -> Option<&FieldResult> {
89        self.field_results.iter().find(|f| f.name == name)
90    }
91
92    /// Only the fields that passed validation.
93    pub fn valid_fields(&self) -> Vec<&FieldResult> {
94        self.field_results.iter().filter(|f| f.is_ok()).collect()
95    }
96
97    /// Only the fields that failed validation.
98    pub fn error_fields(&self) -> Vec<&FieldResult> {
99        self.field_results.iter().filter(|f| f.is_err()).collect()
100    }
101
102    /// Whether all fields passed validation.
103    pub fn is_valid(&self) -> bool {
104        self.field_results.iter().all(|f| f.is_ok())
105    }
106
107    /// Whether at least one field failed.
108    pub fn has_errors(&self) -> bool {
109        self.field_results.iter().any(|f| f.is_err())
110    }
111
112    /// Number of valid fields.
113    pub fn valid_count(&self) -> usize {
114        self.field_results.iter().filter(|f| f.is_ok()).count()
115    }
116
117    /// Number of invalid fields.
118    pub fn error_count(&self) -> usize {
119        self.field_results.iter().filter(|f| f.is_err()).count()
120    }
121
122    /// Consume and return the inner struct.
123    pub fn into_value(self) -> T {
124        self.value
125    }
126
127    /// Consume and return both the struct and field results.
128    pub fn into_parts(self) -> (T, Vec<FieldResult>) {
129        (self.value, self.field_results)
130    }
131}
132
133impl<T: serde::Serialize> ParseResult<T> {
134    /// Serialize the struct to a JSON file.
135    pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
136        let json = serde_json::to_string_pretty(&self.value)
137            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
138        if let Some(parent) = path.parent() {
139            if !parent.as_os_str().is_empty() {
140                std::fs::create_dir_all(parent)?;
141            }
142        }
143        std::fs::write(path, json)
144    }
145
146    /// Serialize the struct to a JSON string.
147    pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
148        serde_json::to_string_pretty(&self.value)
149    }
150
151    /// Serialize the struct to a `serde_json::Value`.
152    pub fn to_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
153        serde_json::to_value(&self.value)
154    }
155}
156
157impl<T: std::fmt::Debug> std::fmt::Display for ParseResult<T> {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        writeln!(
160            f,
161            "ParseResult ({} valid, {} errors):",
162            self.valid_count(),
163            self.error_count()
164        )?;
165        for field in &self.field_results {
166            writeln!(f, "  {}", field)?;
167        }
168        Ok(())
169    }
170}