Skip to content

Commit

Permalink
remove "unknown" exactness
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Nov 8, 2023
1 parent 4c6bfab commit 159a7ce
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 38 deletions.
10 changes: 8 additions & 2 deletions src/validators/frozenset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use pyo3::prelude::*;
use pyo3::types::{PyDict, PyFrozenSet};

use crate::errors::ValResult;
use crate::input::Input;
use crate::input::{GenericIterable, Input};
use crate::tools::SchemaDict;
use crate::validators::Exactness;

use super::list::min_length_check;
use super::set::set_build;
Expand Down Expand Up @@ -34,6 +35,12 @@ impl Validator for FrozenSetValidator {
state: &mut ValidationState,
) -> ValResult<'data, PyObject> {
let collection = input.validate_frozenset(state.strict_or(self.strict))?;
let exactness = match &collection {
GenericIterable::FrozenSet(_) => Exactness::Exact,
GenericIterable::Set(_) | GenericIterable::JsonArray(_) => Exactness::Strict,
_ => Exactness::Lax,
};
state.floor_exactness(exactness);
let f_set = PyFrozenSet::empty(py)?;
collection.validate_to_set(
py,
Expand All @@ -45,7 +52,6 @@ impl Validator for FrozenSetValidator {
state,
)?;
min_length_check!(input, "Frozenset", self.min_length, f_set);
state.set_exactness_unknown();
Ok(f_set.into_py(py))
}

Expand Down
8 changes: 7 additions & 1 deletion src/validators/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pyo3::types::PyDict;
use crate::errors::ValResult;
use crate::input::{GenericIterable, Input};
use crate::tools::SchemaDict;
use crate::validators::Exactness;

use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};

Expand Down Expand Up @@ -122,6 +123,12 @@ impl Validator for ListValidator {
state: &mut ValidationState,
) -> ValResult<'data, PyObject> {
let seq = input.validate_list(state.strict_or(self.strict))?;
let exactness = match &seq {
GenericIterable::List(_) | GenericIterable::JsonArray(_) => Exactness::Exact,
GenericIterable::Tuple(_) => Exactness::Strict,
_ => Exactness::Lax,
};
state.floor_exactness(exactness);

let output = match self.item_validator {
Some(ref v) => seq.validate_to_vec(py, input, self.max_length, "List", v, state)?,
Expand All @@ -135,7 +142,6 @@ impl Validator for ListValidator {
},
};
min_length_check!(input, "List", self.min_length, output);
state.set_exactness_unknown();
Ok(output.into_py(py))
}

Expand Down
10 changes: 8 additions & 2 deletions src/validators/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use pyo3::prelude::*;
use pyo3::types::{PyDict, PySet};

use crate::errors::ValResult;
use crate::input::Input;
use crate::input::{GenericIterable, Input};
use crate::tools::SchemaDict;
use crate::validators::Exactness;

use super::list::min_length_check;
use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
Expand Down Expand Up @@ -64,10 +65,15 @@ impl Validator for SetValidator {
state: &mut ValidationState,
) -> ValResult<'data, PyObject> {
let collection = input.validate_set(state.strict_or(self.strict))?;
let exactness = match &collection {
GenericIterable::Set(_) => Exactness::Exact,
GenericIterable::FrozenSet(_) | GenericIterable::JsonArray(_) => Exactness::Strict,
_ => Exactness::Lax,
};
state.floor_exactness(exactness);
let set = PySet::empty(py)?;
collection.validate_to_set(py, set, input, self.max_length, "Set", &self.item_validator, state)?;
min_length_check!(input, "Set", self.min_length, set);
state.set_exactness_unknown();
Ok(set.into_py(py))
}

Expand Down
16 changes: 14 additions & 2 deletions src/validators/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::build_tools::is_strict;
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValResult};
use crate::input::{GenericIterable, Input};
use crate::tools::SchemaDict;
use crate::validators::Exactness;

use super::list::{get_items_schema, min_length_check};
use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
Expand Down Expand Up @@ -51,13 +52,18 @@ impl Validator for TupleVariableValidator {
state: &mut ValidationState,
) -> ValResult<'data, PyObject> {
let seq = input.validate_tuple(state.strict_or(self.strict))?;
let exactness = match &seq {
GenericIterable::Tuple(_) | GenericIterable::JsonArray(_) => Exactness::Exact,
GenericIterable::List(_) => Exactness::Strict,
_ => Exactness::Lax,
};
state.floor_exactness(exactness);

let output = match self.item_validator {
Some(ref v) => seq.validate_to_vec(py, input, self.max_length, "Tuple", v, state)?,
None => seq.to_vec(py, input, "Tuple", self.max_length)?,
};
min_length_check!(input, "Tuple", self.min_length, output);
state.set_exactness_unknown();
Ok(PyTuple::new(py, &output).into_py(py))
}

Expand Down Expand Up @@ -182,6 +188,13 @@ impl Validator for TuplePositionalValidator {
state: &mut ValidationState,
) -> ValResult<'data, PyObject> {
let collection = input.validate_tuple(state.strict_or(self.strict))?;
let exactness: crate::validators::Exactness = match &collection {
GenericIterable::Tuple(_) | GenericIterable::JsonArray(_) => Exactness::Exact,
GenericIterable::List(_) => Exactness::Strict,
_ => Exactness::Lax,
};
state.floor_exactness(exactness);

let actual_length = collection.generic_len();
let expected_length = if self.extras_validator.is_some() {
actual_length.unwrap_or(self.items_validators.len())
Expand Down Expand Up @@ -215,7 +228,6 @@ impl Validator for TuplePositionalValidator {
other => iter!(other.as_sequence_iterator(py)?),
}
if errors.is_empty() {
state.set_exactness_unknown();
Ok(PyTuple::new(py, &output).into_py(py))
} else {
Err(ValError::LineErrors(errors))
Expand Down
27 changes: 4 additions & 23 deletions src/validators/union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ impl UnionValidator {
}
}
} else {
// one-pass lax validation; for validators which have unknown exactness need to
// revalidate in strict mode
// one-pass lax validation
for (choice, label) in &self.choices {
state.exactness = Some(Exactness::Exact);
let result = choice.validate(py, input, state);
Expand All @@ -155,27 +154,9 @@ impl UnionValidator {
strict_success = Some(success);
}
}
Some(Exactness::Lax) => {
if lax_success.is_none() {
lax_success = Some(success);
}
}
None => {
// unknown match during lax validation, try requiring a strict
// validation, but only if we haven't already had a strict match
if strict_success.is_none() {
state.exactness = Some(Exactness::Exact);
let state = &mut state.rebind_extra(|extra| extra.strict = Some(true));
let res_strict = choice.validate(py, input, state);

if let Ok(success_strict) = res_strict {
// strict validation should never succeed with "lax" result
debug_assert_ne!(state.exactness, Some(Exactness::Lax));
strict_success = Some(success_strict);
}
}

// record this as a lax match irrespective of the above
_ => {
// success should always have an exactness
debug_assert_eq!(state.exactness, Some(Exactness::Lax));
if lax_success.is_none() {
lax_success = Some(success);
}
Expand Down
8 changes: 0 additions & 8 deletions src/validators/validation_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,6 @@ impl<'a> ValidationState<'a> {
self.extra.strict.unwrap_or(default)
}

/// Sets the exactness of this state to unknown.
///
/// In general this de-optimizes union validation by forcing strict & lax validation passes,
/// so it's better to determine exactness and call `floor_exactness` when possible.
pub fn set_exactness_unknown(&mut self) {
self.exactness = None;
}

/// Sets the exactness to the lower of the current exactness
/// and the given exactness.
///
Expand Down

0 comments on commit 159a7ce

Please sign in to comment.