Skip to main content

modo/validate/
error.rs

1use std::collections::HashMap;
2use std::fmt;
3
4/// A collection of per-field validation errors.
5///
6/// Produced by [`super::Validator::check`] when one or more fields fail
7/// validation. Each field maps to a list of human-readable error messages.
8///
9/// Converts automatically into [`crate::Error`] (HTTP 422 Unprocessable Entity)
10/// via the `From` impl, with the field map serialized into the response
11/// `details` field.
12pub struct ValidationError {
13    fields: HashMap<String, Vec<String>>,
14}
15
16impl ValidationError {
17    /// Create a new `ValidationError` from a pre-built field-error map.
18    pub fn new(fields: HashMap<String, Vec<String>>) -> Self {
19        Self { fields }
20    }
21
22    /// Returns `true` if no field errors have been recorded.
23    pub fn is_empty(&self) -> bool {
24        self.fields.is_empty()
25    }
26
27    /// Returns the error messages for a single field, or an empty slice if
28    /// the field had no errors.
29    pub fn field_errors(&self, field: &str) -> &[String] {
30        self.fields.get(field).map(|v| v.as_slice()).unwrap_or(&[])
31    }
32
33    /// Returns the full map of field names to their error message lists.
34    pub fn fields(&self) -> &HashMap<String, Vec<String>> {
35        &self.fields
36    }
37}
38
39impl fmt::Display for ValidationError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(
42            f,
43            "validation failed: {} field(s) invalid",
44            self.fields.len()
45        )
46    }
47}
48
49impl fmt::Debug for ValidationError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("ValidationError")
52            .field("fields", &self.fields)
53            .finish()
54    }
55}
56
57impl std::error::Error for ValidationError {}
58
59impl From<ValidationError> for crate::error::Error {
60    fn from(ve: ValidationError) -> Self {
61        crate::error::Error::unprocessable_entity("validation failed")
62            .with_details(serde_json::json!(ve.fields))
63    }
64}