Skip to content

Add hide_input to ValidationError #633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ class SchemaError(Exception):
class ValidationError(ValueError):
@staticmethod
def from_exception_data(
title: str, errors: 'list[InitErrorDetails]', error_mode: Literal['python', 'json'] = 'python'
title: str,
errors: 'list[InitErrorDetails]',
error_mode: Literal['python', 'json'] = 'python',
hide_input_in_errors: bool = False,
) -> ValidationError:
"""
Provisory constructor for a Validation Error.
Expand Down
2 changes: 2 additions & 0 deletions pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class CoreConfig(TypedDict, total=False):
# the config options are used to customise serialization to JSON
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
ser_json_bytes: Literal['utf8', 'base64'] # default: 'utf8'
# used to hide input data from ValidationError repr
hide_input_in_errors: bool


IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'
Expand Down
9 changes: 5 additions & 4 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ impl SchemaError {
match error {
ValError::LineErrors(raw_errors) => {
let line_errors = raw_errors.into_iter().map(|e| e.into_py(py)).collect();
let validation_error = ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python);
let validation_error =
ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python, false);
let schema_error = SchemaError(SchemaErrorEnum::ValidationError(validation_error));
match Py::new(py, schema_error) {
Ok(err) => PyErr::from_value(err.into_ref(py)),
Expand Down Expand Up @@ -177,21 +178,21 @@ impl SchemaError {
fn errors(&self, py: Python) -> PyResult<Py<PyList>> {
match &self.0 {
SchemaErrorEnum::Message(_) => Ok(PyList::empty(py).into_py(py)),
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, true),
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, false),
}
}

fn __str__(&self, py: Python) -> String {
match &self.0 {
SchemaErrorEnum::Message(message) => message.to_owned(),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
}
}

fn __repr__(&self, py: Python) -> String {
match &self.0 {
SchemaErrorEnum::Message(message) => format!("SchemaError({message:?})"),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
}
}
}
Expand Down
50 changes: 38 additions & 12 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@ pub struct ValidationError {
line_errors: Vec<PyLineError>,
error_mode: ErrorMode,
title: PyObject,
hide_input_in_errors: bool,
}

impl ValidationError {
pub fn new(line_errors: Vec<PyLineError>, title: PyObject, error_mode: ErrorMode) -> Self {
pub fn new(
line_errors: Vec<PyLineError>,
title: PyObject,
error_mode: ErrorMode,
hide_input_in_errors: bool,
) -> Self {
Self {
line_errors,
title,
error_mode,
hide_input_in_errors,
}
}

Expand All @@ -48,6 +55,7 @@ impl ValidationError {
error_mode: ErrorMode,
error: ValError,
outer_location: Option<LocItem>,
hide_input_in_errors: bool,
) -> PyErr {
match error {
ValError::LineErrors(raw_errors) => {
Expand All @@ -58,7 +66,7 @@ impl ValidationError {
.collect(),
None => raw_errors.into_iter().map(|e| e.into_py(py)).collect(),
};
let validation_error = Self::new(line_errors, title, error_mode);
let validation_error = Self::new(line_errors, title, error_mode, hide_input_in_errors);
match Py::new(py, validation_error) {
Ok(err) => PyErr::from_value(err.into_ref(py)),
Err(err) => err,
Expand All @@ -69,9 +77,15 @@ impl ValidationError {
}
}

pub fn display(&self, py: Python, prefix_override: Option<&'static str>) -> String {
pub fn display(&self, py: Python, prefix_override: Option<&'static str>, hide_input_in_errors: bool) -> String {
let url_prefix = get_url_prefix(py, include_url_env(py));
let line_errors = pretty_py_line_errors(py, &self.error_mode, self.line_errors.iter(), url_prefix);
let line_errors = pretty_py_line_errors(
py,
&self.error_mode,
self.line_errors.iter(),
url_prefix,
hide_input_in_errors,
);
if let Some(prefix) = prefix_override {
format!("{prefix}\n{line_errors}")
} else {
Expand Down Expand Up @@ -124,18 +138,21 @@ impl<'a> IntoPy<ValError<'a>> for ValidationError {
#[pymethods]
impl ValidationError {
#[staticmethod]
#[pyo3(signature = (title, line_errors, error_mode=None, hide_input_in_errors=false))]
fn from_exception_data(
py: Python,
title: PyObject,
line_errors: &PyList,
error_mode: Option<&str>,
hide_input_in_errors: bool,
) -> PyResult<Py<Self>> {
Py::new(
py,
Self {
line_errors: line_errors.iter().map(PyLineError::try_from).collect::<PyResult<_>>()?,
title,
error_mode: ErrorMode::try_from(error_mode)?,
hide_input_in_errors,
},
)
}
Expand Down Expand Up @@ -210,7 +227,7 @@ impl ValidationError {
}

fn __repr__(&self, py: Python) -> String {
self.display(py, None)
self.display(py, None, self.hide_input_in_errors)
}

fn __str__(&self, py: Python) -> String {
Expand Down Expand Up @@ -238,9 +255,10 @@ pub fn pretty_py_line_errors<'a>(
error_mode: &ErrorMode,
line_errors_iter: impl Iterator<Item = &'a PyLineError>,
url_prefix: Option<&str>,
hide_input_in_errors: bool,
) -> String {
line_errors_iter
.map(|i| i.pretty(py, error_mode, url_prefix))
.map(|i| i.pretty(py, error_mode, url_prefix, hide_input_in_errors))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|err| vec![format!("[error formatting line errors: {err}]")])
.join("\n")
Expand Down Expand Up @@ -349,7 +367,13 @@ impl PyLineError {
Ok(dict.into_py(py))
}

fn pretty(&self, py: Python, error_mode: &ErrorMode, url_prefix: Option<&str>) -> Result<String, fmt::Error> {
fn pretty(
&self,
py: Python,
error_mode: &ErrorMode,
url_prefix: Option<&str>,
hide_input_in_errors: bool,
) -> Result<String, fmt::Error> {
let mut output = String::with_capacity(200);
write!(output, "{}", self.location)?;

Expand All @@ -359,12 +383,14 @@ impl PyLineError {
};
write!(output, " {message} [type={}", self.error_type.type_string())?;

let input_value = self.input_value.as_ref(py);
let input_str = safe_repr(input_value);
truncate_input_value!(output, input_str);
if !hide_input_in_errors {
let input_value = self.input_value.as_ref(py);
let input_str = safe_repr(input_value);
truncate_input_value!(output, input_str);

if let Ok(type_) = input_value.get_type().name() {
write!(output, ", input_type={type_}")?;
if let Ok(type_) = input_value.get_type().name() {
write!(output, ", input_type={type_}")?;
}
}
if let Some(url_prefix) = url_prefix {
match self.error_type {
Expand Down
31 changes: 30 additions & 1 deletion src/validators/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,36 @@ pub struct FunctionWrapValidator {
name: String,
is_field_validator: bool,
info_arg: bool,
hide_input_in_errors: bool,
}

impl_build!(FunctionWrapValidator, "function-wrap");
impl BuildValidator for FunctionWrapValidator {
const EXPECTED_TYPE: &'static str = "function-wrap";

fn build(
schema: &PyDict,
config: Option<&PyDict>,
definitions: &mut DefinitionsBuilder<CombinedValidator>,
) -> PyResult<CombinedValidator> {
let py = schema.py();
let validator = build_validator(schema.get_as_req(intern!(py, "schema"))?, config, definitions)?;
let (is_field_validator, info_arg, function) = destructure_function_schema(schema)?;
let hide_input_in_errors: bool = config.get_as(intern!(py, "hide_input_in_errors"))?.unwrap_or(false);
Ok(Self {
validator: Box::new(validator),
func: function.into_py(py),
config: match config {
Some(c) => c.into(),
None => py.None(),
},
name: format!("function-wrap[{}()]", function_name(function)?),
is_field_validator,
info_arg,
hide_input_in_errors,
}
.into())
}
}

impl FunctionWrapValidator {
fn _validate<'s, 'data>(
Expand Down Expand Up @@ -301,6 +328,7 @@ impl Validator for FunctionWrapValidator {
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
),
};
self._validate(
Expand Down Expand Up @@ -329,6 +357,7 @@ impl Validator for FunctionWrapValidator {
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
),
updated_field_name: field_name.to_string(),
updated_field_value: field_value.to_object(py),
Expand Down
46 changes: 40 additions & 6 deletions src/validators/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct GeneratorValidator {
min_length: Option<usize>,
max_length: Option<usize>,
name: String,
hide_input_in_errors: bool,
}

impl BuildValidator for GeneratorValidator {
Expand All @@ -33,11 +34,15 @@ impl BuildValidator for GeneratorValidator {
Some(ref v) => format!("{}[{}]", Self::EXPECTED_TYPE, v.get_name()),
None => format!("{}[any]", Self::EXPECTED_TYPE),
};
let hide_input_in_errors: bool = config
.get_as(pyo3::intern!(schema.py(), "hide_input_in_errors"))?
.unwrap_or(false);
Ok(Self {
item_validator,
name,
min_length: schema.get_as(pyo3::intern!(schema.py(), "min_length"))?,
max_length: schema.get_as(pyo3::intern!(schema.py(), "max_length"))?,
hide_input_in_errors,
}
.into())
}
Expand All @@ -53,16 +58,24 @@ impl Validator for GeneratorValidator {
recursion_guard: &'s mut RecursionGuard,
) -> ValResult<'data, PyObject> {
let iterator = input.validate_iter()?;
let validator = self
.item_validator
.as_ref()
.map(|v| InternalValidator::new(py, "ValidatorIterator", v, definitions, extra, recursion_guard));
let validator = self.item_validator.as_ref().map(|v| {
InternalValidator::new(
py,
"ValidatorIterator",
v,
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
)
});

let v_iterator = ValidatorIterator {
iterator,
validator,
min_length: self.min_length,
max_length: self.max_length,
hide_input_in_errors: self.hide_input_in_errors,
};
Ok(v_iterator.into_py(py))
}
Expand Down Expand Up @@ -98,6 +111,7 @@ struct ValidatorIterator {
validator: Option<InternalValidator>,
min_length: Option<usize>,
max_length: Option<usize>,
hide_input_in_errors: bool,
}

#[pymethods]
Expand All @@ -109,6 +123,7 @@ impl ValidatorIterator {
fn __next__(mut slf: PyRefMut<'_, Self>, py: Python) -> PyResult<Option<PyObject>> {
let min_length = slf.min_length;
let max_length = slf.max_length;
let hide_input_in_errors = slf.hide_input_in_errors;
let Self {
validator, iterator, ..
} = &mut *slf;
Expand All @@ -133,6 +148,7 @@ impl ValidatorIterator {
ErrorMode::Python,
val_error,
None,
hide_input_in_errors,
));
}
}
Expand All @@ -157,6 +173,7 @@ impl ValidatorIterator {
ErrorMode::Python,
val_error,
None,
hide_input_in_errors,
));
}
}
Expand Down Expand Up @@ -203,6 +220,7 @@ pub struct InternalValidator {
self_instance: Option<PyObject>,
recursion_guard: RecursionGuard,
validation_mode: InputType,
hide_input_in_errors: bool,
}

impl fmt::Debug for InternalValidator {
Expand All @@ -219,6 +237,7 @@ impl InternalValidator {
definitions: &[CombinedValidator],
extra: &Extra,
recursion_guard: &RecursionGuard,
hide_input_in_errors: bool,
) -> Self {
Self {
name: name.to_string(),
Expand All @@ -230,6 +249,7 @@ impl InternalValidator {
self_instance: extra.self_instance.map(|d| d.into_py(py)),
recursion_guard: recursion_guard.clone(),
validation_mode: extra.mode,
hide_input_in_errors,
}
}

Expand Down Expand Up @@ -261,7 +281,14 @@ impl InternalValidator {
&mut self.recursion_guard,
)
.map_err(|e| {
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
ValidationError::from_val_error(
py,
self.name.to_object(py),
ErrorMode::Python,
e,
outer_location,
self.hide_input_in_errors,
)
})
}

Expand All @@ -286,7 +313,14 @@ impl InternalValidator {
self.validator
.validate(py, input, &extra, &self.definitions, &mut self.recursion_guard)
.map_err(|e| {
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
ValidationError::from_val_error(
py,
self.name.to_object(py),
ErrorMode::Python,
e,
outer_location,
self.hide_input_in_errors,
)
})
}
}
Loading