Skip to content

Commit be9c21c

Browse files
authored
Remove lifetime from errors (#1084)
1 parent 2bb53d8 commit be9c21c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+294
-319
lines changed

src/errors/line_error.rs

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,54 @@ use crate::input::Input;
99
use super::location::{LocItem, Location};
1010
use super::types::ErrorType;
1111

12-
pub type ValResult<'a, T> = Result<T, ValError<'a>>;
12+
pub type ValResult<T> = Result<T, ValError>;
13+
14+
pub trait AsErrorValue {
15+
fn as_error_value(&self) -> InputValue;
16+
}
17+
18+
impl<'a, T: Input<'a>> AsErrorValue for T {
19+
fn as_error_value(&self) -> InputValue {
20+
Input::as_error_value(self)
21+
}
22+
}
1323

1424
#[cfg_attr(debug_assertions, derive(Debug))]
15-
pub enum ValError<'a> {
16-
LineErrors(Vec<ValLineError<'a>>),
25+
pub enum ValError {
26+
LineErrors(Vec<ValLineError>),
1727
InternalErr(PyErr),
1828
Omit,
1929
UseDefault,
2030
}
2131

22-
impl<'a> From<PyErr> for ValError<'a> {
32+
impl From<PyErr> for ValError {
2333
fn from(py_err: PyErr) -> Self {
2434
Self::InternalErr(py_err)
2535
}
2636
}
2737

28-
impl<'a> From<PyDowncastError<'_>> for ValError<'a> {
38+
impl From<PyDowncastError<'_>> for ValError {
2939
fn from(py_downcast: PyDowncastError) -> Self {
3040
Self::InternalErr(PyTypeError::new_err(py_downcast.to_string()))
3141
}
3242
}
3343

34-
impl<'a> From<Vec<ValLineError<'a>>> for ValError<'a> {
35-
fn from(line_errors: Vec<ValLineError<'a>>) -> Self {
44+
impl From<Vec<ValLineError>> for ValError {
45+
fn from(line_errors: Vec<ValLineError>) -> Self {
3646
Self::LineErrors(line_errors)
3747
}
3848
}
3949

40-
impl<'a> ValError<'a> {
41-
pub fn new(error_type: ErrorType, input: &'a impl Input<'a>) -> ValError<'a> {
50+
impl ValError {
51+
pub fn new(error_type: ErrorType, input: &impl AsErrorValue) -> ValError {
4252
Self::LineErrors(vec![ValLineError::new(error_type, input)])
4353
}
4454

45-
pub fn new_with_loc(error_type: ErrorType, input: &'a impl Input<'a>, loc: impl Into<LocItem>) -> ValError<'a> {
55+
pub fn new_with_loc(error_type: ErrorType, input: &impl AsErrorValue, loc: impl Into<LocItem>) -> ValError {
4656
Self::LineErrors(vec![ValLineError::new_with_loc(error_type, input, loc)])
4757
}
4858

49-
pub fn new_custom_input(error_type: ErrorType, input_value: InputValue<'a>) -> ValError<'a> {
59+
pub fn new_custom_input(error_type: ErrorType, input_value: InputValue) -> ValError {
5060
Self::LineErrors(vec![ValLineError::new_custom_input(error_type, input_value)])
5161
}
5262

@@ -62,55 +72,45 @@ impl<'a> ValError<'a> {
6272
other => other,
6373
}
6474
}
65-
66-
/// a bit like clone but change the lifetime to match py
67-
pub fn into_owned(self, py: Python<'_>) -> ValError<'_> {
68-
match self {
69-
ValError::LineErrors(errors) => errors.into_iter().map(|e| e.into_owned(py)).collect::<Vec<_>>().into(),
70-
ValError::InternalErr(err) => ValError::InternalErr(err),
71-
ValError::Omit => ValError::Omit,
72-
ValError::UseDefault => ValError::UseDefault,
73-
}
74-
}
7575
}
7676

7777
/// A `ValLineError` is a single error that occurred during validation which is converted to a `PyLineError`
7878
/// to eventually form a `ValidationError`.
7979
/// I don't like the name `ValLineError`, but it's the best I could come up with (for now).
8080
#[cfg_attr(debug_assertions, derive(Debug))]
81-
pub struct ValLineError<'a> {
81+
pub struct ValLineError {
8282
pub error_type: ErrorType,
8383
// location is reversed so that adding an "outer" location item is pushing, it's reversed before showing to the user
8484
pub location: Location,
85-
pub input_value: InputValue<'a>,
85+
pub input_value: InputValue,
8686
}
8787

88-
impl<'a> ValLineError<'a> {
89-
pub fn new(error_type: ErrorType, input: &'a impl Input<'a>) -> ValLineError<'a> {
88+
impl ValLineError {
89+
pub fn new(error_type: ErrorType, input: &impl AsErrorValue) -> ValLineError {
9090
Self {
9191
error_type,
9292
input_value: input.as_error_value(),
9393
location: Location::default(),
9494
}
9595
}
9696

97-
pub fn new_with_loc(error_type: ErrorType, input: &'a impl Input<'a>, loc: impl Into<LocItem>) -> ValLineError<'a> {
97+
pub fn new_with_loc(error_type: ErrorType, input: &impl AsErrorValue, loc: impl Into<LocItem>) -> ValLineError {
9898
Self {
9999
error_type,
100100
input_value: input.as_error_value(),
101101
location: Location::new_some(loc.into()),
102102
}
103103
}
104104

105-
pub fn new_with_full_loc(error_type: ErrorType, input: &'a impl Input<'a>, location: Location) -> ValLineError<'a> {
105+
pub fn new_with_full_loc(error_type: ErrorType, input: &impl AsErrorValue, location: Location) -> ValLineError {
106106
Self {
107107
error_type,
108108
input_value: input.as_error_value(),
109109
location,
110110
}
111111
}
112112

113-
pub fn new_custom_input(error_type: ErrorType, input_value: InputValue<'a>) -> ValLineError<'a> {
113+
pub fn new_custom_input(error_type: ErrorType, input_value: InputValue) -> ValLineError {
114114
Self {
115115
error_type,
116116
input_value,
@@ -130,35 +130,20 @@ impl<'a> ValLineError<'a> {
130130
self.error_type = error_type;
131131
self
132132
}
133-
134-
/// a bit like clone but change the lifetime to match py, used by ValError.into_owned above
135-
pub fn into_owned(self, py: Python<'_>) -> ValLineError<'_> {
136-
ValLineError {
137-
error_type: self.error_type,
138-
input_value: match self.input_value {
139-
InputValue::PyAny(input) => InputValue::PyAny(input.to_object(py).into_ref(py)),
140-
InputValue::JsonInput(input) => InputValue::JsonInput(input),
141-
InputValue::String(input) => InputValue::PyAny(input.to_object(py).into_ref(py)),
142-
},
143-
location: self.location,
144-
}
145-
}
146133
}
147134

148135
#[cfg_attr(debug_assertions, derive(Debug))]
149136
#[derive(Clone)]
150-
pub enum InputValue<'a> {
151-
PyAny(&'a PyAny),
152-
JsonInput(JsonValue),
153-
String(&'a str),
137+
pub enum InputValue {
138+
Python(PyObject),
139+
Json(JsonValue),
154140
}
155141

156-
impl<'a> ToPyObject for InputValue<'a> {
142+
impl ToPyObject for InputValue {
157143
fn to_object(&self, py: Python) -> PyObject {
158144
match self {
159-
Self::PyAny(input) => input.into_py(py),
160-
Self::JsonInput(input) => input.to_object(py),
161-
Self::String(input) => input.into_py(py),
145+
Self::Python(input) => input.clone_ref(py),
146+
Self::Json(input) => input.to_object(py),
162147
}
163148
}
164149
}

src/errors/mod.rs

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

9-
pub use self::line_error::{InputValue, ValError, ValLineError, ValResult};
9+
pub use self::line_error::{AsErrorValue, InputValue, ValError, ValLineError, ValResult};
1010
pub use self::location::{AsLocItem, LocItem};
1111
pub use self::types::{list_all_errors, ErrorType, ErrorTypeDefaults, Number};
1212
pub use self::validation_exception::ValidationError;

src/errors/validation_exception.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,8 @@ fn get_url_prefix(py: Python, include_url: bool) -> Option<&str> {
225225

226226
// used to convert a validation error back to ValError for wrap functions
227227
impl ValidationError {
228-
pub(crate) fn into_val_error(self, py: Python<'_>) -> ValError<'_> {
229-
self.line_errors
230-
.into_iter()
231-
.map(|e| e.into_val_line_error(py))
232-
.collect::<Vec<_>>()
233-
.into()
228+
pub(crate) fn into_val_error(self) -> ValError {
229+
self.line_errors.into_iter().map(Into::into).collect::<Vec<_>>().into()
234230
}
235231
}
236232

@@ -416,7 +412,7 @@ pub struct PyLineError {
416412
input_value: PyObject,
417413
}
418414

419-
impl<'a> IntoPy<PyLineError> for ValLineError<'a> {
415+
impl IntoPy<PyLineError> for ValLineError {
420416
fn into_py(self, py: Python<'_>) -> PyLineError {
421417
PyLineError {
422418
error_type: self.error_type,
@@ -426,13 +422,13 @@ impl<'a> IntoPy<PyLineError> for ValLineError<'a> {
426422
}
427423
}
428424

429-
impl PyLineError {
425+
impl From<PyLineError> for ValLineError {
430426
/// Used to extract line errors from a validation error for wrap functions
431-
fn into_val_line_error(self, py: Python<'_>) -> ValLineError<'_> {
427+
fn from(other: PyLineError) -> ValLineError {
432428
ValLineError {
433-
error_type: self.error_type,
434-
location: self.location,
435-
input_value: InputValue::PyAny(self.input_value.into_ref(py)),
429+
error_type: other.error_type,
430+
location: other.location,
431+
input_value: InputValue::Python(other.input_value),
436432
}
437433
}
438434
}

src/errors/value_exception.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use pyo3::exceptions::{PyException, PyValueError};
22
use pyo3::prelude::*;
33
use pyo3::types::{PyDict, PyString};
44

5-
use crate::input::{Input, InputType};
5+
use crate::input::InputType;
66
use crate::tools::extract_i64;
77

8+
use super::line_error::AsErrorValue;
89
use super::{ErrorType, ValError};
910

1011
#[pyclass(extends=PyException, module="pydantic_core._pydantic_core")]
@@ -105,7 +106,7 @@ impl PydanticCustomError {
105106
}
106107

107108
impl PydanticCustomError {
108-
pub fn into_val_error<'a>(self, input: &'a impl Input<'a>) -> ValError<'a> {
109+
pub fn into_val_error(self, input: &impl AsErrorValue) -> ValError {
109110
let error_type = ErrorType::CustomError {
110111
error_type: self.error_type,
111112
message_template: self.message_template,
@@ -184,7 +185,7 @@ impl PydanticKnownError {
184185
}
185186

186187
impl PydanticKnownError {
187-
pub fn into_val_error<'a>(self, input: &'a impl Input<'a>) -> ValError<'a> {
188+
pub fn into_val_error(self, input: &impl AsErrorValue) -> ValError {
188189
ValError::new(self.error_type, input)
189190
}
190191
}

src/input/datetime.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ impl<'a> EitherDateTime<'a> {
286286
}
287287
}
288288

289-
pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'a, EitherDate<'a>> {
289+
pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<EitherDate<'a>> {
290290
match Date::parse_bytes(bytes) {
291291
Ok(date) => Ok(date.into()),
292292
Err(err) => Err(ValError::new(
@@ -303,7 +303,7 @@ pub fn bytes_as_time<'a>(
303303
input: &'a impl Input<'a>,
304304
bytes: &[u8],
305305
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
306-
) -> ValResult<'a, EitherTime<'a>> {
306+
) -> ValResult<EitherTime<'a>> {
307307
match Time::parse_bytes_with_config(
308308
bytes,
309309
&TimeConfig {
@@ -326,7 +326,7 @@ pub fn bytes_as_datetime<'a, 'b>(
326326
input: &'a impl Input<'a>,
327327
bytes: &'b [u8],
328328
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
329-
) -> ValResult<'a, EitherDateTime<'a>> {
329+
) -> ValResult<EitherDateTime<'a>> {
330330
match DateTime::parse_bytes_with_config(
331331
bytes,
332332
&TimeConfig {
@@ -455,7 +455,7 @@ pub fn float_as_time<'a>(input: &'a impl Input<'a>, timestamp: f64) -> ValResult
455455
int_as_time(input, timestamp.floor() as i64, microseconds.round() as u32)
456456
}
457457

458-
fn map_timedelta_err<'a>(input: &'a impl Input<'a>, err: ParseError) -> ValError<'a> {
458+
fn map_timedelta_err<'a>(input: &'a impl Input<'a>, err: ParseError) -> ValError {
459459
ValError::new(
460460
ErrorType::TimeDeltaParsing {
461461
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
@@ -469,7 +469,7 @@ pub fn bytes_as_timedelta<'a, 'b>(
469469
input: &'a impl Input<'a>,
470470
bytes: &'b [u8],
471471
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
472-
) -> ValResult<'a, EitherTimedelta<'a>> {
472+
) -> ValResult<EitherTimedelta<'a>> {
473473
match Duration::parse_bytes_with_config(
474474
bytes,
475475
&TimeConfig {

src/input/input_abstract.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl TryFrom<&str> for InputType {
4949
/// * `strict_*` & `lax_*` if they have different behavior
5050
/// * or, `validate_*` and `strict_*` to just call `validate_*` if the behavior for strict and lax is the same
5151
pub trait Input<'a>: fmt::Debug + ToPyObject + AsLocItem + Sized {
52-
fn as_error_value(&'a self) -> InputValue<'a>;
52+
fn as_error_value(&self) -> InputValue;
5353

5454
fn identity(&self) -> Option<usize> {
5555
None
@@ -85,11 +85,11 @@ pub trait Input<'a>: fmt::Debug + ToPyObject + AsLocItem + Sized {
8585
false
8686
}
8787

88-
fn validate_args(&'a self) -> ValResult<'a, GenericArguments<'a>>;
88+
fn validate_args(&'a self) -> ValResult<GenericArguments<'a>>;
8989

90-
fn validate_dataclass_args(&'a self, dataclass_name: &str) -> ValResult<'a, GenericArguments<'a>>;
90+
fn validate_dataclass_args(&'a self, dataclass_name: &str) -> ValResult<GenericArguments<'a>>;
9191

92-
fn parse_json(&'a self) -> ValResult<'a, JsonValue>;
92+
fn parse_json(&'a self) -> ValResult<JsonValue>;
9393

9494
fn validate_str(
9595
&'a self,
@@ -99,9 +99,9 @@ pub trait Input<'a>: fmt::Debug + ToPyObject + AsLocItem + Sized {
9999

100100
fn validate_bytes(&'a self, strict: bool) -> ValResult<ValidationMatch<EitherBytes<'a>>>;
101101

102-
fn validate_bool(&self, strict: bool) -> ValResult<'_, ValidationMatch<bool>>;
102+
fn validate_bool(&self, strict: bool) -> ValResult<ValidationMatch<bool>>;
103103

104-
fn validate_int(&'a self, strict: bool) -> ValResult<'a, ValidationMatch<EitherInt<'a>>>;
104+
fn validate_int(&'a self, strict: bool) -> ValResult<ValidationMatch<EitherInt<'a>>>;
105105

106106
fn exact_int(&'a self) -> ValResult<EitherInt<'a>> {
107107
self.validate_int(true).and_then(|val_match| {
@@ -121,7 +121,7 @@ pub trait Input<'a>: fmt::Debug + ToPyObject + AsLocItem + Sized {
121121
})
122122
}
123123

124-
fn validate_float(&'a self, strict: bool) -> ValResult<'a, ValidationMatch<EitherFloat<'a>>>;
124+
fn validate_float(&'a self, strict: bool) -> ValResult<ValidationMatch<EitherFloat<'a>>>;
125125

126126
fn validate_decimal(&'a self, strict: bool, py: Python<'a>) -> ValResult<&'a PyAny> {
127127
if strict {
@@ -230,15 +230,11 @@ pub trait Input<'a>: fmt::Debug + ToPyObject + AsLocItem + Sized {
230230
) -> ValResult<ValidationMatch<EitherTimedelta>>;
231231
}
232232

233-
/// The problem to solve here is that iterating a `StringMapping` returns an owned
234-
/// `StringMapping`, but all the other iterators return references. By introducing
233+
/// The problem to solve here is that iterating collections often returns owned
234+
/// values, but inputs are usually taken by reference. By introducing
235235
/// this trait we abstract over whether the return value from the iterator is owned
236236
/// or borrowed; all we care about is that we can borrow it again with `borrow_input`
237237
/// for some lifetime 'a.
238-
///
239-
/// This lifetime `'a` is shorter than the original lifetime `'data` of the input,
240-
/// which is only a problem in error branches. To resolve we have to call `into_owned`
241-
/// to extend out the lifetime to match the original input.
242238
pub trait BorrowInput {
243239
type Input<'a>: Input<'a>
244240
where

0 commit comments

Comments
 (0)