Skip to content

Commit 88c4738

Browse files
authored
Add field name in serialization error (#1799)
1 parent 1887c34 commit 88c4738

File tree

5 files changed

+50
-9
lines changed

5 files changed

+50
-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
);

tests/serializers/test_model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ def test_model_wrong_warn():
284284
):
285285
assert s.to_python({'foo': 1, 'bar': b'more'}) == {'foo': 1, 'bar': b'more'}
286286

287+
with pytest.warns(
288+
UserWarning,
289+
match=r"Expected `int` - serialized value may not be as expected \[field_name=foo, input_value='lorem', input_type=str\]",
290+
):
291+
assert s.to_python(BasicModel(foo='lorem')) == {'foo': 'lorem'}
292+
287293

288294
def test_exclude_none():
289295
s = SchemaSerializer(

0 commit comments

Comments
 (0)