Skip to content

Commit f27fa47

Browse files
committed
Add hide_input to ValidationError
1 parent 26fa27d commit f27fa47

File tree

11 files changed

+233
-25
lines changed

11 files changed

+233
-25
lines changed

pydantic_core/_pydantic_core.pyi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ class SchemaError(Exception):
173173
class ValidationError(ValueError):
174174
@staticmethod
175175
def from_exception_data(
176-
title: str, errors: 'list[InitErrorDetails]', error_mode: Literal['python', 'json'] = 'python'
176+
title: str,
177+
errors: 'list[InitErrorDetails]',
178+
error_mode: Literal['python', 'json'] = 'python',
179+
hide_input_in_errors: bool = False,
177180
) -> ValidationError:
178181
"""
179182
Provisory constructor for a Validation Error.

pydantic_core/core_schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class CoreConfig(TypedDict, total=False):
5151
# the config options are used to customise serialization to JSON
5252
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
5353
ser_json_bytes: Literal['utf8', 'base64'] # default: 'utf8'
54+
# used to hide input data from ValidationError repr
55+
hide_input_in_errors: bool
5456

5557

5658
IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'

src/build_tools.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ impl SchemaError {
140140
match error {
141141
ValError::LineErrors(raw_errors) => {
142142
let line_errors = raw_errors.into_iter().map(|e| e.into_py(py)).collect();
143-
let validation_error = ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python);
143+
let validation_error =
144+
ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python, false);
144145
let schema_error = SchemaError(SchemaErrorEnum::ValidationError(validation_error));
145146
match Py::new(py, schema_error) {
146147
Ok(err) => PyErr::from_value(err.into_ref(py)),
@@ -177,21 +178,21 @@ impl SchemaError {
177178
fn errors(&self, py: Python) -> PyResult<Py<PyList>> {
178179
match &self.0 {
179180
SchemaErrorEnum::Message(_) => Ok(PyList::empty(py).into_py(py)),
180-
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, true),
181+
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, false),
181182
}
182183
}
183184

184185
fn __str__(&self, py: Python) -> String {
185186
match &self.0 {
186187
SchemaErrorEnum::Message(message) => message.to_owned(),
187-
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
188+
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
188189
}
189190
}
190191

191192
fn __repr__(&self, py: Python) -> String {
192193
match &self.0 {
193194
SchemaErrorEnum::Message(message) => format!("SchemaError({message:?})"),
194-
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
195+
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
195196
}
196197
}
197198
}

src/errors/validation_exception.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ pub struct ValidationError {
3131
line_errors: Vec<PyLineError>,
3232
error_mode: ErrorMode,
3333
title: PyObject,
34+
hide_input_in_errors: bool,
3435
}
3536

3637
impl ValidationError {
37-
pub fn new(line_errors: Vec<PyLineError>, title: PyObject, error_mode: ErrorMode) -> Self {
38+
pub fn new(line_errors: Vec<PyLineError>, title: PyObject, error_mode: ErrorMode, hide_input_in_errors: bool) -> Self {
3839
Self {
3940
line_errors,
4041
title,
4142
error_mode,
43+
hide_input_in_errors,
4244
}
4345
}
4446

@@ -48,6 +50,7 @@ impl ValidationError {
4850
error_mode: ErrorMode,
4951
error: ValError,
5052
outer_location: Option<LocItem>,
53+
hide_input_in_errors: bool,
5154
) -> PyErr {
5255
match error {
5356
ValError::LineErrors(raw_errors) => {
@@ -58,7 +61,7 @@ impl ValidationError {
5861
.collect(),
5962
None => raw_errors.into_iter().map(|e| e.into_py(py)).collect(),
6063
};
61-
let validation_error = Self::new(line_errors, title, error_mode);
64+
let validation_error = Self::new(line_errors, title, error_mode, hide_input_in_errors);
6265
match Py::new(py, validation_error) {
6366
Ok(err) => PyErr::from_value(err.into_ref(py)),
6467
Err(err) => err,
@@ -69,9 +72,9 @@ impl ValidationError {
6972
}
7073
}
7174

72-
pub fn display(&self, py: Python, prefix_override: Option<&'static str>) -> String {
75+
pub fn display(&self, py: Python, prefix_override: Option<&'static str>, hide_input_in_errors: bool) -> String {
7376
let url_prefix = get_url_prefix(py, include_url_env(py));
74-
let line_errors = pretty_py_line_errors(py, &self.error_mode, self.line_errors.iter(), url_prefix);
77+
let line_errors = pretty_py_line_errors(py, &self.error_mode, self.line_errors.iter(), url_prefix, hide_input_in_errors);
7578
if let Some(prefix) = prefix_override {
7679
format!("{prefix}\n{line_errors}")
7780
} else {
@@ -124,18 +127,21 @@ impl<'a> IntoPy<ValError<'a>> for ValidationError {
124127
#[pymethods]
125128
impl ValidationError {
126129
#[staticmethod]
130+
#[pyo3(signature = (title, line_errors, error_mode=None, hide_input_in_errors=false))]
127131
fn from_exception_data(
128132
py: Python,
129133
title: PyObject,
130134
line_errors: &PyList,
131135
error_mode: Option<&str>,
136+
hide_input_in_errors: bool,
132137
) -> PyResult<Py<Self>> {
133138
Py::new(
134139
py,
135140
Self {
136141
line_errors: line_errors.iter().map(PyLineError::try_from).collect::<PyResult<_>>()?,
137142
title,
138143
error_mode: ErrorMode::try_from(error_mode)?,
144+
hide_input_in_errors,
139145
},
140146
)
141147
}
@@ -210,7 +216,7 @@ impl ValidationError {
210216
}
211217

212218
fn __repr__(&self, py: Python) -> String {
213-
self.display(py, None)
219+
self.display(py, None, self.hide_input_in_errors)
214220
}
215221

216222
fn __str__(&self, py: Python) -> String {
@@ -238,9 +244,10 @@ pub fn pretty_py_line_errors<'a>(
238244
error_mode: &ErrorMode,
239245
line_errors_iter: impl Iterator<Item = &'a PyLineError>,
240246
url_prefix: Option<&str>,
247+
hide_input_in_errors: bool,
241248
) -> String {
242249
line_errors_iter
243-
.map(|i| i.pretty(py, error_mode, url_prefix))
250+
.map(|i| i.pretty(py, error_mode, url_prefix, hide_input_in_errors))
244251
.collect::<Result<Vec<_>, _>>()
245252
.unwrap_or_else(|err| vec![format!("[error formatting line errors: {err}]")])
246253
.join("\n")
@@ -349,7 +356,13 @@ impl PyLineError {
349356
Ok(dict.into_py(py))
350357
}
351358

352-
fn pretty(&self, py: Python, error_mode: &ErrorMode, url_prefix: Option<&str>) -> Result<String, fmt::Error> {
359+
fn pretty(
360+
&self,
361+
py: Python,
362+
error_mode: &ErrorMode,
363+
url_prefix: Option<&str>,
364+
hide_input_in_errors: bool,
365+
) -> Result<String, fmt::Error> {
353366
let mut output = String::with_capacity(200);
354367
write!(output, "{}", self.location)?;
355368

@@ -359,12 +372,14 @@ impl PyLineError {
359372
};
360373
write!(output, " {message} [type={}", self.error_type.type_string())?;
361374

362-
let input_value = self.input_value.as_ref(py);
363-
let input_str = safe_repr(input_value);
364-
truncate_input_value!(output, input_str);
375+
if !hide_input_in_errors {
376+
let input_value = self.input_value.as_ref(py);
377+
let input_str = safe_repr(input_value);
378+
truncate_input_value!(output, input_str);
365379

366-
if let Ok(type_) = input_value.get_type().name() {
367-
write!(output, ", input_type={type_}")?;
380+
if let Ok(type_) = input_value.get_type().name() {
381+
write!(output, ", input_type={type_}")?;
382+
}
368383
}
369384
if let Some(url_prefix) = url_prefix {
370385
match self.error_type {

src/validators/function.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,36 @@ pub struct FunctionWrapValidator {
262262
name: String,
263263
is_field_validator: bool,
264264
info_arg: bool,
265+
hide_input_in_errors: bool,
265266
}
266267

267-
impl_build!(FunctionWrapValidator, "function-wrap");
268+
impl BuildValidator for FunctionWrapValidator {
269+
const EXPECTED_TYPE: &'static str = "function-wrap";
270+
271+
fn build(
272+
schema: &PyDict,
273+
config: Option<&PyDict>,
274+
definitions: &mut DefinitionsBuilder<CombinedValidator>,
275+
) -> PyResult<CombinedValidator> {
276+
let py = schema.py();
277+
let validator = build_validator(schema.get_as_req(intern!(py, "schema"))?, config, definitions)?;
278+
let (is_field_validator, info_arg, function) = destructure_function_schema(schema)?;
279+
let hide_input_in_errors: bool = config.get_as(intern!(py, "hide_input_in_errors"))?.unwrap_or(false);
280+
Ok(Self {
281+
validator: Box::new(validator),
282+
func: function.into_py(py),
283+
config: match config {
284+
Some(c) => c.into(),
285+
None => py.None(),
286+
},
287+
name: format!("function-wrap[{}()]", function_name(function)?),
288+
is_field_validator,
289+
info_arg,
290+
hide_input_in_errors,
291+
}
292+
.into())
293+
}
294+
}
268295

269296
impl FunctionWrapValidator {
270297
fn _validate<'s, 'data>(
@@ -301,6 +328,7 @@ impl Validator for FunctionWrapValidator {
301328
definitions,
302329
extra,
303330
recursion_guard,
331+
self.hide_input_in_errors,
304332
),
305333
};
306334
self._validate(
@@ -329,6 +357,7 @@ impl Validator for FunctionWrapValidator {
329357
definitions,
330358
extra,
331359
recursion_guard,
360+
self.hide_input_in_errors,
332361
),
333362
updated_field_name: field_name.to_string(),
334363
updated_field_value: field_value.to_object(py),

src/validators/generator.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct GeneratorValidator {
1818
min_length: Option<usize>,
1919
max_length: Option<usize>,
2020
name: String,
21+
hide_input_in_errors: bool,
2122
}
2223

2324
impl BuildValidator for GeneratorValidator {
@@ -33,11 +34,13 @@ impl BuildValidator for GeneratorValidator {
3334
Some(ref v) => format!("{}[{}]", Self::EXPECTED_TYPE, v.get_name()),
3435
None => format!("{}[any]", Self::EXPECTED_TYPE),
3536
};
37+
let hide_input_in_errors: bool = config.get_as(pyo3::intern!(schema.py(), "hide_input_in_errors"))?.unwrap_or(false);
3638
Ok(Self {
3739
item_validator,
3840
name,
3941
min_length: schema.get_as(pyo3::intern!(schema.py(), "min_length"))?,
4042
max_length: schema.get_as(pyo3::intern!(schema.py(), "max_length"))?,
43+
hide_input_in_errors,
4144
}
4245
.into())
4346
}
@@ -53,16 +56,24 @@ impl Validator for GeneratorValidator {
5356
recursion_guard: &'s mut RecursionGuard,
5457
) -> ValResult<'data, PyObject> {
5558
let iterator = input.validate_iter()?;
56-
let validator = self
57-
.item_validator
58-
.as_ref()
59-
.map(|v| InternalValidator::new(py, "ValidatorIterator", v, definitions, extra, recursion_guard));
59+
let validator = self.item_validator.as_ref().map(|v| {
60+
InternalValidator::new(
61+
py,
62+
"ValidatorIterator",
63+
v,
64+
definitions,
65+
extra,
66+
recursion_guard,
67+
self.hide_input_in_errors,
68+
)
69+
});
6070

6171
let v_iterator = ValidatorIterator {
6272
iterator,
6373
validator,
6474
min_length: self.min_length,
6575
max_length: self.max_length,
76+
hide_input_in_errors: self.hide_input_in_errors,
6677
};
6778
Ok(v_iterator.into_py(py))
6879
}
@@ -98,6 +109,7 @@ struct ValidatorIterator {
98109
validator: Option<InternalValidator>,
99110
min_length: Option<usize>,
100111
max_length: Option<usize>,
112+
hide_input_in_errors: bool,
101113
}
102114

103115
#[pymethods]
@@ -109,6 +121,7 @@ impl ValidatorIterator {
109121
fn __next__(mut slf: PyRefMut<'_, Self>, py: Python) -> PyResult<Option<PyObject>> {
110122
let min_length = slf.min_length;
111123
let max_length = slf.max_length;
124+
let hide_input_in_errors = slf.hide_input_in_errors;
112125
let Self {
113126
validator, iterator, ..
114127
} = &mut *slf;
@@ -133,6 +146,7 @@ impl ValidatorIterator {
133146
ErrorMode::Python,
134147
val_error,
135148
None,
149+
hide_input_in_errors,
136150
));
137151
}
138152
}
@@ -157,6 +171,7 @@ impl ValidatorIterator {
157171
ErrorMode::Python,
158172
val_error,
159173
None,
174+
hide_input_in_errors,
160175
));
161176
}
162177
}
@@ -203,6 +218,7 @@ pub struct InternalValidator {
203218
self_instance: Option<PyObject>,
204219
recursion_guard: RecursionGuard,
205220
validation_mode: InputType,
221+
hide_input_in_errors: bool,
206222
}
207223

208224
impl fmt::Debug for InternalValidator {
@@ -219,6 +235,7 @@ impl InternalValidator {
219235
definitions: &[CombinedValidator],
220236
extra: &Extra,
221237
recursion_guard: &RecursionGuard,
238+
hide_input_in_errors: bool,
222239
) -> Self {
223240
Self {
224241
name: name.to_string(),
@@ -230,6 +247,7 @@ impl InternalValidator {
230247
self_instance: extra.self_instance.map(|d| d.into_py(py)),
231248
recursion_guard: recursion_guard.clone(),
232249
validation_mode: extra.mode,
250+
hide_input_in_errors,
233251
}
234252
}
235253

@@ -261,7 +279,14 @@ impl InternalValidator {
261279
&mut self.recursion_guard,
262280
)
263281
.map_err(|e| {
264-
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
282+
ValidationError::from_val_error(
283+
py,
284+
self.name.to_object(py),
285+
ErrorMode::Python,
286+
e,
287+
outer_location,
288+
self.hide_input_in_errors,
289+
)
265290
})
266291
}
267292

@@ -286,7 +311,14 @@ impl InternalValidator {
286311
self.validator
287312
.validate(py, input, &extra, &self.definitions, &mut self.recursion_guard)
288313
.map_err(|e| {
289-
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
314+
ValidationError::from_val_error(
315+
py,
316+
self.name.to_object(py),
317+
ErrorMode::Python,
318+
e,
319+
outer_location,
320+
self.hide_input_in_errors,
321+
)
290322
})
291323
}
292324
}

0 commit comments

Comments
 (0)