Skip to content

Commit dbe128a

Browse files
authored
unique error kinds (#285)
* unque error kinds * simplifying errors with Number type * add list_all_errors function
1 parent 29ae322 commit dbe128a

30 files changed

+347
-301
lines changed

pydantic_core/_pydantic_core.pyi

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ErrorDetails(TypedDict):
4343
loc: 'list[int | str]'
4444
message: str
4545
input_value: Any
46-
context: NotRequired['dict[str, Any]']
46+
context: NotRequired['dict[str, str | int | float]']
4747

4848
class ValidationError(ValueError):
4949
title: str
@@ -69,3 +69,14 @@ class PydanticErrorKind(ValueError):
6969

7070
class PydanticOmit(Exception):
7171
def __init__(self) -> None: ...
72+
73+
class ErrorKind(TypedDict):
74+
kind: str
75+
message_template: str
76+
example_message: str
77+
example_context: 'dict[str, str | int | float] | None'
78+
79+
def list_all_errors() -> 'list[ErrorKind]':
80+
"""
81+
Get information about all built-in errors.
82+
"""

src/errors/kinds.rs

Lines changed: 169 additions & 165 deletions
Large diffs are not rendered by default.

src/errors/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod location;
66
mod validation_exception;
77
mod value_exception;
88

9-
pub use self::kinds::ErrorKind;
9+
pub use self::kinds::{list_all_errors, ErrorKind};
1010
pub use self::line_error::{pretty_line_errors, InputValue, ValError, ValLineError, ValResult};
1111
pub use self::location::LocItem;
1212
pub use self::validation_exception::ValidationError;

src/input/datetime.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use pyo3::intern;
22
use pyo3::prelude::*;
33
use pyo3::types::{PyDate, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTzInfo};
44
use speedate::{Date, DateTime, Duration, Time};
5+
use std::borrow::Cow;
56
use strum::EnumMessage;
67

78
use crate::errors::{ErrorKind, ValError, ValResult};
@@ -257,7 +258,7 @@ pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'
257258
Ok(date) => Ok(date.into()),
258259
Err(err) => Err(ValError::new(
259260
ErrorKind::DateParsing {
260-
error: err.get_documentation().unwrap_or_default().to_string(),
261+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
261262
},
262263
input,
263264
)),
@@ -269,7 +270,7 @@ pub fn bytes_as_time<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'
269270
Ok(date) => Ok(date.into()),
270271
Err(err) => Err(ValError::new(
271272
ErrorKind::TimeParsing {
272-
error: err.get_documentation().unwrap_or_default().to_string(),
273+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
273274
},
274275
input,
275276
)),
@@ -280,8 +281,8 @@ pub fn bytes_as_datetime<'a, 'b>(input: &'a impl Input<'a>, bytes: &'b [u8]) ->
280281
match DateTime::parse_bytes(bytes) {
281282
Ok(dt) => Ok(dt.into()),
282283
Err(err) => Err(ValError::new(
283-
ErrorKind::DateTimeParsing {
284-
error: err.get_documentation().unwrap_or_default().to_string(),
284+
ErrorKind::DatetimeParsing {
285+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
285286
},
286287
input,
287288
)),
@@ -296,8 +297,8 @@ pub fn int_as_datetime<'a>(
296297
match DateTime::from_timestamp(timestamp, timestamp_microseconds) {
297298
Ok(dt) => Ok(dt.into()),
298299
Err(err) => Err(ValError::new(
299-
ErrorKind::DateTimeParsing {
300-
error: err.get_documentation().unwrap_or_default().to_string(),
300+
ErrorKind::DatetimeParsing {
301+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
301302
},
302303
input,
303304
)),
@@ -338,7 +339,7 @@ pub fn int_as_time<'a>(
338339
t if t < 0_i64 => {
339340
return Err(ValError::new(
340341
ErrorKind::TimeParsing {
341-
error: "time in seconds should be positive".to_string(),
342+
error: Cow::Borrowed("time in seconds should be positive"),
342343
},
343344
input,
344345
));
@@ -352,7 +353,7 @@ pub fn int_as_time<'a>(
352353
Ok(dt) => Ok(dt.into()),
353354
Err(err) => Err(ValError::new(
354355
ErrorKind::TimeParsing {
355-
error: err.get_documentation().unwrap_or_default().to_string(),
356+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
356357
},
357358
input,
358359
)),
@@ -370,7 +371,7 @@ pub fn bytes_as_timedelta<'a, 'b>(input: &'a impl Input<'a>, bytes: &'b [u8]) ->
370371
Ok(dt) => Ok(dt.into()),
371372
Err(err) => Err(ValError::new(
372373
ErrorKind::TimeDeltaParsing {
373-
error: err.get_documentation().unwrap_or_default().to_string(),
374+
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
374375
},
375376
input,
376377
)),

src/input/input_json.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ impl<'a> Input<'a> for JsonInput {
7878
fn strict_str(&'a self) -> ValResult<EitherString<'a>> {
7979
match self {
8080
JsonInput::String(s) => Ok(s.as_str().into()),
81-
_ => Err(ValError::new(ErrorKind::StrType, self)),
81+
_ => Err(ValError::new(ErrorKind::StringType, self)),
8282
}
8383
}
8484
fn lax_str(&'a self) -> ValResult<EitherString<'a>> {
8585
match self {
8686
JsonInput::String(s) => Ok(s.as_str().into()),
87-
_ => Err(ValError::new(ErrorKind::StrType, self)),
87+
_ => Err(ValError::new(ErrorKind::StringType, self)),
8888
}
8989
}
9090

@@ -262,15 +262,15 @@ impl<'a> Input<'a> for JsonInput {
262262
fn strict_datetime(&self) -> ValResult<EitherDateTime> {
263263
match self {
264264
JsonInput::String(v) => bytes_as_datetime(self, v.as_bytes()),
265-
_ => Err(ValError::new(ErrorKind::DateTimeType, self)),
265+
_ => Err(ValError::new(ErrorKind::DatetimeType, self)),
266266
}
267267
}
268268
fn lax_datetime(&self) -> ValResult<EitherDateTime> {
269269
match self {
270270
JsonInput::String(v) => bytes_as_datetime(self, v.as_bytes()),
271271
JsonInput::Int(v) => int_as_datetime(self, *v, 0),
272272
JsonInput::Float(v) => float_as_datetime(self, *v),
273-
_ => Err(ValError::new(ErrorKind::DateTimeType, self)),
273+
_ => Err(ValError::new(ErrorKind::DatetimeType, self)),
274274
}
275275
}
276276

src/input/input_python.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl<'a> Input<'a> for PyAny {
127127
if let Ok(py_str) = self.cast_as::<PyString>() {
128128
Ok(py_str.into())
129129
} else {
130-
Err(ValError::new(ErrorKind::StrType, self))
130+
Err(ValError::new(ErrorKind::StringType, self))
131131
}
132132
}
133133

@@ -137,17 +137,17 @@ impl<'a> Input<'a> for PyAny {
137137
} else if let Ok(bytes) = self.cast_as::<PyBytes>() {
138138
let str = match from_utf8(bytes.as_bytes()) {
139139
Ok(s) => s,
140-
Err(_) => return Err(ValError::new(ErrorKind::StrUnicode, self)),
140+
Err(_) => return Err(ValError::new(ErrorKind::StringUnicode, self)),
141141
};
142142
Ok(str.into())
143143
} else if let Ok(py_byte_array) = self.cast_as::<PyByteArray>() {
144144
let str = match from_utf8(unsafe { py_byte_array.as_bytes() }) {
145145
Ok(s) => s,
146-
Err(_) => return Err(ValError::new(ErrorKind::StrUnicode, self)),
146+
Err(_) => return Err(ValError::new(ErrorKind::StringUnicode, self)),
147147
};
148148
Ok(str.into())
149149
} else {
150-
Err(ValError::new(ErrorKind::StrType, self))
150+
Err(ValError::new(ErrorKind::StringType, self))
151151
}
152152
}
153153

@@ -517,7 +517,7 @@ impl<'a> Input<'a> for PyAny {
517517
if let Ok(dt) = self.cast_as::<PyDateTime>() {
518518
Ok(dt.into())
519519
} else {
520-
Err(ValError::new(ErrorKind::DateTimeType, self))
520+
Err(ValError::new(ErrorKind::DatetimeType, self))
521521
}
522522
}
523523

@@ -530,15 +530,15 @@ impl<'a> Input<'a> for PyAny {
530530
} else if let Ok(py_bytes) = self.cast_as::<PyBytes>() {
531531
bytes_as_datetime(self, py_bytes.as_bytes())
532532
} else if self.cast_as::<PyBool>().is_ok() {
533-
Err(ValError::new(ErrorKind::DateTimeType, self))
533+
Err(ValError::new(ErrorKind::DatetimeType, self))
534534
} else if let Ok(int) = self.extract::<i64>() {
535535
int_as_datetime(self, int, 0)
536536
} else if let Ok(float) = self.extract::<f64>() {
537537
float_as_datetime(self, float)
538538
} else if let Ok(date) = self.cast_as::<PyDate>() {
539539
Ok(date_as_datetime(date)?)
540540
} else {
541-
Err(ValError::new(ErrorKind::DateTimeType, self))
541+
Err(ValError::new(ErrorKind::DatetimeType, self))
542542
}
543543
}
544544

src/input/return_enums.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ impl<'a> IntoPy<PyObject> for EitherString<'a> {
413413
pub fn py_string_str(py_str: &PyString) -> ValResult<&str> {
414414
py_str
415415
.to_str()
416-
.map_err(|_| ValError::new_custom_input(ErrorKind::StrUnicode, InputValue::PyAny(py_str as &PyAny)))
416+
.map_err(|_| ValError::new_custom_input(ErrorKind::StringUnicode, InputValue::PyAny(py_str as &PyAny)))
417417
}
418418

419419
#[cfg_attr(debug_assertions, derive(Debug))]

src/input/shared.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,8 @@ pub fn str_as_int<'s, 'l>(input: &'s impl Input<'s>, str: &'l str) -> ValResult<
4848
}
4949

5050
pub fn float_as_int<'a>(input: &'a impl Input<'a>, float: f64) -> ValResult<'a, i64> {
51-
if float == f64::INFINITY {
52-
Err(ValError::new(
53-
ErrorKind::IntNan {
54-
nan_value: "infinity".to_string(),
55-
},
56-
input,
57-
))
58-
} else if float == f64::NEG_INFINITY {
59-
Err(ValError::new(
60-
ErrorKind::IntNan {
61-
nan_value: "negative infinity".to_string(),
62-
},
63-
input,
64-
))
65-
} else if float.is_nan() {
66-
Err(ValError::new(
67-
ErrorKind::IntNan {
68-
nan_value: "NaN".to_string(),
69-
},
70-
input,
71-
))
51+
if float == f64::INFINITY || float == f64::NEG_INFINITY || float.is_nan() {
52+
Err(ValError::new(ErrorKind::FiniteNumber, input))
7253
} else if float % 1.0 != 0.0 {
7354
Err(ValError::new(ErrorKind::IntFromFloat, input))
7455
} else {

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod validators;
1818

1919
// required for benchmarks
2020
pub use build_tools::SchemaError;
21-
pub use errors::{PydanticCustomError, PydanticErrorKind, PydanticOmit, ValidationError};
21+
pub use errors::{list_all_errors, PydanticCustomError, PydanticErrorKind, PydanticOmit, ValidationError};
2222
pub use validators::SchemaValidator;
2323

2424
pub fn get_version() -> String {
@@ -41,5 +41,6 @@ fn _pydantic_core(_py: Python, m: &PyModule) -> PyResult<()> {
4141
m.add_class::<PydanticCustomError>()?;
4242
m.add_class::<PydanticErrorKind>()?;
4343
m.add_class::<PydanticOmit>()?;
44+
m.add_function(wrap_pyfunction!(list_all_errors, m)?)?;
4445
Ok(())
4546
}

src/validators/date.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl Validator for DateValidator {
8383
if !raw_date.$constraint(constraint) {
8484
return Err(ValError::new(
8585
ErrorKind::$error {
86-
$constraint: constraint.to_string(),
86+
$constraint: constraint.to_string().into(),
8787
},
8888
input,
8989
));
@@ -120,7 +120,7 @@ fn date_from_datetime<'data>(
120120
// convert DateTimeParsing -> DateFromDatetimeParsing but keep the rest of the error unchanged
121121
for line_error in line_errors.iter_mut() {
122122
match line_error.kind {
123-
ErrorKind::DateTimeParsing { ref error } => {
123+
ErrorKind::DatetimeParsing { ref error } => {
124124
line_error.kind = ErrorKind::DateFromDatetimeParsing {
125125
error: error.to_string(),
126126
};

0 commit comments

Comments
 (0)