Skip to content

Commit 84a1843

Browse files
committed
Add the field name in serialization errors if available
1 parent 715154d commit 84a1843

File tree

4 files changed

+44
-9
lines changed

4 files changed

+44
-9
lines changed

src/serializers/errors.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ impl PydanticSerializationError {
9999
#[derive(Debug, Clone)]
100100
pub struct PydanticSerializationUnexpectedValue {
101101
message: Option<String>,
102+
field_name: Option<String>,
102103
field_type: Option<String>,
103104
input_value: Option<Py<PyAny>>,
104105
}
@@ -107,30 +108,43 @@ impl PydanticSerializationUnexpectedValue {
107108
pub fn new_from_msg(message: Option<String>) -> Self {
108109
Self {
109110
message,
111+
field_name: None,
110112
field_type: None,
111113
input_value: None,
112114
}
113115
}
114116

115-
pub fn new_from_parts(field_type: Option<String>, input_value: Option<Py<PyAny>>) -> Self {
117+
pub fn new_from_parts(
118+
field_name: Option<String>,
119+
field_type: Option<String>,
120+
input_value: Option<Py<PyAny>>,
121+
) -> Self {
116122
Self {
117123
message: None,
124+
field_name,
118125
field_type,
119126
input_value,
120127
}
121128
}
122129

123-
pub fn new(message: Option<String>, field_type: Option<String>, input_value: Option<Py<PyAny>>) -> Self {
130+
pub fn new(
131+
message: Option<String>,
132+
field_name: Option<String>,
133+
field_type: Option<String>,
134+
input_value: Option<Py<PyAny>>,
135+
) -> Self {
124136
Self {
125137
message,
138+
field_name,
126139
field_type,
127140
input_value,
128141
}
129142
}
130143

131144
pub fn to_py_err(&self) -> PyErr {
132-
PyErr::new::<Self, (Option<String>, Option<String>, Option<Py<PyAny>>)>((
145+
PyErr::new::<Self, (Option<String>, Option<String>, Option<String>, Option<Py<PyAny>>)>((
133146
self.message.clone(),
147+
self.field_name.clone(),
134148
self.field_type.clone(),
135149
self.input_value.clone(),
136150
))
@@ -140,10 +154,16 @@ impl PydanticSerializationUnexpectedValue {
140154
#[pymethods]
141155
impl PydanticSerializationUnexpectedValue {
142156
#[new]
143-
#[pyo3(signature = (message=None, field_type=None, input_value=None, /))]
144-
fn py_new(message: Option<String>, field_type: Option<String>, input_value: Option<Py<PyAny>>) -> Self {
157+
#[pyo3(signature = (message=None, field_name=None, field_type=None, input_value=None, /))]
158+
fn py_new(
159+
message: Option<String>,
160+
field_name: Option<String>,
161+
field_type: Option<String>,
162+
input_value: Option<Py<PyAny>>,
163+
) -> Self {
145164
Self {
146165
message,
166+
field_name,
147167
field_type,
148168
input_value,
149169
}
@@ -172,8 +192,16 @@ impl PydanticSerializationUnexpectedValue {
172192

173193
let value_str = truncate_safe_repr(bound_input, None);
174194

175-
write!(message, " [input_value={value_str}, input_type={input_type}]")
195+
if let Some(field_name) = &self.field_name {
196+
write!(
197+
message,
198+
" [field_name={field_name}, input_value={value_str}, input_type={input_type}]"
199+
)
176200
.expect("writing to string should never fail");
201+
} else {
202+
write!(message, " [input_value={value_str}, input_type={input_type}]")
203+
.expect("writing to string should never fail");
204+
}
177205
}
178206

179207
if message.is_empty() {

src/serializers/extra.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::convert::Infallible;
22
use std::ffi::CString;
33
use std::fmt;
4+
use std::string::ToString;
45
use std::sync::Mutex;
56

67
use pyo3::exceptions::{PyTypeError, PyUserWarning, PyValueError};
@@ -383,12 +384,13 @@ impl CollectWarnings {
383384
Ok(())
384385
} else if extra.check.enabled() {
385386
Err(PydanticSerializationUnexpectedValue::new_from_parts(
387+
extra.field_name.map(ToString::to_string),
386388
Some(field_type.to_string()),
387389
Some(value.clone().unbind()),
388390
)
389391
.to_py_err())
390392
} else {
391-
self.fallback_warning(field_type, value);
393+
self.fallback_warning(extra.field_name, field_type, value);
392394
Ok(())
393395
}
394396
}
@@ -408,14 +410,15 @@ impl CollectWarnings {
408410
// in particular, in future we could allow errors instead of warnings on fallback
409411
Err(S::Error::custom(UNEXPECTED_TYPE_SER_MARKER))
410412
} else {
411-
self.fallback_warning(field_type, value);
413+
self.fallback_warning(extra.field_name, field_type, value);
412414
Ok(())
413415
}
414416
}
415417

416-
fn fallback_warning(&self, field_type: &str, value: &Bound<'_, PyAny>) {
418+
fn fallback_warning(&self, field_name: Option<&str>, field_type: &str, value: &Bound<'_, PyAny>) {
417419
if self.mode != WarningsMode::None {
418420
self.register_warning(PydanticSerializationUnexpectedValue::new_from_parts(
421+
field_name.map(ToString::to_string),
419422
Some(field_type.to_string()),
420423
Some(value.clone().unbind()),
421424
));

src/serializers/fields.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::string::ToString;
23
use std::sync::Arc;
34

45
use pyo3::prelude::*;
@@ -217,6 +218,7 @@ impl GeneralFieldsSerializer {
217218
} else if field_extra.check == SerCheck::Strict {
218219
return Err(PydanticSerializationUnexpectedValue::new(
219220
Some(format!("Unexpected field `{key}`")),
221+
field_extra.field_name.map(ToString::to_string),
220222
field_extra.model_type_name().map(|bound| bound.to_string()),
221223
None,
222224
)
@@ -235,6 +237,7 @@ impl GeneralFieldsSerializer {
235237

236238
Err(PydanticSerializationUnexpectedValue::new(
237239
Some(format!("Expected {required_fields} fields but got {used_req_fields}").to_string()),
240+
extra.field_name.map(ToString::to_string),
238241
extra.model_type_name().map(|bound| bound.to_string()),
239242
extra.model.map(|bound| bound.clone().unbind()),
240243
)

src/serializers/type_serializers/union.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ impl TaggedUnionSerializer {
338338
PydanticSerializationUnexpectedValue::new(
339339
Some("Defaulting to left to right union serialization - failed to get discriminator value for tagged union serialization".to_string()),
340340
None,
341+
None,
341342
Some(value.clone().unbind()),
342343
)
343344
);

0 commit comments

Comments
 (0)