Skip to main content

rydantic/
error.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::{collections::BTreeMap, fmt};
4
5/// A location in a nested input. Object keys and array indices remain distinct.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum PathSegment {
9    Field(String),
10    Index(usize),
11}
12
13impl From<&str> for PathSegment {
14    fn from(value: &str) -> Self {
15        Self::Field(value.to_owned())
16    }
17}
18impl From<String> for PathSegment {
19    fn from(value: String) -> Self {
20        Self::Field(value)
21    }
22}
23impl From<usize> for PathSegment {
24    fn from(value: usize) -> Self {
25        Self::Index(value)
26    }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum Stage {
32    Parse,
33    Validate,
34    Serialize,
35}
36
37/// Machine-readable validation failure. Raw input is deliberately not retained.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct ValidationError {
40    pub loc: Vec<PathSegment>,
41    pub code: String,
42    pub message: String,
43    pub stage: Stage,
44    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
45    pub params: BTreeMap<String, Value>,
46}
47
48impl ValidationError {
49    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
50        Self {
51            loc: vec![],
52            code: code.into(),
53            message: message.into(),
54            stage: Stage::Validate,
55            params: BTreeMap::new(),
56        }
57    }
58    pub fn custom(message: impl Into<String>) -> Self {
59        Self::new("custom", message)
60    }
61    /// Add a relative location (outermost segment first).
62    pub fn at(mut self, segment: impl Into<PathSegment>) -> Self {
63        self.loc.push(segment.into());
64        self
65    }
66    pub fn with_param(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
67        self.params.insert(key.into(), value.into());
68        self
69    }
70    pub fn with_stage(mut self, stage: Stage) -> Self {
71        self.stage = stage;
72        self
73    }
74    pub fn pointer(&self) -> String {
75        self.loc
76            .iter()
77            .map(|part| match part {
78                PathSegment::Field(key) => {
79                    format!("/{}", key.replace('~', "~0").replace('/', "~1"))
80                }
81                PathSegment::Index(index) => format!("/{index}"),
82            })
83            .collect()
84    }
85}
86
87impl fmt::Display for ValidationError {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "${}: {} [{}]", self.pointer(), self.message, self.code)
90    }
91}
92impl std::error::Error for ValidationError {}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(transparent)]
96pub struct ValidationErrors(pub Vec<ValidationError>);
97
98impl ValidationErrors {
99    pub fn new(errors: Vec<ValidationError>) -> Self {
100        Self(errors)
101    }
102    pub fn errors(&self) -> &[ValidationError] {
103        &self.0
104    }
105    pub fn len(&self) -> usize {
106        self.0.len()
107    }
108    pub fn is_empty(&self) -> bool {
109        self.0.is_empty()
110    }
111    pub fn to_json(&self) -> Value {
112        serde_json::to_value(self).expect("error fields are JSON-compatible")
113    }
114}
115impl From<ValidationError> for ValidationErrors {
116    fn from(error: ValidationError) -> Self {
117        Self(vec![error])
118    }
119}
120impl fmt::Display for ValidationErrors {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        writeln!(f, "{} validation error(s)", self.len())?;
123        for (index, error) in self.0.iter().enumerate() {
124            if index > 0 {
125                writeln!(f)?;
126            }
127            write!(f, "{error}")?;
128        }
129        Ok(())
130    }
131}
132impl std::error::Error for ValidationErrors {}
133
134/// Shared error collector used by generated code and custom `JsonType` implementations.
135#[derive(Debug, Default)]
136pub struct Context {
137    path: Vec<PathSegment>,
138    errors: Vec<ValidationError>,
139}
140impl Context {
141    pub fn new() -> Self {
142        Self::default()
143    }
144    pub fn error_count(&self) -> usize {
145        self.errors.len()
146    }
147    pub fn push(&mut self, mut error: ValidationError) {
148        let mut path = self.path.clone();
149        path.append(&mut error.loc);
150        error.loc = path;
151        self.errors.push(error);
152    }
153    pub fn extend(&mut self, errors: ValidationErrors) {
154        for error in errors.0 {
155            self.push(error);
156        }
157    }
158    pub fn at<T>(&mut self, segment: impl Into<PathSegment>, f: impl FnOnce(&mut Self) -> T) -> T {
159        self.path.push(segment.into());
160        let result = f(self);
161        self.path.pop();
162        result
163    }
164    pub fn child(&self, segment: Option<PathSegment>) -> Self {
165        let mut path = self.path.clone();
166        if let Some(segment) = segment {
167            path.push(segment);
168        }
169        Self {
170            path,
171            errors: vec![],
172        }
173    }
174    pub fn absorb(&mut self, child: Self) {
175        self.errors.extend(child.errors);
176    }
177    pub fn prepend_errors(
178        errors: ValidationErrors,
179        segment: impl Into<PathSegment>,
180    ) -> ValidationErrors {
181        let segment = segment.into();
182        ValidationErrors(
183            errors
184                .0
185                .into_iter()
186                .map(|mut error| {
187                    error.loc.insert(0, segment.clone());
188                    error
189                })
190                .collect(),
191        )
192    }
193    pub fn finish<T>(self, value: Option<T>) -> Result<T, ValidationErrors> {
194        if !self.errors.is_empty() {
195            return Err(ValidationErrors(self.errors));
196        }
197        value.ok_or_else(|| {
198            ValidationError::new("internal", "decoder returned no value without an error").into()
199        })
200    }
201    pub fn result(self) -> Result<(), ValidationErrors> {
202        self.finish(Some(()))
203    }
204}