From c655030527efd5a7394201d83143a78141969613 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 2 May 2023 23:14:39 -0500 Subject: [PATCH] Rework def ref system to fix various bugs (#571) --- src/build_context.rs | 212 ------------------ src/definitions.rs | 113 ++++++++++ src/input/return_enums.rs | 18 +- src/lib.rs | 2 +- src/recursion_guard.rs | 23 +- src/serializers/extra.rs | 27 ++- src/serializers/infer.rs | 35 ++- src/serializers/mod.rs | 22 +- src/serializers/shared.rs | 62 ++--- src/serializers/type_serializers/any.rs | 4 +- src/serializers/type_serializers/bytes.rs | 4 +- src/serializers/type_serializers/dataclass.rs | 10 +- .../type_serializers/datetime_etc.rs | 4 +- .../type_serializers/definitions.rs | 39 ++-- src/serializers/type_serializers/dict.rs | 12 +- src/serializers/type_serializers/format.rs | 8 +- src/serializers/type_serializers/function.rs | 26 +-- src/serializers/type_serializers/generator.rs | 8 +- src/serializers/type_serializers/json.rs | 8 +- src/serializers/type_serializers/list.rs | 8 +- src/serializers/type_serializers/literal.rs | 4 +- src/serializers/type_serializers/model.rs | 10 +- src/serializers/type_serializers/nullable.rs | 6 +- src/serializers/type_serializers/other.rs | 26 +-- .../type_serializers/set_frozenset.rs | 8 +- src/serializers/type_serializers/simple.rs | 6 +- src/serializers/type_serializers/string.rs | 4 +- src/serializers/type_serializers/timedelta.rs | 4 +- src/serializers/type_serializers/tuple.rs | 18 +- .../type_serializers/typed_dict.rs | 6 +- src/serializers/type_serializers/union.rs | 10 +- src/serializers/type_serializers/url.rs | 4 +- .../type_serializers/with_default.rs | 6 +- src/validators/any.rs | 10 +- src/validators/arguments.rs | 34 +-- src/validators/bool.rs | 10 +- src/validators/bytes.rs | 16 +- src/validators/call.rs | 26 +-- src/validators/callable.rs | 10 +- src/validators/chain.rs | 24 +- src/validators/custom_error.rs | 22 +- src/validators/dataclass.rs | 70 +++--- src/validators/date.rs | 10 +- src/validators/datetime.rs | 10 +- src/validators/definitions.rs | 126 ++++++++--- src/validators/dict.rs | 40 ++-- src/validators/float.rs | 22 +- src/validators/frozenset.rs | 14 +- src/validators/function.rs | 60 +++-- src/validators/generator.rs | 28 +-- src/validators/int.rs | 16 +- src/validators/is_instance.rs | 10 +- src/validators/is_subclass.rs | 10 +- src/validators/json.rs | 18 +- src/validators/lax_or_strict.rs | 26 ++- src/validators/list.rs | 22 +- src/validators/literal.rs | 10 +- src/validators/mod.rs | 101 ++++----- src/validators/model.rs | 44 ++-- src/validators/model_fields.rs | 32 +-- src/validators/none.rs | 10 +- src/validators/nullable.rs | 18 +- src/validators/set.rs | 18 +- src/validators/string.rs | 16 +- src/validators/time.rs | 10 +- src/validators/timedelta.rs | 10 +- src/validators/tuple.rs | 44 ++-- src/validators/typed_dict.rs | 26 +-- src/validators/union.rs | 65 +++--- src/validators/url.rs | 18 +- src/validators/with_default.rs | 24 +- tests/serializers/test_any.py | 4 +- tests/serializers/test_definitions.py | 4 - tests/serializers/test_other.py | 10 +- tests/test_build.py | 40 ++++ tests/validators/test_bool.py | 4 +- tests/validators/test_definitions.py | 10 +- .../validators/test_definitions_recursive.py | 122 +++++++++- tests/validators/test_float.py | 4 +- tests/validators/test_frozenset.py | 2 +- tests/validators/test_int.py | 4 +- tests/validators/test_string.py | 2 +- tests/validators/test_union.py | 2 +- 83 files changed, 1086 insertions(+), 959 deletions(-) delete mode 100644 src/build_context.rs create mode 100644 src/definitions.rs diff --git a/src/build_context.rs b/src/build_context.rs deleted file mode 100644 index 2b78a0d23..000000000 --- a/src/build_context.rs +++ /dev/null @@ -1,212 +0,0 @@ -use pyo3::intern; -use pyo3::prelude::*; -use pyo3::types::{PyDict, PyList}; - -use ahash::{AHashMap, AHashSet}; - -use crate::build_tools::{py_err, py_error_type, SchemaDict}; -use crate::serializers::CombinedSerializer; -use crate::validators::{CombinedValidator, Validator}; - -#[derive(Clone, Debug)] -struct Slot { - slot_ref: String, - op_val_ser: Option, -} - -pub enum ThingOrId { - Thing(T), - Id(usize), -} - -/// `BuildContext` is used to store extra information while building validators and type_serializers -#[derive(Clone, Debug)] -pub struct BuildContext { - /// set of used refs, useful to see if a `ref` is actually used elsewhere in the schema - used_refs: AHashSet, - /// holds validators/type_serializers which reference themselves and therefore can't be cloned and owned - /// in one or multiple places. - slots: Vec>, - /// holds validators/type_serializers which need to be accessed from multiple other validators/type_serializers - /// and therefore can't be owned by them directly. - reusable: AHashMap, -} - -impl BuildContext { - pub fn new(schema: &PyAny) -> PyResult { - let mut used_refs = AHashSet::new(); - extract_used_refs(schema, &mut used_refs)?; - Ok(Self { - used_refs, - slots: Vec::new(), - reusable: AHashMap::new(), - }) - } - - pub fn for_self_schema() -> Self { - let mut used_refs = AHashSet::with_capacity(3); - // NOTE: we don't call `extract_used_refs` for performance reasons, if more recursive references - // are used, they would need to be manually added here. - // we use `2` as count to avoid `find_slot` pulling the validator out of slots and returning it directly - used_refs.insert("root-schema".to_string()); - used_refs.insert("ser-schema".to_string()); - used_refs.insert("inc-ex-type".to_string()); - Self { - used_refs, - slots: Vec::new(), - reusable: AHashMap::new(), - } - } - - /// Check whether a ref is already in `reusable` or `slots`, we shouldn't allow repeated refs - pub fn ref_already_used(&self, ref_: &str) -> bool { - self.reusable.contains_key(ref_) || self.slots.iter().any(|slot| slot.slot_ref == ref_) - } - - /// check if a ref is used elsewhere in the schema - pub fn ref_used(&self, ref_: &str) -> bool { - self.used_refs.contains(ref_) - } - - /// check if a ref is used within a given schema - pub fn ref_used_within(&self, schema_dict: &PyAny, ref_: &str) -> PyResult { - check_ref_used(schema_dict, ref_) - } - - /// add a validator/serializer to `reusable` so it can be cloned and used again elsewhere - pub fn store_reusable(&mut self, ref_: String, val_ser: T) { - self.reusable.insert(ref_, val_ser); - } - - /// First of two part process to add a new validator/serializer slot, we add the `slot_ref` to the array, - /// but not the actual `validator`/`serializer`, we can't add that until it's build. - /// But we need the `id` to build it, hence this two-step process. - pub fn prepare_slot(&mut self, slot_ref: String) -> PyResult { - let id = self.slots.len(); - let slot = Slot { - slot_ref, - op_val_ser: None, - }; - self.slots.push(slot); - Ok(id) - } - - /// Second part of adding a validator/serializer - we update the slot to include a validator - pub fn complete_slot(&mut self, slot_id: usize, val_ser: T) -> PyResult<()> { - match self.slots.get(slot_id) { - Some(slot) => { - self.slots[slot_id] = Slot { - slot_ref: slot.slot_ref.clone(), - op_val_ser: Some(val_ser), - }; - Ok(()) - } - None => py_err!("Slots Error: slot {} not found", slot_id), - } - } - - /// find validator/serializer by `ref`, if the `ref` is in `resuable` return a clone of the validator/serializer, - /// otherwise return the id of the slot. - pub fn find(&mut self, ref_: &str) -> PyResult> { - if let Some(val_ser) = self.reusable.get(ref_) { - Ok(ThingOrId::Thing(val_ser.clone())) - } else { - let id = match self.slots.iter().position(|slot| slot.slot_ref == ref_) { - Some(id) => id, - None => return py_err!("Slots Error: ref '{}' not found", ref_), - }; - Ok(ThingOrId::Id(id)) - } - } - - /// find a validator/serializer by `slot_id` - this used in `Validator.complete`, - /// specifically `DefinitionRefValidator` to set its name - pub fn find_validator(&self, slot_id: usize) -> PyResult<&T> { - match self.slots.get(slot_id) { - Some(slot) => match slot.op_val_ser { - Some(ref validator) => Ok(validator), - None => py_err!("Slots Error: slot {} not yet filled", slot_id), - }, - None => py_err!("Slots Error: slot {} not found", slot_id), - } - } -} - -impl BuildContext { - /// Move validators into a new vec which maintains the order of slots, `complete` is called on each validator - /// at the same time. - pub fn into_slots_val(self) -> PyResult> { - let self_clone = self.clone(); - self.slots - .into_iter() - .map(|slot| match slot.op_val_ser { - Some(mut validator) => { - validator.complete(&self_clone)?; - Ok(validator) - } - None => py_err!("Slots Error: slot not yet filled"), - }) - .collect() - } -} - -impl BuildContext { - /// Move validators into a new vec which maintains the order of slots - pub fn into_slots_ser(self) -> PyResult> { - self.slots - .into_iter() - .map(|slot| { - slot.op_val_ser - .ok_or_else(|| py_error_type!("Slots Error: slot not yet filled")) - }) - .collect() - } -} - -fn extract_used_refs(schema: &PyAny, refs: &mut AHashSet) -> PyResult<()> { - if let Ok(dict) = schema.downcast::() { - if is_definition_ref(dict)? { - refs.insert(dict.get_as_req(intern!(schema.py(), "schema_ref"))?); - } else { - for (key, value) in dict.iter() { - if !key.eq(intern!(schema.py(), "metadata"))? { - extract_used_refs(value, refs)?; - } - } - } - } else if let Ok(list) = schema.downcast::() { - for item in list.iter() { - extract_used_refs(item, refs)?; - } - } - Ok(()) -} - -fn check_ref_used(schema: &PyAny, ref_: &str) -> PyResult { - if let Ok(dict) = schema.downcast::() { - if is_definition_ref(dict)? { - let value: &str = dict.get_as_req(intern!(schema.py(), "schema_ref"))?; - return Ok(value == ref_); - } else { - for (key, value) in dict.iter() { - if !key.eq(intern!(schema.py(), "metadata"))? && check_ref_used(value, ref_)? { - return Ok(true); - } - } - } - } else if let Ok(list) = schema.downcast::() { - for item in list.iter() { - if check_ref_used(item, ref_)? { - return Ok(true); - } - } - } - Ok(false) -} - -fn is_definition_ref(dict: &PyDict) -> PyResult { - match dict.get_item(intern!(dict.py(), "type")) { - Some(type_value) => type_value.eq(intern!(dict.py(), "definition-ref")), - None => Ok(false), - } -} diff --git a/src/definitions.rs b/src/definitions.rs new file mode 100644 index 000000000..d71e4ddd2 --- /dev/null +++ b/src/definitions.rs @@ -0,0 +1,113 @@ +/// Definition / reference management +/// Our definitions system is very similar to json schema's: there's ref strings and a definitions section +/// Unlike json schema we let you put definitions inline, not just in a single '#/$defs/' block or similar. +/// We use DefinitionsBuilder to collect the references / definitions into a single vector +/// and then get a definition from a reference using an integer id (just for performance of not using a HashMap) +use std::collections::hash_map::Entry; + +use pyo3::prelude::*; + +use ahash::AHashMap; + +use crate::build_tools::py_err; + +// An integer id for the reference +pub type ReferenceId = usize; + +/// Definitions are validators and serializers that are +/// shared by reference. +/// They come into play whenever there is recursion, e.g. +/// if you have validators A -> B -> A then A will be shared +/// by reference so that the SchemaValidator itself can own it. +/// These primarily get used by DefinitionRefValidator and DefinitionRefSerializer, +/// other validators / serializers primarily pass them around without interacting with them. +/// They get indexed by a ReferenceId, which are integer identifiers +/// that are handed out and managed by DefinitionsBuilder when the Schema{Validator,Serializer} +/// gets build. +pub type Definitions = [T]; + +#[derive(Clone, Debug)] +struct Definition { + pub id: ReferenceId, + pub value: Option, +} + +#[derive(Clone, Debug)] +pub struct DefinitionsBuilder { + definitions: AHashMap>, +} + +impl DefinitionsBuilder { + pub fn new() -> Self { + Self { + definitions: AHashMap::new(), + } + } + + /// Get a ReferenceId for the given reference string. + // This ReferenceId can later be used to retrieve a definition + pub fn get_reference_id(&mut self, reference: &str) -> ReferenceId { + let next_id = self.definitions.len(); + // We either need a String copy or two hashmap lookups + // Neither is better than the other + // We opted for the easier outward facing API + match self.definitions.entry(reference.to_string()) { + Entry::Occupied(entry) => entry.get().id, + Entry::Vacant(entry) => { + entry.insert(Definition { + id: next_id, + value: None, + }); + next_id + } + } + } + + /// Add a definition, returning the ReferenceId that maps to it + pub fn add_definition(&mut self, reference: String, value: T) -> PyResult { + let next_id = self.definitions.len(); + match self.definitions.entry(reference.clone()) { + Entry::Occupied(mut entry) => match entry.get_mut().value.replace(value) { + Some(_) => py_err!("Duplicate ref: `{}`", reference), + None => Ok(entry.get().id), + }, + Entry::Vacant(entry) => { + entry.insert(Definition { + id: next_id, + value: Some(value), + }); + Ok(next_id) + } + } + } + + /// Retrieve an item definition using a ReferenceId + /// Will raise an error if the definition for that reference does not yet exist + pub fn get_definition(&self, reference_id: ReferenceId) -> PyResult<&T> { + let (reference, def) = match self.definitions.iter().find(|(_, def)| def.id == reference_id) { + Some(v) => v, + None => return py_err!("Definitions error: no definition for ReferenceId `{}`", reference_id), + }; + match def.value.as_ref() { + Some(v) => Ok(v), + None => py_err!( + "Definitions error: attempted to use `{}` before it was filled", + reference + ), + } + } + + /// Consume this Definitions into a vector of items, indexed by each items ReferenceId + pub fn finish(self) -> PyResult> { + // We need to create a vec of defs according to the order in their ids + let mut defs: Vec<(usize, T)> = Vec::new(); + for (reference, def) in self.definitions.into_iter() { + match def.value { + None => return py_err!("Definitions error: definition {} was never filled", reference), + Some(v) => defs.push((def.id, v)), + } + } + defs.sort_by_key(|(id, _)| *id); + Ok(defs.into_iter().map(|(_, v)| v).collect()) + } +} diff --git a/src/input/return_enums.rs b/src/input/return_enums.rs index 912955a58..f8741a7d4 100644 --- a/src/input/return_enums.rs +++ b/src/input/return_enums.rs @@ -54,13 +54,13 @@ fn validate_iter_to_vec<'a, 's>( capacity: usize, validator: &'s CombinedValidator, extra: &Extra, - slots: &'a [CombinedValidator], + definitions: &'a [CombinedValidator], recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'a, Vec> { let mut output: Vec = Vec::with_capacity(capacity); let mut errors: Vec = Vec::new(); for (index, item) in iter.enumerate() { - match validator.validate(py, item, extra, slots, recursion_guard) { + match validator.validate(py, item, extra, definitions, recursion_guard) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend(line_errors.into_iter().map(|err| err.with_outer_location(index.into()))); @@ -131,7 +131,7 @@ impl<'a> GenericCollection<'a> { generator_max_length: Option, validator: &'s CombinedValidator, extra: &Extra, - slots: &'a [CombinedValidator], + definitions: &'a [CombinedValidator], recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'a, Vec> { let capacity = self @@ -144,7 +144,7 @@ impl<'a> GenericCollection<'a> { capacity, validator, extra, - slots, + definitions, recursion_guard, ), Self::Tuple(collection) => validate_iter_to_vec( @@ -153,7 +153,7 @@ impl<'a> GenericCollection<'a> { capacity, validator, extra, - slots, + definitions, recursion_guard, ), Self::Set(collection) => validate_iter_to_vec( @@ -162,7 +162,7 @@ impl<'a> GenericCollection<'a> { capacity, validator, extra, - slots, + definitions, recursion_guard, ), Self::FrozenSet(collection) => validate_iter_to_vec( @@ -171,7 +171,7 @@ impl<'a> GenericCollection<'a> { capacity, validator, extra, - slots, + definitions, recursion_guard, ), Self::PyAny(collection) => { @@ -180,7 +180,7 @@ impl<'a> GenericCollection<'a> { let mut errors: Vec = Vec::new(); for (index, item_result) in iter.enumerate() { let item = item_result.map_err(|e| any_next_error!(collection.py(), e, input, index))?; - match validator.validate(py, item, extra, slots, recursion_guard) { + match validator.validate(py, item, extra, definitions, recursion_guard) { Ok(item) => { generator_too_long!(input, index, generator_max_length, field_type); output.push(item); @@ -206,7 +206,7 @@ impl<'a> GenericCollection<'a> { capacity, validator, extra, - slots, + definitions, recursion_guard, ), } diff --git a/src/lib.rs b/src/lib.rs index ae90c8e66..cb70e925d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ use pyo3::prelude::*; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod argument_markers; -mod build_context; mod build_tools; +mod definitions; mod errors; mod input; mod lazy_index_map; diff --git a/src/recursion_guard.rs b/src/recursion_guard.rs index 4ecfc5837..2e8151727 100644 --- a/src/recursion_guard.rs +++ b/src/recursion_guard.rs @@ -1,10 +1,19 @@ use ahash::AHashSet; +type RecursionKey = ( + // Identifier for the input object, e.g. the id() of a Python dict + usize, + // Identifier for the node we are traversing, e.g. the validator's id + // Generally only things that can be traversed multiple times, like a definition reference + // need to use the recursion guard, and those things should already have a natural node id + usize, +); + /// This is used to avoid cyclic references in input data causing recursive validation and a nasty segmentation fault. /// It's used in `validators/definition` to detect when a reference is reused within itself. #[derive(Debug, Clone, Default)] pub struct RecursionGuard { - ids: Option>, + ids: Option>, // see validators/definition::BACKUP_GUARD_LIMIT for details // depth could be a hashmap {validator_id => depth} but for simplicity and performance it's easier to just // use one number for all validators @@ -13,14 +22,14 @@ pub struct RecursionGuard { impl RecursionGuard { // insert a new id into the set, return whether the set already had the id in it - pub fn contains_or_insert(&mut self, id: usize) -> bool { + pub fn contains_or_insert(&mut self, obj_id: usize, node_id: usize) -> bool { match self.ids { // https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.insert // "If the set did not have this value present, `true` is returned." - Some(ref mut set) => !set.insert(id), + Some(ref mut set) => !set.insert((obj_id, node_id)), None => { - let mut set: AHashSet = AHashSet::with_capacity(10); - set.insert(id); + let mut set: AHashSet = AHashSet::with_capacity(10); + set.insert((obj_id, node_id)); self.ids = Some(set); false } @@ -37,10 +46,10 @@ impl RecursionGuard { self.depth -= 1; } - pub fn remove(&mut self, id: &usize) { + pub fn remove(&mut self, obj_id: usize, node_id: usize) { match self.ids { Some(ref mut set) => { - set.remove(id); + set.remove(&(obj_id, node_id)); } None => unreachable!(), }; diff --git a/src/serializers/extra.rs b/src/serializers/extra.rs index f4195de7e..a293c3119 100644 --- a/src/serializers/extra.rs +++ b/src/serializers/extra.rs @@ -12,6 +12,7 @@ use super::config::SerializationConfig; use super::errors::{PydanticSerializationUnexpectedValue, UNEXPECTED_TYPE_SER_MARKER}; use super::ob_type::ObTypeLookup; use super::shared::CombinedSerializer; +use crate::definitions::Definitions; /// this is ugly, would be much better if extra could be stored in `SerializationState` /// then `SerializationState` got a `serialize_infer` method, but I couldn't get it to work @@ -71,7 +72,7 @@ impl SerializationState { #[cfg_attr(debug_assertions, derive(Debug))] pub(crate) struct Extra<'a> { pub mode: &'a SerMode, - pub slots: &'a [CombinedSerializer], + pub definitions: &'a Definitions, pub ob_type_lookup: &'a ObTypeLookup, pub warnings: &'a CollectWarnings, pub by_alias: bool, @@ -97,7 +98,7 @@ impl<'a> Extra<'a> { pub fn new( py: Python<'a>, mode: &'a SerMode, - slots: &'a [CombinedSerializer], + definitions: &'a Definitions, by_alias: bool, warnings: &'a CollectWarnings, exclude_unset: bool, @@ -111,7 +112,7 @@ impl<'a> Extra<'a> { ) -> Self { Self { mode, - slots, + definitions, ob_type_lookup: ObTypeLookup::cached(py), warnings, by_alias, @@ -155,7 +156,7 @@ impl SerCheck { #[cfg_attr(debug_assertions, derive(Debug))] pub(crate) struct ExtraOwned { mode: SerMode, - slots: Vec, + definitions: Vec, warnings: CollectWarnings, by_alias: bool, exclude_unset: bool, @@ -175,7 +176,7 @@ impl ExtraOwned { pub fn new(extra: &Extra) -> Self { Self { mode: extra.mode.clone(), - slots: extra.slots.to_vec(), + definitions: extra.definitions.to_vec(), warnings: extra.warnings.clone(), by_alias: extra.by_alias, exclude_unset: extra.exclude_unset, @@ -195,7 +196,7 @@ impl ExtraOwned { pub fn to_extra<'py>(&'py self, py: Python<'py>) -> Extra<'py> { Extra { mode: &self.mode, - slots: &self.slots, + definitions: &self.definitions, ob_type_lookup: ObTypeLookup::cached(py), warnings: &self.warnings, by_alias: self.by_alias, @@ -267,6 +268,10 @@ pub(crate) struct CollectWarnings { } impl CollectWarnings { + pub(crate) fn is_active(&self) -> bool { + self.active + } + pub(crate) fn new(active: bool) -> Self { Self { active, @@ -351,7 +356,7 @@ impl CollectWarnings { #[derive(Default, Clone)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct RecursionInfo { - ids: AHashSet, + ids: AHashSet<(usize, usize)>, // first element is the object's id, the second is the serializer's id /// as with `src/recursion_guard.rs` this is used as a backup in case the identity check recursion guard fails /// see #143 depth: u16, @@ -366,12 +371,12 @@ pub struct SerRecursionGuard { impl SerRecursionGuard { const MAX_DEPTH: u16 = 200; - pub fn add(&self, value: &PyAny) -> PyResult { + pub fn add(&self, value: &PyAny, def_ref_id: usize) -> PyResult { // https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.insert // "If the set did not have this value present, `true` is returned." let id = value.as_ptr() as usize; let mut info = self.info.borrow_mut(); - if !info.ids.insert(id) { + if !info.ids.insert((id, def_ref_id)) { Err(PyValueError::new_err("Circular reference detected (id repeated)")) } else if info.depth > Self::MAX_DEPTH { Err(PyValueError::new_err("Circular reference detected (depth exceeded)")) @@ -381,9 +386,9 @@ impl SerRecursionGuard { } } - pub fn pop(&self, id: usize) { + pub fn pop(&self, id: usize, def_ref_id: usize) { let mut info = self.info.borrow_mut(); info.depth -= 1; - info.ids.remove(&id); + info.ids.remove(&(id, def_ref_id)); } } diff --git a/src/serializers/infer.rs b/src/serializers/infer.rs index ddd0facc0..83678453c 100644 --- a/src/serializers/infer.rs +++ b/src/serializers/infer.rs @@ -14,7 +14,7 @@ use crate::build_tools::{py_err, safe_repr}; use crate::serializers::errors::SERIALIZATION_ERR_MARKER; use crate::serializers::filter::SchemaFilter; use crate::serializers::shared::PydanticSerializer; -use crate::serializers::{shared::TypeSerializer, SchemaSerializer}; +use crate::serializers::SchemaSerializer; use crate::url::{PyMultiHostUrl, PyUrl}; use super::errors::{py_err_se_err, PydanticSerializationError}; @@ -32,6 +32,10 @@ pub(crate) fn infer_to_python( infer_to_python_known(&extra.ob_type_lookup.get_type(value), value, include, exclude, extra) } +// arbitrary ids to identify that we recursed through infer_to_{python,json}_known +// We just need them to be different from definition ref slot ids, which start at 0 +const INFER_DEF_REF_ID: usize = usize::MAX; + pub(crate) fn infer_to_python_known( ob_type: &ObType, value: &PyAny, @@ -40,7 +44,7 @@ pub(crate) fn infer_to_python_known( extra: &Extra, ) -> PyResult { let py = value.py(); - let value_id = match extra.rec_guard.add(value) { + let value_id = match extra.rec_guard.add(value, INFER_DEF_REF_ID) { Ok(id) => id, Err(e) => { return match extra.mode { @@ -96,7 +100,20 @@ pub(crate) fn infer_to_python_known( let serialize_with_serializer = |value: &PyAny, is_model: bool| { if let Ok(py_serializer) = value.getattr(intern!(py, "__pydantic_serializer__")) { if let Ok(serializer) = py_serializer.extract::() { - return serializer.serializer.to_python(value, include, exclude, extra); + return serializer.to_python( + py, + value, + extra.mode.to_object(py).extract(py)?, + include, + exclude, + extra.by_alias, + extra.exclude_unset, + extra.exclude_defaults, + extra.exclude_none, + extra.round_trip, + extra.warnings.is_active(), + extra.fallback, + ); } } // Fallback to dict serialization if `__pydantic_serializer__` is not set. @@ -199,7 +216,7 @@ pub(crate) fn infer_to_python_known( if let Some(fallback) = extra.fallback { let next_value = fallback.call1((value,))?; let next_result = infer_to_python(next_value, include, exclude, extra); - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, INFER_DEF_REF_ID); return next_result; } else if extra.serialize_unknown { serialize_unknown(value).into_py(py) @@ -257,7 +274,7 @@ pub(crate) fn infer_to_python_known( if let Some(fallback) = extra.fallback { let next_value = fallback.call1((value,))?; let next_result = infer_to_python(next_value, include, exclude, extra); - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, INFER_DEF_REF_ID); return next_result; } else { value.into_py(py) @@ -266,7 +283,7 @@ pub(crate) fn infer_to_python_known( _ => value.into_py(py), }, }; - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, INFER_DEF_REF_ID); Ok(value) } @@ -325,7 +342,7 @@ pub(crate) fn infer_serialize_known( exclude: Option<&PyAny>, extra: &Extra, ) -> Result { - let value_id = match extra.rec_guard.add(value).map_err(py_err_se_err) { + let value_id = match extra.rec_guard.add(value, INFER_DEF_REF_ID).map_err(py_err_se_err) { Ok(v) => v, Err(e) => { return if extra.serialize_unknown { @@ -487,7 +504,7 @@ pub(crate) fn infer_serialize_known( if let Some(fallback) = extra.fallback { let next_value = fallback.call1((value,)).map_err(py_err_se_err)?; let next_result = infer_serialize(next_value, serializer, include, exclude, extra); - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, INFER_DEF_REF_ID); return next_result; } else if extra.serialize_unknown { serializer.serialize_str(&serialize_unknown(value)) @@ -501,7 +518,7 @@ pub(crate) fn infer_serialize_known( } } }; - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, INFER_DEF_REF_ID); ser_result } diff --git a/src/serializers/mod.rs b/src/serializers/mod.rs index 740557c4c..36e68efac 100644 --- a/src/serializers/mod.rs +++ b/src/serializers/mod.rs @@ -4,7 +4,7 @@ use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict}; use pyo3::{PyTraverseError, PyVisit}; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use crate::validators::SelfValidator; use config::SerializationConfig; @@ -28,7 +28,7 @@ mod type_serializers; #[derive(Debug, Clone)] pub struct SchemaSerializer { serializer: CombinedSerializer, - slots: Vec, + definitions: Vec, json_size: usize, config: SerializationConfig, } @@ -39,12 +39,12 @@ impl SchemaSerializer { pub fn py_new(py: Python, schema: &PyDict, config: Option<&PyDict>) -> PyResult { let self_validator = SelfValidator::new(py)?; let schema = self_validator.validate_schema(py, schema)?; - let mut build_context = BuildContext::new(schema)?; + let mut definitions_builder = DefinitionsBuilder::new(); - let serializer = CombinedSerializer::build(schema.downcast()?, config, &mut build_context)?; + let serializer = CombinedSerializer::build(schema.downcast()?, config, &mut definitions_builder)?; Ok(Self { serializer, - slots: build_context.into_slots_ser()?, + definitions: definitions_builder.finish()?, json_size: 1024, config: SerializationConfig::from_config(config)?, }) @@ -75,7 +75,7 @@ impl SchemaSerializer { let extra = Extra::new( py, &mode, - &self.slots, + &self.definitions, by_alias, &warnings, exclude_unset, @@ -116,7 +116,7 @@ impl SchemaSerializer { let extra = Extra::new( py, &SerMode::Json, - &self.slots, + &self.definitions, by_alias, &warnings, exclude_unset, @@ -147,14 +147,14 @@ impl SchemaSerializer { pub fn __repr__(&self) -> String { format!( - "SchemaSerializer(serializer={:#?}, slots={:#?})", - self.serializer, self.slots + "SchemaSerializer(serializer={:#?}, definitions={:#?})", + self.serializer, self.definitions ) } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { self.serializer.py_gc_traverse(&visit)?; - for slot in self.slots.iter() { + for slot in self.definitions.iter() { slot.py_gc_traverse(&visit)?; } Ok(()) @@ -162,7 +162,7 @@ impl SchemaSerializer { fn __clear__(&mut self) { self.serializer.py_gc_clear(); - for slot in self.slots.iter_mut() { + for slot in self.definitions.iter_mut() { slot.py_gc_clear(); } } diff --git a/src/serializers/shared.rs b/src/serializers/shared.rs index 93d0d0a6f..bfe2c81b7 100644 --- a/src/serializers/shared.rs +++ b/src/serializers/shared.rs @@ -10,13 +10,14 @@ use enum_dispatch::enum_dispatch; use serde::Serialize; use serde_json::ser::PrettyFormatter; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, py_error_type, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use super::errors::se_err_py_err; use super::extra::Extra; use super::infer::infer_json_key; use super::ob_type::{IsType, ObType}; +use super::type_serializers::definitions::DefinitionRefSerializer; pub(crate) trait BuildSerializer: Sized { const EXPECTED_TYPE: &'static str; @@ -24,7 +25,7 @@ pub(crate) trait BuildSerializer: Sized { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult; } @@ -47,17 +48,17 @@ macro_rules! combined_serializer { lookup_type: &str, schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext + definitions: &mut DefinitionsBuilder ) -> PyResult { match lookup_type { $( - <$b_serializer>::EXPECTED_TYPE => match <$b_serializer>::build(schema, config, build_context) { + <$b_serializer>::EXPECTED_TYPE => match <$b_serializer>::build(schema, config, definitions) { Ok(serializer) => Ok(serializer), Err(err) => py_err!("Error building `{}` serializer:\n {}", lookup_type, err), }, )* $( - <$builder>::EXPECTED_TYPE => match <$builder>::build(schema, config, build_context) { + <$builder>::EXPECTED_TYPE => match <$builder>::build(schema, config, definitions) { Ok(serializer) => Ok(serializer), Err(err) => py_err!("Error building `{}` serializer:\n {}", lookup_type, err), }, @@ -91,7 +92,7 @@ combined_serializer! { super::type_serializers::other::IsInstanceBuilder; super::type_serializers::other::IsSubclassBuilder; super::type_serializers::other::CallableBuilder; - super::type_serializers::definitions::DefinitionsBuilder; + super::type_serializers::definitions::DefinitionsSerializerBuilder; super::type_serializers::dataclass::DataclassArgsBuilder; super::type_serializers::dataclass::DataclassBuilder; super::type_serializers::function::FunctionBeforeSerializerBuilder; @@ -140,7 +141,7 @@ impl CombinedSerializer { fn _build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let type_key = intern!(py, "type"); @@ -155,7 +156,7 @@ impl CombinedSerializer { return super::type_serializers::function::FunctionPlainSerializer::build( schema, config, - build_context, + definitions, ) .map_err(|err| py_error_type!("Error building `function-plain` serializer:\n {}", err)); } @@ -166,7 +167,7 @@ impl CombinedSerializer { return super::type_serializers::function::FunctionWrapSerializer::build( schema, config, - build_context, + definitions, ) .map_err(|err| py_error_type!("Error building `function-wrap` serializer:\n {}", err)); } @@ -177,7 +178,7 @@ impl CombinedSerializer { Some(ser_type) => { // otherwise if `schema.serialization.type` is defined, use that with `find_serializer` // instead of `schema.type`. In this case it's an error if a serializer isn't found. - return Self::find_serializer(ser_type, ser_schema, config, build_context); + return Self::find_serializer(ser_type, ser_schema, config, definitions); } // if `schema.serialization.type` is None, fall back to `schema.type` None => (), @@ -185,7 +186,7 @@ impl CombinedSerializer { } let type_: &str = schema.get_as_req(type_key)?; - Self::find_serializer(type_, schema, config, build_context) + Self::find_serializer(type_, schema, config, definitions) } } @@ -196,41 +197,16 @@ impl BuildSerializer for CombinedSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - if let Some(schema_ref) = schema.get_as::(intern!(schema.py(), "ref"))? { - // as with validators, if there's a ref, - // we **might** want to store the serializer in slots and return a DefinitionRefSerializer: - // * if the ref isn't used at all, we just want to return a normal serializer, and ignore the ref completely - // * if the ref is used inside itself, we have to store the serializer in slots, - // and return a DefinitionRefSerializer - two step process with `prepare_slot` and `complete_slot` - // * if the ref is used elsewhere, we want to clone it each time it's used - if build_context.ref_used(&schema_ref) { - // the ref is used somewhere - // check the ref is unique - if build_context.ref_already_used(&schema_ref) { - return py_err!("Duplicate ref: `{}`", schema_ref); - } - - return if build_context.ref_used_within(schema, &schema_ref)? { - // the ref is used within itself, so we have to store the serializer in slots - // and return a DefinitionRefSerializer - let slot_id = build_context.prepare_slot(schema_ref)?; - let inner_ser = Self::_build(schema, config, build_context)?; - build_context.complete_slot(slot_id, inner_ser)?; - Ok(super::type_serializers::definitions::DefinitionRefSerializer::from_id( - slot_id, - )) - } else { - // the ref is used elsewhere, so we want to clone it each time it's used - let serializer = Self::_build(schema, config, build_context)?; - build_context.store_reusable(schema_ref, serializer.clone()); - Ok(serializer) - }; - } + let py: Python = schema.py(); + if let Some(schema_ref) = schema.get_as::(intern!(py, "ref"))? { + let inner_ser = Self::_build(schema, config, definitions)?; + let ser_id = definitions.add_definition(schema_ref, inner_ser)?; + return Ok(DefinitionRefSerializer::from_id(ser_id)); } - Self::_build(schema, config, build_context) + Self::_build(schema, config, definitions) } } diff --git a/src/serializers/type_serializers/any.rs b/src/serializers/type_serializers/any.rs index 06d96ab4b..6e5bc32c3 100644 --- a/src/serializers/type_serializers/any.rs +++ b/src/serializers/type_serializers/any.rs @@ -5,7 +5,7 @@ use pyo3::types::PyDict; use serde::ser::Serializer; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, TypeSerializer, @@ -20,7 +20,7 @@ impl BuildSerializer for AnySerializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/bytes.rs b/src/serializers/type_serializers/bytes.rs index 905fc1a64..e69d897f1 100644 --- a/src/serializers/type_serializers/bytes.rs +++ b/src/serializers/type_serializers/bytes.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict}; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, SerMode, @@ -19,7 +19,7 @@ impl BuildSerializer for BytesSerializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/dataclass.rs b/src/serializers/type_serializers/dataclass.rs index fa581d996..44d5006a6 100644 --- a/src/serializers/type_serializers/dataclass.rs +++ b/src/serializers/type_serializers/dataclass.rs @@ -4,8 +4,8 @@ use pyo3::types::{PyDict, PyList, PyString}; use ahash::AHashMap; -use crate::build_context::BuildContext; use crate::build_tools::{py_error_type, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use super::model::ModelSerializer; use super::typed_dict::{TypedDictField, TypedDictSerializer}; @@ -19,7 +19,7 @@ impl BuildSerializer for DataclassArgsBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -37,7 +37,7 @@ impl BuildSerializer for DataclassArgsBuilder { exclude.push(key_py.clone_ref(py)); } else { let schema = field_info.get_as_req(intern!(py, "schema"))?; - let serializer = CombinedSerializer::build(schema, config, build_context) + let serializer = CombinedSerializer::build(schema, config, definitions) .map_err(|e| py_error_type!("Field `{}`:\n {}", index, e))?; let alias = field_info.get_as(intern!(py, "serialization_alias"))?; @@ -60,8 +60,8 @@ impl BuildSerializer for DataclassBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - ModelSerializer::build(schema, config, build_context) + ModelSerializer::build(schema, config, definitions) } } diff --git a/src/serializers/type_serializers/datetime_etc.rs b/src/serializers/type_serializers/datetime_etc.rs index 0cbfce0cf..b2d9239f1 100644 --- a/src/serializers/type_serializers/datetime_etc.rs +++ b/src/serializers/type_serializers/datetime_etc.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pyo3::prelude::*; use pyo3::types::{PyDate, PyDateTime, PyDict, PyTime}; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use crate::input::{pydate_as_date, pydatetime_as_datetime, pytime_as_time}; use super::{ @@ -37,7 +37,7 @@ macro_rules! build_serializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/definitions.rs b/src/serializers/type_serializers/definitions.rs index 904e0cf52..82f1fa758 100644 --- a/src/serializers/type_serializers/definitions.rs +++ b/src/serializers/type_serializers/definitions.rs @@ -4,33 +4,33 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; -use crate::build_context::{BuildContext, ThingOrId}; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::{py_err_se_err, BuildSerializer, CombinedSerializer, Extra, TypeSerializer}; #[derive(Debug, Clone)] -pub struct DefinitionsBuilder; +pub struct DefinitionsSerializerBuilder; -impl BuildSerializer for DefinitionsBuilder { +impl BuildSerializer for DefinitionsSerializerBuilder { const EXPECTED_TYPE: &'static str = "definitions"; fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let definitions: &PyList = schema.get_as_req(intern!(py, "definitions"))?; + let schema_definitions: &PyList = schema.get_as_req(intern!(py, "definitions"))?; - for def_schema in definitions { - CombinedSerializer::build(def_schema.downcast()?, config, build_context)?; - // no need to store the serializer here, it has already been stored in build_context if necessary + for schema_def in schema_definitions { + CombinedSerializer::build(schema_def.downcast()?, config, definitions)?; + // no need to store the serializer here, it has already been stored in definitions if necessary } let inner_schema: &PyDict = schema.get_as_req(intern!(py, "schema"))?; - CombinedSerializer::build(inner_schema, config, build_context) + CombinedSerializer::build(inner_schema, config, definitions) } } @@ -51,14 +51,11 @@ impl BuildSerializer for DefinitionRefSerializer { fn build( schema: &PyDict, _config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let schema_ref: String = schema.get_as_req(intern!(schema.py(), "schema_ref"))?; - - match build_context.find(&schema_ref)? { - ThingOrId::Thing(serializer) => Ok(serializer), - ThingOrId::Id(serializer_id) => Ok(Self { serializer_id }.into()), - } + let serializer_id = definitions.get_reference_id(&schema_ref); + Ok(Self { serializer_id }.into()) } } @@ -70,10 +67,10 @@ impl TypeSerializer for DefinitionRefSerializer { exclude: Option<&PyAny>, extra: &Extra, ) -> PyResult { - let value_id = extra.rec_guard.add(value)?; - let comb_serializer = unsafe { extra.slots.get_unchecked(self.serializer_id) }; + let value_id = extra.rec_guard.add(value, self.serializer_id)?; + let comb_serializer = extra.definitions.get(self.serializer_id).unwrap(); let r = comb_serializer.to_python(value, include, exclude, extra); - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, self.serializer_id); r } @@ -89,10 +86,10 @@ impl TypeSerializer for DefinitionRefSerializer { exclude: Option<&PyAny>, extra: &Extra, ) -> Result { - let value_id = extra.rec_guard.add(value).map_err(py_err_se_err)?; - let comb_serializer = unsafe { extra.slots.get_unchecked(self.serializer_id) }; + let value_id = extra.rec_guard.add(value, self.serializer_id).map_err(py_err_se_err)?; + let comb_serializer = extra.definitions.get(self.serializer_id).unwrap(); let r = comb_serializer.serde_serialize(value, serializer, include, exclude, extra); - extra.rec_guard.pop(value_id); + extra.rec_guard.pop(value_id, self.serializer_id); r } diff --git a/src/serializers/type_serializers/dict.rs b/src/serializers/type_serializers/dict.rs index 1e000fd0f..79b18dc63 100644 --- a/src/serializers/type_serializers/dict.rs +++ b/src/serializers/type_serializers/dict.rs @@ -6,8 +6,8 @@ use pyo3::types::PyDict; use serde::ser::SerializeMap; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -30,16 +30,16 @@ impl BuildSerializer for DictSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let key_serializer = match schema.get_as::<&PyDict>(intern!(py, "keys_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let value_serializer = match schema.get_as::<&PyDict>(intern!(py, "values_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let filter = match schema.get_as::<&PyDict>(intern!(py, "serialization"))? { Some(ser) => { diff --git a/src/serializers/type_serializers/format.rs b/src/serializers/type_serializers/format.rs index 25ac23b20..6384cd2f9 100644 --- a/src/serializers/type_serializers/format.rs +++ b/src/serializers/type_serializers/format.rs @@ -6,8 +6,8 @@ use pyo3::types::{PyDict, PyString}; use serde::ser::Error; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use super::simple::none_json_key; use super::string::serialize_py_str; @@ -65,12 +65,12 @@ impl BuildSerializer for FormatSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let formatting_string: &str = schema.get_as_req(intern!(py, "formatting_string"))?; if formatting_string.is_empty() { - ToStringSerializer::build(schema, config, build_context) + ToStringSerializer::build(schema, config, definitions) } else { Ok(Self { format_func: py @@ -165,7 +165,7 @@ impl BuildSerializer for ToStringSerializer { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self { when_used: WhenUsed::new(schema, WhenUsed::JsonUnlessNone)?, diff --git a/src/serializers/type_serializers/function.rs b/src/serializers/type_serializers/function.rs index 3f44356e6..4f5b3ca51 100644 --- a/src/serializers/type_serializers/function.rs +++ b/src/serializers/type_serializers/function.rs @@ -9,8 +9,8 @@ use pyo3::types::PyDict; use pyo3::types::PyString; use serde::ser::Error; -use crate::build_context::BuildContext; use crate::build_tools::{function_name, py_error_type, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use crate::serializers::extra::{ExtraOwned, SerMode}; use crate::serializers::filter::AnyFilter; use crate::{PydanticOmit, PydanticSerializationUnexpectedValue}; @@ -31,12 +31,12 @@ impl BuildSerializer for FunctionBeforeSerializerBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); // `before` schemas will obviously have type from `schema` since the validator is called second let schema = schema.get_as_req(intern!(py, "schema"))?; - CombinedSerializer::build(schema, config, build_context) + CombinedSerializer::build(schema, config, definitions) } } @@ -47,14 +47,14 @@ impl BuildSerializer for FunctionAfterSerializerBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); // while `before` schemas have an obvious type, for // `after` schemas it's less, clear but the default will be the same type, and the user/lib can always // override the serializer let schema = schema.get_as_req(intern!(py, "schema"))?; - CombinedSerializer::build(schema, config, build_context) + CombinedSerializer::build(schema, config, definitions) } } @@ -65,9 +65,9 @@ impl BuildSerializer for FunctionPlainSerializerBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - super::any::AnySerializer::build(schema, config, build_context) + super::any::AnySerializer::build(schema, config, definitions) } } @@ -99,7 +99,7 @@ impl BuildSerializer for FunctionPlainSerializer { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -277,9 +277,9 @@ impl BuildSerializer for FunctionWrapSerializerBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - super::any::AnySerializer::build(schema, config, build_context) + super::any::AnySerializer::build(schema, config, definitions) } } @@ -303,7 +303,7 @@ impl BuildSerializer for FunctionWrapSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let ser_schema: &PyDict = schema.get_as_req(intern!(py, "serialization"))?; @@ -320,13 +320,13 @@ impl BuildSerializer for FunctionWrapSerializer { // remove the serialization key from the schema so we don't recurse schema_copy.del_item(intern!(py, "serialization"))?; // remove ref if it exists - the point is that `schema` here has already run through - // `CombinedSerializer::build` so "ref" here will have already been added to `BuildContext::used_ref` + // `CombinedSerializer::build` so "ref" here will have already been added to `Definitions::used_ref` // we don't want to error by "finding" it now schema_copy.del_item(intern!(py, "ref")).ok(); schema_copy }; - let serializer = CombinedSerializer::build(inner_schema, config, build_context)?; + let serializer = CombinedSerializer::build(inner_schema, config, definitions)?; let name = format!("wrap_function[{function_name}, {}]", serializer.get_name()); Ok(Self { diff --git a/src/serializers/type_serializers/generator.rs b/src/serializers/type_serializers/generator.rs index b78d5109d..a5f31eacd 100644 --- a/src/serializers/type_serializers/generator.rs +++ b/src/serializers/type_serializers/generator.rs @@ -6,8 +6,8 @@ use pyo3::types::{PyDict, PyIterator}; use serde::ser::SerializeSeq; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -27,12 +27,12 @@ impl BuildSerializer for GeneratorSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let item_serializer = match schema.get_as::<&PyDict>(intern!(py, "items_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; Ok(Self { item_serializer: Box::new(item_serializer), diff --git a/src/serializers/type_serializers/json.rs b/src/serializers/type_serializers/json.rs index 2f92df511..bf8ced7d3 100644 --- a/src/serializers/type_serializers/json.rs +++ b/src/serializers/type_serializers/json.rs @@ -7,8 +7,8 @@ use pyo3::types::PyDict; use serde::ser::Error; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -27,13 +27,13 @@ impl BuildSerializer for JsonSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let serializer = match schema.get_as::<&PyDict>(intern!(py, "schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; Ok(Self { serializer: Box::new(serializer), diff --git a/src/serializers/type_serializers/list.rs b/src/serializers/type_serializers/list.rs index 77db55058..ac418151c 100644 --- a/src/serializers/type_serializers/list.rs +++ b/src/serializers/type_serializers/list.rs @@ -6,8 +6,8 @@ use pyo3::types::{PyDict, PyList}; use serde::ser::SerializeSeq; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -28,12 +28,12 @@ impl BuildSerializer for ListSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let item_serializer = match schema.get_as::<&PyDict>(intern!(py, "items_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let name = format!("{}[{}]", Self::EXPECTED_TYPE, item_serializer.get_name()); Ok(Self { diff --git a/src/serializers/type_serializers/literal.rs b/src/serializers/type_serializers/literal.rs index a8ca8579e..a90c01314 100644 --- a/src/serializers/type_serializers/literal.rs +++ b/src/serializers/type_serializers/literal.rs @@ -7,8 +7,8 @@ use pyo3::types::{PyDict, PyList, PyString}; use ahash::AHashSet; use serde::Serialize; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, py_err_se_err, BuildSerializer, CombinedSerializer, Extra, @@ -29,7 +29,7 @@ impl BuildSerializer for LiteralSerializer { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let expected: &PyList = schema.get_as_req(intern!(schema.py(), "expected"))?; diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 1d52aca12..d5ef5d4a4 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -6,8 +6,8 @@ use pyo3::types::{PyDict, PyString, PyType}; use ahash::AHashMap; -use crate::build_context::BuildContext; use crate::build_tools::{py_error_type, ExtraBehavior, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use crate::serializers::computed_fields::ComputedFields; use crate::serializers::extra::SerCheck; use crate::serializers::filter::SchemaFilter; @@ -28,7 +28,7 @@ impl BuildSerializer for ModelFieldsBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -54,7 +54,7 @@ impl BuildSerializer for ModelFieldsBuilder { let alias: Option = field_info.get_as(intern!(py, "serialization_alias"))?; let schema = field_info.get_as_req(intern!(py, "schema"))?; - let serializer = CombinedSerializer::build(schema, config, build_context) + let serializer = CombinedSerializer::build(schema, config, definitions) .map_err(|e| py_error_type!("Field `{}`:\n {}", key, e))?; fields.insert(key, TypedDictField::new(py, key_py, alias, serializer, true)); @@ -81,12 +81,12 @@ impl BuildSerializer for ModelSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let class: &PyType = schema.get_as_req(intern!(py, "cls"))?; let sub_schema: &PyDict = schema.get_as_req(intern!(py, "schema"))?; - let serializer = Box::new(CombinedSerializer::build(sub_schema, config, build_context)?); + let serializer = Box::new(CombinedSerializer::build(sub_schema, config, definitions)?); Ok(Self { class: class.into(), diff --git a/src/serializers/type_serializers/nullable.rs b/src/serializers/type_serializers/nullable.rs index f8260d34c..1848d455e 100644 --- a/src/serializers/type_serializers/nullable.rs +++ b/src/serializers/type_serializers/nullable.rs @@ -4,8 +4,8 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::{infer_json_key_known, BuildSerializer, CombinedSerializer, Extra, IsType, ObType, TypeSerializer}; @@ -20,11 +20,11 @@ impl BuildSerializer for NullableSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let sub_schema = schema.get_as_req::<&PyDict>(intern!(schema.py(), "schema"))?; Ok(Self { - serializer: Box::new(CombinedSerializer::build(sub_schema, config, build_context)?), + serializer: Box::new(CombinedSerializer::build(sub_schema, config, definitions)?), } .into()) } diff --git a/src/serializers/type_serializers/other.rs b/src/serializers/type_serializers/other.rs index d5f1fdf93..08c173892 100644 --- a/src/serializers/type_serializers/other.rs +++ b/src/serializers/type_serializers/other.rs @@ -2,8 +2,8 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use crate::serializers::shared::CombinedSerializer; use super::any::AnySerializer; @@ -17,7 +17,7 @@ impl BuildSerializer for ChainBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let last_schema = schema .get_as_req::<&PyList>(intern!(schema.py(), "steps"))? @@ -25,7 +25,7 @@ impl BuildSerializer for ChainBuilder { .last() .unwrap() .downcast()?; - CombinedSerializer::build(last_schema, config, build_context) + CombinedSerializer::build(last_schema, config, definitions) } } @@ -37,10 +37,10 @@ impl BuildSerializer for CustomErrorBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let sub_schema: &PyDict = schema.get_as_req(intern!(schema.py(), "schema"))?; - CombinedSerializer::build(sub_schema, config, build_context) + CombinedSerializer::build(sub_schema, config, definitions) } } @@ -52,12 +52,12 @@ impl BuildSerializer for CallBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let return_schema = schema.get_as::<&PyDict>(intern!(schema.py(), "return_schema"))?; match return_schema { - Some(return_schema) => CombinedSerializer::build(return_schema, config, build_context), - None => AnySerializer::build(schema, config, build_context), + Some(return_schema) => CombinedSerializer::build(return_schema, config, definitions), + None => AnySerializer::build(schema, config, definitions), } } } @@ -70,10 +70,10 @@ impl BuildSerializer for LaxOrStrictBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let strict_schema: &PyDict = schema.get_as_req(intern!(schema.py(), "strict_schema"))?; - CombinedSerializer::build(strict_schema, config, build_context) + CombinedSerializer::build(strict_schema, config, definitions) } } @@ -85,7 +85,7 @@ impl BuildSerializer for ArgumentsBuilder { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { py_err!("`arguments` validators require a custom serializer") } @@ -101,9 +101,9 @@ macro_rules! any_build_serializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - AnySerializer::build(schema, config, build_context) + AnySerializer::build(schema, config, definitions) } } }; diff --git a/src/serializers/type_serializers/set_frozenset.rs b/src/serializers/type_serializers/set_frozenset.rs index 843933796..0496ca5bd 100644 --- a/src/serializers/type_serializers/set_frozenset.rs +++ b/src/serializers/type_serializers/set_frozenset.rs @@ -6,8 +6,8 @@ use pyo3::types::{PyDict, PyFrozenSet, PyList, PySet}; use serde::ser::SerializeSeq; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -29,12 +29,12 @@ macro_rules! build_serializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let item_serializer = match schema.get_as::<&PyDict>(intern!(py, "items_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let name = format!("{}[{}]", Self::EXPECTED_TYPE, item_serializer.get_name()); Ok(Self { diff --git a/src/serializers/type_serializers/simple.rs b/src/serializers/type_serializers/simple.rs index 18e685e81..e430705b9 100644 --- a/src/serializers/type_serializers/simple.rs +++ b/src/serializers/type_serializers/simple.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use serde::Serialize; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, IsType, ObType, @@ -20,7 +20,7 @@ impl BuildSerializer for NoneSerializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } @@ -92,7 +92,7 @@ macro_rules! build_simple_serializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/string.rs b/src/serializers/type_serializers/string.rs index 2547b2f1f..178be796b 100644 --- a/src/serializers/type_serializers/string.rs +++ b/src/serializers/type_serializers/string.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, py_err_se_err, BuildSerializer, CombinedSerializer, Extra, @@ -19,7 +19,7 @@ impl BuildSerializer for StrSerializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/timedelta.rs b/src/serializers/type_serializers/timedelta.rs index 385533d02..156f3b4fb 100644 --- a/src/serializers/type_serializers/timedelta.rs +++ b/src/serializers/type_serializers/timedelta.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pyo3::prelude::*; use pyo3::types::{PyDelta, PyDict}; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use super::{ infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, SerMode, @@ -19,7 +19,7 @@ impl BuildSerializer for TimeDeltaSerializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index 525d4f01d..1eb9dfa1c 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -5,8 +5,8 @@ use std::borrow::Cow; use serde::ser::SerializeSeq; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use super::any::AnySerializer; use super::{ @@ -27,15 +27,15 @@ impl BuildSerializer for TupleVariableSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); if let Some("positional") = schema.get_as::<&str>(intern!(py, "mode"))? { - return TuplePositionalSerializer::build(schema, config, build_context); + return TuplePositionalSerializer::build(schema, config, definitions); } let item_serializer = match schema.get_as::<&PyDict>(intern!(py, "items_schema"))? { - Some(items_schema) => CombinedSerializer::build(items_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(items_schema) => CombinedSerializer::build(items_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let name = format!("tuple[{}, ...]", item_serializer.get_name()); Ok(Self { @@ -150,18 +150,18 @@ impl BuildSerializer for TuplePositionalSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let items: &PyList = schema.get_as_req(intern!(py, "items_schema"))?; let extra_serializer = match schema.get_as::<&PyDict>(intern!(py, "extra_schema"))? { - Some(extra_schema) => CombinedSerializer::build(extra_schema, config, build_context)?, - None => AnySerializer::build(schema, config, build_context)?, + Some(extra_schema) => CombinedSerializer::build(extra_schema, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let items_serializers: Vec = items .iter() - .map(|item| CombinedSerializer::build(item.downcast()?, config, build_context)) + .map(|item| CombinedSerializer::build(item.downcast()?, config, definitions)) .collect::>()?; let descr = items_serializers diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index 1f799030f..10f9b0b8b 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -7,8 +7,8 @@ use pyo3::types::{PyDict, PyString}; use ahash::{AHashMap, AHashSet}; use serde::ser::SerializeMap; -use crate::build_context::BuildContext; use crate::build_tools::{py_error_type, schema_or_config, ExtraBehavior, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use crate::PydanticSerializationUnexpectedValue; use super::{ @@ -77,7 +77,7 @@ impl BuildSerializer for TypedDictSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -106,7 +106,7 @@ impl BuildSerializer for TypedDictSerializer { let alias: Option = field_info.get_as(intern!(py, "serialization_alias"))?; let schema = field_info.get_as_req(intern!(py, "schema"))?; - let serializer = CombinedSerializer::build(schema, config, build_context) + let serializer = CombinedSerializer::build(schema, config, definitions) .map_err(|e| py_error_type!("Field `{}`:\n {}", key, e))?; fields.insert( diff --git a/src/serializers/type_serializers/union.rs b/src/serializers/type_serializers/union.rs index ea4f1dc7b..43bc91462 100644 --- a/src/serializers/type_serializers/union.rs +++ b/src/serializers/type_serializers/union.rs @@ -3,8 +3,8 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; use std::borrow::Cow; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, SchemaDict}; +use crate::definitions::DefinitionsBuilder; use crate::serializers::extra::SerCheck; use crate::PydanticSerializationUnexpectedValue; @@ -25,13 +25,13 @@ impl BuildSerializer for UnionSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let choices: Vec = schema .get_as_req::<&PyList>(intern!(py, "choices"))? .iter() - .map(|choice| CombinedSerializer::build(choice.downcast()?, config, build_context)) + .map(|choice| CombinedSerializer::build(choice.downcast()?, config, definitions)) .collect::>>()?; Self::from_choices(choices) @@ -175,14 +175,14 @@ impl BuildSerializer for TaggedUnionBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let schema_choices: &PyDict = schema.get_as_req(intern!(schema.py(), "choices"))?; let mut choices: Vec = Vec::with_capacity(schema_choices.len()); for (_, value) in schema_choices { if let Ok(choice_schema) = value.downcast::() { - choices.push(CombinedSerializer::build(choice_schema, config, build_context)?) + choices.push(CombinedSerializer::build(choice_schema, config, definitions)?) } } UnionSerializer::from_choices(choices) diff --git a/src/serializers/type_serializers/url.rs b/src/serializers/type_serializers/url.rs index c623df771..1690ade21 100644 --- a/src/serializers/type_serializers/url.rs +++ b/src/serializers/type_serializers/url.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pyo3::prelude::*; use pyo3::types::PyDict; -use crate::build_context::BuildContext; +use crate::definitions::DefinitionsBuilder; use crate::url::{PyMultiHostUrl, PyUrl}; use super::{ @@ -22,7 +22,7 @@ macro_rules! build_serializer { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self {}.into()) } diff --git a/src/serializers/type_serializers/with_default.rs b/src/serializers/type_serializers/with_default.rs index f8934efc7..acee06d46 100644 --- a/src/serializers/type_serializers/with_default.rs +++ b/src/serializers/type_serializers/with_default.rs @@ -4,8 +4,8 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; -use crate::build_context::BuildContext; use crate::build_tools::SchemaDict; +use crate::definitions::DefinitionsBuilder; use crate::validators::DefaultType; use super::{BuildSerializer, CombinedSerializer, Extra, TypeSerializer}; @@ -22,13 +22,13 @@ impl BuildSerializer for WithDefaultSerializer { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let default = DefaultType::new(schema)?; let sub_schema: &PyDict = schema.get_as_req(intern!(py, "schema"))?; - let serializer = Box::new(CombinedSerializer::build(sub_schema, config, build_context)?); + let serializer = Box::new(CombinedSerializer::build(sub_schema, config, definitions)?); Ok(Self { default, serializer }.into()) } diff --git a/src/validators/any.rs b/src/validators/any.rs index 9bcb0ab22..2f7762bd0 100644 --- a/src/validators/any.rs +++ b/src/validators/any.rs @@ -5,7 +5,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; /// This might seem useless, but it's useful in DictValidator to avoid Option a lot #[derive(Debug, Clone)] @@ -17,7 +17,7 @@ impl BuildValidator for AnyValidator { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self.into()) } @@ -29,7 +29,7 @@ impl Validator for AnyValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { // Ok(input.clone().into_py(py)) @@ -38,7 +38,7 @@ impl Validator for AnyValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { false @@ -48,7 +48,7 @@ impl Validator for AnyValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/arguments.rs b/src/validators/arguments.rs index 7215da050..56508400a 100644 --- a/src/validators/arguments.rs +++ b/src/validators/arguments.rs @@ -10,7 +10,7 @@ use crate::input::{GenericArguments, Input}; use crate::lookup_key::LookupKey; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] struct Parameter { @@ -36,7 +36,7 @@ impl BuildValidator for ArgumentsValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -75,7 +75,7 @@ impl BuildValidator for ArgumentsValidator { let schema: &PyAny = arg.get_as_req(intern!(py, "schema"))?; - let validator = match build_validator(schema, config, build_context) { + let validator = match build_validator(schema, config, definitions) { Ok(v) => v, Err(err) => return py_err!("Parameter '{}':\n {}", name, err), }; @@ -108,11 +108,11 @@ impl BuildValidator for ArgumentsValidator { parameters, positional_params_count, var_args_validator: match schema.get_item(intern!(py, "var_args_schema")) { - Some(v) => Some(Box::new(build_validator(v, config, build_context)?)), + Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, var_kwargs_validator: match schema.get_item(intern!(py, "var_kwargs_schema")) { - Some(v) => Some(Box::new(build_validator(v, config, build_context)?)), + Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), @@ -155,7 +155,7 @@ impl Validator for ArgumentsValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let args = input.validate_args()?; @@ -196,7 +196,7 @@ impl Validator for ArgumentsValidator { (Some(pos_value), None) => { match parameter .validator - .validate(py, pos_value, extra, slots, recursion_guard) + .validate(py, pos_value, extra, definitions, recursion_guard) { Ok(value) => output_args.push(value), Err(ValError::LineErrors(line_errors)) => { @@ -208,7 +208,7 @@ impl Validator for ArgumentsValidator { (None, Some((lookup_path, kw_value))) => { match parameter .validator - .validate(py, kw_value, extra, slots, recursion_guard) + .validate(py, kw_value, extra, definitions, recursion_guard) { Ok(value) => output_kwargs.set_item(parameter.kwarg_key.as_ref().unwrap(), value)?, Err(ValError::LineErrors(line_errors)) => { @@ -220,7 +220,7 @@ impl Validator for ArgumentsValidator { } } (None, None) => { - if let Some(value) = parameter.validator.default_value(py, Some(parameter.name.as_str()), extra, slots, recursion_guard)? { + if let Some(value) = parameter.validator.default_value(py, Some(parameter.name.as_str()), extra, definitions, recursion_guard)? { if let Some(ref kwarg_key) = parameter.kwarg_key { output_kwargs.set_item(kwarg_key, value)?; } else { @@ -250,7 +250,7 @@ impl Validator for ArgumentsValidator { if len > self.positional_params_count { if let Some(ref validator) = self.var_args_validator { for (index, item) in $slice_macro!(args, self.positional_params_count, len).iter().enumerate() { - match validator.validate(py, item, extra, slots, recursion_guard) { + match validator.validate(py, item, extra, definitions, recursion_guard) { Ok(value) => output_args.push(value), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -292,7 +292,7 @@ impl Validator for ArgumentsValidator { }; if !used_kwargs.contains(either_str.as_cow()?.as_ref()) { match self.var_kwargs_validator { - Some(ref validator) => match validator.validate(py, value, extra, slots, recursion_guard) { + Some(ref validator) => match validator.validate(py, value, extra, definitions, recursion_guard) { Ok(value) => output_kwargs.set_item(either_str.as_py_string(py), value)?, Err(ValError::LineErrors(line_errors)) => { for err in line_errors { @@ -328,27 +328,27 @@ impl Validator for ArgumentsValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.parameters .iter() - .any(|p| p.validator.different_strict_behavior(build_context, ultra_strict)) + .any(|p| p.validator.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { Self::EXPECTED_TYPE } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.parameters .iter_mut() - .try_for_each(|parameter| parameter.validator.complete(build_context))?; + .try_for_each(|parameter| parameter.validator.complete(definitions))?; if let Some(v) = &mut self.var_args_validator { - v.complete(build_context)?; + v.complete(definitions)?; } if let Some(v) = &mut self.var_kwargs_validator { - v.complete(build_context)?; + v.complete(definitions)?; }; Ok(()) } diff --git a/src/validators/bool.rs b/src/validators/bool.rs index 4e6dee141..3a561f78b 100644 --- a/src/validators/bool.rs +++ b/src/validators/bool.rs @@ -6,7 +6,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct BoolValidator { @@ -19,7 +19,7 @@ impl BuildValidator for BoolValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self { strict: is_strict(schema, config)?, @@ -34,7 +34,7 @@ impl Validator for BoolValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { // TODO in theory this could be quicker if we used PyBool rather than going to a bool @@ -44,7 +44,7 @@ impl Validator for BoolValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -54,7 +54,7 @@ impl Validator for BoolValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/bytes.rs b/src/validators/bytes.rs index 632d0d116..ab977c031 100644 --- a/src/validators/bytes.rs +++ b/src/validators/bytes.rs @@ -7,7 +7,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct BytesValidator { @@ -20,7 +20,7 @@ impl BuildValidator for BytesValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "max_length")).is_some() @@ -42,7 +42,7 @@ impl Validator for BytesValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let either_bytes = input.validate_bytes(extra.strict.unwrap_or(self.strict))?; @@ -51,7 +51,7 @@ impl Validator for BytesValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -61,7 +61,7 @@ impl Validator for BytesValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -79,7 +79,7 @@ impl Validator for BytesConstrainedValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let either_bytes = input.validate_bytes(extra.strict.unwrap_or(self.strict))?; @@ -101,7 +101,7 @@ impl Validator for BytesConstrainedValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -111,7 +111,7 @@ impl Validator for BytesConstrainedValidator { "constrained-bytes" } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/call.rs b/src/validators/call.rs index 8e92f0b37..a38304a94 100644 --- a/src/validators/call.rs +++ b/src/validators/call.rs @@ -8,7 +8,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct CallValidator { @@ -24,16 +24,16 @@ impl BuildValidator for CallValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let arguments_schema: &PyAny = schema.get_as_req(intern!(py, "arguments_schema"))?; - let arguments_validator = Box::new(build_validator(arguments_schema, config, build_context)?); + let arguments_validator = Box::new(build_validator(arguments_schema, config, definitions)?); let return_schema = schema.get_item(intern!(py, "return_schema")); let return_validator = match return_schema { - Some(return_schema) => Some(Box::new(build_validator(return_schema, config, build_context)?)), + Some(return_schema) => Some(Box::new(build_validator(return_schema, config, definitions)?)), None => None, }; let function: &PyAny = schema.get_as_req(intern!(py, "function"))?; @@ -71,12 +71,12 @@ impl Validator for CallValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let args = self .arguments_validator - .validate(py, input, extra, slots, recursion_guard)?; + .validate(py, input, extra, definitions, recursion_guard)?; let return_value = if let Ok((args, kwargs)) = args.extract::<(&PyTuple, &PyDict)>(py) { self.function.call(py, args, Some(kwargs))? @@ -89,7 +89,7 @@ impl Validator for CallValidator { if let Some(return_validator) = &self.return_validator { return_validator - .validate(py, return_value.into_ref(py), extra, slots, recursion_guard) + .validate(py, return_value.into_ref(py), extra, definitions, recursion_guard) .map_err(|e| e.with_outer_location("return".into())) } else { Ok(return_value.to_object(py)) @@ -98,26 +98,26 @@ impl Validator for CallValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if let Some(return_validator) = &self.return_validator { - if return_validator.different_strict_behavior(build_context, ultra_strict) { + if return_validator.different_strict_behavior(definitions, ultra_strict) { return true; } } self.arguments_validator - .different_strict_behavior(build_context, ultra_strict) + .different_strict_behavior(definitions, ultra_strict) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.arguments_validator.complete(build_context)?; + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.arguments_validator.complete(definitions)?; match &mut self.return_validator { - Some(v) => v.complete(build_context), + Some(v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/callable.rs b/src/validators/callable.rs index 369239dc4..8811b04ad 100644 --- a/src/validators/callable.rs +++ b/src/validators/callable.rs @@ -5,7 +5,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct CallableValidator; @@ -16,7 +16,7 @@ impl BuildValidator for CallableValidator { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self.into()) } @@ -28,7 +28,7 @@ impl Validator for CallableValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match input.callable() { @@ -39,7 +39,7 @@ impl Validator for CallableValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { false @@ -49,7 +49,7 @@ impl Validator for CallableValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/chain.rs b/src/validators/chain.rs index 153462014..f51281438 100644 --- a/src/validators/chain.rs +++ b/src/validators/chain.rs @@ -7,7 +7,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct ChainValidator { @@ -21,12 +21,12 @@ impl BuildValidator for ChainValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let steps: Vec = schema .get_as_req::<&PyList>(intern!(schema.py(), "steps"))? .iter() - .map(|step| build_validator_steps(step, config, build_context)) + .map(|step| build_validator_steps(step, config, definitions)) .collect::>>>()? .into_iter() .flatten() @@ -56,9 +56,9 @@ impl BuildValidator for ChainValidator { fn build_validator_steps<'a>( step: &'a PyAny, config: Option<&'a PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult> { - let validator = build_validator(step, config, build_context)?; + let validator = build_validator(step, config, definitions)?; if let CombinedValidator::Chain(chain_validator) = validator { Ok(chain_validator.steps) } else { @@ -72,33 +72,33 @@ impl Validator for ChainValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let mut steps_iter = self.steps.iter(); let first_step = steps_iter.next().unwrap(); - let value = first_step.validate(py, input, extra, slots, recursion_guard)?; + let value = first_step.validate(py, input, extra, definitions, recursion_guard)?; steps_iter.try_fold(value, |v, step| { - step.validate(py, v.into_ref(py), extra, slots, recursion_guard) + step.validate(py, v.into_ref(py), extra, definitions, recursion_guard) }) } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.steps .iter() - .any(|v| v.different_strict_behavior(build_context, ultra_strict)) + .any(|v| v.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.steps.iter_mut().try_for_each(|v| v.complete(build_context)) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.steps.iter_mut().try_for_each(|v| v.complete(definitions)) } } diff --git a/src/validators/custom_error.rs b/src/validators/custom_error.rs index 3fe6c4c00..999b94e75 100644 --- a/src/validators/custom_error.rs +++ b/src/validators/custom_error.rs @@ -7,7 +7,7 @@ use crate::errors::{ErrorType, PydanticCustomError, PydanticKnownError, ValError use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub enum CustomError { @@ -19,7 +19,7 @@ impl CustomError { pub fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult> { let py = schema.py(); let error_type: String = match schema.get_as(intern!(py, "custom_error_type"))? { @@ -67,11 +67,11 @@ impl BuildValidator for CustomErrorValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - let custom_error = CustomError::build(schema, config, build_context)?.unwrap(); + let custom_error = CustomError::build(schema, config, definitions)?.unwrap(); let schema: &PyAny = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(schema, config, build_context)?); + let validator = Box::new(build_validator(schema, config, definitions)?); let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); Ok(Self { validator, @@ -88,27 +88,27 @@ impl Validator for CustomErrorValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { self.validator - .validate(py, input, extra, slots, recursion_guard) + .validate(py, input, extra, definitions, recursion_guard) .map_err(|_| self.custom_error.as_val_error(input)) } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index c5be4e1ee..57b8f270d 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -14,7 +14,7 @@ use crate::validators::function::convert_err; use super::arguments::{json_get, json_slice, py_get, py_slice}; use super::model::{create_class, force_setattr, Revalidate}; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] struct Field { @@ -44,7 +44,7 @@ impl BuildValidator for DataclassArgsValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); @@ -73,7 +73,7 @@ impl BuildValidator for DataclassArgsValidator { let schema: &PyAny = field.get_as_req(intern!(py, "schema"))?; - let validator = match build_validator(schema, config, build_context) { + let validator = match build_validator(schema, config, definitions) { Ok(v) => v, Err(err) => return py_err!("Field '{}':\n {}", name, err), }; @@ -127,7 +127,7 @@ impl Validator for DataclassArgsValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let args = input.validate_dataclass_args(&self.dataclass_name)?; @@ -192,7 +192,7 @@ impl Validator for DataclassArgsValidator { (Some(pos_value), None) => { match field .validator - .validate(py, pos_value, &extra, slots, recursion_guard) + .validate(py, pos_value, &extra, definitions, recursion_guard) { Ok(value) => set_item!(field, value), Err(ValError::LineErrors(line_errors)) => { @@ -209,7 +209,7 @@ impl Validator for DataclassArgsValidator { (None, Some((lookup_path, kw_value))) => { match field .validator - .validate(py, kw_value, &extra, slots, recursion_guard) + .validate(py, kw_value, &extra, definitions, recursion_guard) { Ok(value) => set_item!(field, value), Err(ValError::LineErrors(line_errors)) => { @@ -228,7 +228,7 @@ impl Validator for DataclassArgsValidator { py, Some(field.name.as_str()), &extra, - slots, + definitions, recursion_guard, )? { set_item!(field, value); @@ -316,7 +316,7 @@ impl Validator for DataclassArgsValidator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let dict: &PyDict = obj.downcast()?; @@ -353,7 +353,7 @@ impl Validator for DataclassArgsValidator { }; match field .validator - .validate(py, field_value, &next_extra, slots, recursion_guard) + .validate(py, field_value, &next_extra, definitions, recursion_guard) { Ok(output) => ok(output), Err(ValError::LineErrors(line_errors)) => { @@ -386,22 +386,22 @@ impl Validator for DataclassArgsValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.fields .iter() - .any(|f| f.validator.different_strict_behavior(build_context, ultra_strict)) + .any(|f| f.validator.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { &self.validator_name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.fields .iter_mut() - .try_for_each(|field| field.validator.complete(build_context)) + .try_for_each(|field| field.validator.complete(definitions)) } } @@ -422,13 +422,13 @@ impl BuildValidator for DataclassValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let class: &PyType = schema.get_as_req(intern!(py, "cls"))?; let sub_schema: &PyAny = schema.get_as_req(intern!(py, "schema"))?; - let validator = build_validator(sub_schema, config, build_context)?; + let validator = build_validator(sub_schema, config, definitions)?; let post_init = if schema.get_as::(intern!(py, "post_init"))?.unwrap_or(false) { Some(PyString::intern(py, "__post_init__").into_py(py)) @@ -461,12 +461,12 @@ impl Validator for DataclassValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if let Some(self_instance) = extra.self_instance { // in the case that self_instance is Some, we're calling validation from within `BaseModel.__init__` - return self.validate_init(py, self_instance, input, extra, slots, recursion_guard); + return self.validate_init(py, self_instance, input, extra, definitions, recursion_guard); } // same logic as on models @@ -474,7 +474,9 @@ impl Validator for DataclassValidator { if input.input_is_instance(class, 0)? { if self.revalidate.should_revalidate(input, class) { let input = input.input_get_attr(intern!(py, "__dict__")).unwrap()?; - let val_output = self.validator.validate(py, input, extra, slots, recursion_guard)?; + let val_output = self + .validator + .validate(py, input, extra, definitions, recursion_guard)?; let dc = create_class(self.class.as_ref(py))?; self.set_dict_call(py, dc.as_ref(py), val_output, input)?; Ok(dc) @@ -489,7 +491,9 @@ impl Validator for DataclassValidator { input, )) } else { - let val_output = self.validator.validate(py, input, extra, slots, recursion_guard)?; + let val_output = self + .validator + .validate(py, input, extra, definitions, recursion_guard)?; let dc = create_class(self.class.as_ref(py))?; self.set_dict_call(py, dc.as_ref(py), val_output, input)?; Ok(dc) @@ -503,7 +507,7 @@ impl Validator for DataclassValidator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if self.frozen { @@ -517,9 +521,15 @@ impl Validator for DataclassValidator { // Discard the second return value, which is `init_only_args` but is always // None anyway for validate_assignment; see validate_assignment in DataclassArgsValidator - let val_assignment_result = - self.validator - .validate_assignment(py, new_dict, field_name, field_value, extra, slots, recursion_guard)?; + let val_assignment_result = self.validator.validate_assignment( + py, + new_dict, + field_name, + field_value, + extra, + definitions, + recursion_guard, + )?; let (dc_dict, _): (&PyDict, PyObject) = val_assignment_result.extract(py)?; @@ -530,11 +540,11 @@ impl Validator for DataclassValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } else { true } @@ -544,8 +554,8 @@ impl Validator for DataclassValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } @@ -557,7 +567,7 @@ impl DataclassValidator { self_instance: &'s PyAny, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { // we need to set `self_instance` to None for nested validators as we don't want to operate on the self_instance @@ -566,7 +576,9 @@ impl DataclassValidator { self_instance: None, ..*extra }; - let val_output = self.validator.validate(py, input, &new_extra, slots, recursion_guard)?; + let val_output = self + .validator + .validate(py, input, &new_extra, definitions, recursion_guard)?; self.set_dict_call(py, self_instance, val_output, input)?; diff --git a/src/validators/date.rs b/src/validators/date.rs index e29c92fa2..df1e96901 100644 --- a/src/validators/date.rs +++ b/src/validators/date.rs @@ -10,7 +10,7 @@ use crate::input::{EitherDate, Input}; use crate::recursion_guard::RecursionGuard; use crate::validators::datetime::{NowConstraint, NowOp}; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct DateValidator { @@ -24,7 +24,7 @@ impl BuildValidator for DateValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self { strict: is_strict(schema, config)?, @@ -40,7 +40,7 @@ impl Validator for DateValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let date = match input.validate_date(extra.strict.unwrap_or(self.strict)) { @@ -100,7 +100,7 @@ impl Validator for DateValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -110,7 +110,7 @@ impl Validator for DateValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/datetime.rs b/src/validators/datetime.rs index 979e9adb7..e306634a5 100644 --- a/src/validators/datetime.rs +++ b/src/validators/datetime.rs @@ -11,7 +11,7 @@ use crate::errors::{py_err_string, ErrorType, ValError, ValResult}; use crate::input::{EitherDateTime, Input}; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct DateTimeValidator { @@ -25,7 +25,7 @@ impl BuildValidator for DateTimeValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self { strict: is_strict(schema, config)?, @@ -41,7 +41,7 @@ impl Validator for DateTimeValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let datetime = input.validate_datetime(extra.strict.unwrap_or(self.strict))?; @@ -104,7 +104,7 @@ impl Validator for DateTimeValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -114,7 +114,7 @@ impl Validator for DateTimeValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/definitions.rs b/src/validators/definitions.rs index 4b94a5be9..afbd46c2e 100644 --- a/src/validators/definitions.rs +++ b/src/validators/definitions.rs @@ -2,36 +2,35 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; -use crate::build_context::{BuildContext, ThingOrId}; use crate::build_tools::SchemaDict; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] -pub struct DefinitionsBuilder; +pub struct DefinitionsValidatorBuilder; -impl BuildValidator for DefinitionsBuilder { +impl BuildValidator for DefinitionsValidatorBuilder { const EXPECTED_TYPE: &'static str = "definitions"; fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let definitions: &PyList = schema.get_as_req(intern!(py, "definitions"))?; + let schema_definitions: &PyList = schema.get_as_req(intern!(py, "definitions"))?; - for def_schema in definitions { - build_validator(def_schema, config, build_context)?; - // no need to store the validator here, it has already been stored in build_context if necessary + for schema_definition in schema_definitions { + build_validator(schema_definition, config, definitions)?; + // no need to store the validator here, it has already been stored in definitions if necessary } let inner_schema: &PyAny = schema.get_as_req(intern!(py, "schema"))?; - build_validator(inner_schema, config, build_context) + build_validator(inner_schema, config, definitions) } } @@ -43,12 +42,11 @@ pub struct DefinitionRefValidator { } impl DefinitionRefValidator { - pub fn from_id(validator_id: usize, inner_name: String) -> CombinedValidator { + pub fn new(validator_id: usize) -> Self { Self { validator_id, - inner_name, + inner_name: "...".to_string(), } - .into() } } @@ -58,18 +56,17 @@ impl BuildValidator for DefinitionRefValidator { fn build( schema: &PyDict, _config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let schema_ref: String = schema.get_as_req(intern!(schema.py(), "schema_ref"))?; - match build_context.find(&schema_ref)? { - ThingOrId::Thing(validator) => Ok(validator), - ThingOrId::Id(validator_id) => Ok(Self { - validator_id, - inner_name: "...".to_string(), - } - .into()), + let validator_id = definitions.get_reference_id(&schema_ref); + + Ok(Self { + validator_id, + inner_name: "...".to_string(), } + .into()) } } @@ -79,35 +76,81 @@ impl Validator for DefinitionRefValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if let Some(id) = input.identity() { - if recursion_guard.contains_or_insert(id) { + if recursion_guard.contains_or_insert(id, self.validator_id) { // we don't remove id here, we leave that to the validator which originally added id to `recursion_guard` Err(ValError::new(ErrorType::RecursionLoop, input)) } else { if recursion_guard.incr_depth() > BACKUP_GUARD_LIMIT { return Err(ValError::new(ErrorType::RecursionLoop, input)); } - let output = validate(self.validator_id, py, input, extra, slots, recursion_guard); - recursion_guard.remove(&id); + let output = validate(self.validator_id, py, input, extra, definitions, recursion_guard); + recursion_guard.remove(id, self.validator_id); recursion_guard.decr_depth(); output } } else { - validate(self.validator_id, py, input, extra, slots, recursion_guard) + validate(self.validator_id, py, input, extra, definitions, recursion_guard) + } + } + + fn validate_assignment<'s, 'data: 's>( + &'s self, + py: Python<'data>, + obj: &'data PyAny, + field_name: &'data str, + field_value: &'data PyAny, + extra: &Extra, + definitions: &'data Definitions, + recursion_guard: &'s mut RecursionGuard, + ) -> ValResult<'data, PyObject> { + if let Some(id) = obj.identity() { + if recursion_guard.contains_or_insert(id, self.validator_id) { + // we don't remove id here, we leave that to the validator which originally added id to `recursion_guard` + Err(ValError::new(ErrorType::RecursionLoop, obj)) + } else { + if recursion_guard.incr_depth() > BACKUP_GUARD_LIMIT { + return Err(ValError::new(ErrorType::RecursionLoop, obj)); + } + let output = validate_assignment( + self.validator_id, + py, + obj, + field_name, + field_value, + extra, + definitions, + recursion_guard, + ); + recursion_guard.remove(id, self.validator_id); + recursion_guard.decr_depth(); + output + } + } else { + validate_assignment( + self.validator_id, + py, + obj, + field_name, + field_value, + extra, + definitions, + recursion_guard, + ) } } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { - if let Some(build_context) = build_context { + if let Some(definitions) = definitions { // have to unwrap here, because we can't return an error from this function, should be okay - let validator = build_context.find_validator(self.validator_id).unwrap(); + let validator = definitions.get_definition(self.validator_id).unwrap(); validator.different_strict_behavior(None, ultra_strict) } else { false @@ -119,8 +162,8 @@ impl Validator for DefinitionRefValidator { } /// don't need to call complete on the inner validator here, complete_validators takes care of that. - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - let validator = build_context.find_validator(self.validator_id)?; + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + let validator = definitions.get_definition(self.validator_id)?; self.inner_name = validator.get_name().to_string(); Ok(()) } @@ -140,9 +183,24 @@ fn validate<'s, 'data>( py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { - let validator = unsafe { slots.get_unchecked(validator_id) }; - validator.validate(py, input, extra, slots, recursion_guard) + let validator = definitions.get(validator_id).unwrap(); + validator.validate(py, input, extra, definitions, recursion_guard) +} + +#[allow(clippy::too_many_arguments)] +fn validate_assignment<'data>( + validator_id: usize, + py: Python<'data>, + obj: &'data PyAny, + field_name: &'data str, + field_value: &'data PyAny, + extra: &Extra, + definitions: &'data Definitions, + recursion_guard: &mut RecursionGuard, +) -> ValResult<'data, PyObject> { + let validator = definitions.get(validator_id).unwrap(); + validator.validate_assignment(py, obj, field_name, field_value, extra, definitions, recursion_guard) } diff --git a/src/validators/dict.rs b/src/validators/dict.rs index b97de0784..23d196005 100644 --- a/src/validators/dict.rs +++ b/src/validators/dict.rs @@ -11,7 +11,7 @@ use crate::recursion_guard::RecursionGuard; use super::any::AnyValidator; use super::list::length_check; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct DictValidator { @@ -29,16 +29,16 @@ impl BuildValidator for DictValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let key_validator = match schema.get_item(intern!(py, "keys_schema")) { - Some(schema) => Box::new(build_validator(schema, config, build_context)?), - None => Box::new(AnyValidator::build(schema, config, build_context)?), + Some(schema) => Box::new(build_validator(schema, config, definitions)?), + None => Box::new(AnyValidator::build(schema, config, definitions)?), }; let value_validator = match schema.get_item(intern!(py, "values_schema")) { - Some(d) => Box::new(build_validator(d, config, build_context)?), - None => Box::new(AnyValidator::build(schema, config, build_context)?), + Some(d) => Box::new(build_validator(d, config, definitions)?), + None => Box::new(AnyValidator::build(schema, config, definitions)?), }; let name = format!( "{}[{},{}]", @@ -64,30 +64,32 @@ impl Validator for DictValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let dict = input.validate_dict(extra.strict.unwrap_or(self.strict))?; match dict { - GenericMapping::PyDict(py_dict) => self.validate_dict(py, input, py_dict, extra, slots, recursion_guard), + GenericMapping::PyDict(py_dict) => { + self.validate_dict(py, input, py_dict, extra, definitions, recursion_guard) + } GenericMapping::PyMapping(mapping) => { - self.validate_mapping(py, input, mapping, extra, slots, recursion_guard) + self.validate_mapping(py, input, mapping, extra, definitions, recursion_guard) } GenericMapping::PyGetAttr(_, _) => unreachable!(), GenericMapping::JsonObject(json_object) => { - self.validate_json_object(py, input, json_object, extra, slots, recursion_guard) + self.validate_json_object(py, input, json_object, extra, definitions, recursion_guard) } } } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { - self.key_validator.different_strict_behavior(build_context, true) - || self.value_validator.different_strict_behavior(build_context, true) + self.key_validator.different_strict_behavior(definitions, true) + || self.value_validator.different_strict_behavior(definitions, true) } else { true } @@ -97,9 +99,9 @@ impl Validator for DictValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.key_validator.complete(build_context)?; - self.value_validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.key_validator.complete(definitions)?; + self.value_validator.complete(definitions) } } @@ -111,7 +113,7 @@ macro_rules! build_validate { input: &'data impl Input<'data>, dict: &'data $dict_type, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let output = PyDict::new(py); @@ -121,7 +123,7 @@ macro_rules! build_validate { let value_validator = self.value_validator.as_ref(); for item_result in <$iter>::new(dict)? { let (key, value) = item_result?; - let output_key = match key_validator.validate(py, key, extra, slots, recursion_guard) { + let output_key = match key_validator.validate(py, key, extra, definitions, recursion_guard) { Ok(value) => Some(value), Err(ValError::LineErrors(line_errors)) => { for err in line_errors { @@ -136,7 +138,7 @@ macro_rules! build_validate { Err(ValError::Omit) => continue, Err(err) => return Err(err), }; - let output_value = match value_validator.validate(py, value, extra, slots, recursion_guard) { + let output_value = match value_validator.validate(py, value, extra, definitions, recursion_guard) { Ok(value) => Some(value), Err(ValError::LineErrors(line_errors)) => { for err in line_errors { diff --git a/src/validators/float.rs b/src/validators/float.rs index 7b31a51ca..960804f23 100644 --- a/src/validators/float.rs +++ b/src/validators/float.rs @@ -7,7 +7,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; pub struct FloatBuilder; @@ -16,7 +16,7 @@ impl BuildValidator for FloatBuilder { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "multiple_of")).is_some() @@ -25,7 +25,7 @@ impl BuildValidator for FloatBuilder { || schema.get_item(intern!(py, "ge")).is_some() || schema.get_item(intern!(py, "gt")).is_some(); if use_constrained { - ConstrainedFloatValidator::build(schema, config, build_context) + ConstrainedFloatValidator::build(schema, config, definitions) } else { Ok(FloatValidator { strict: is_strict(schema, config)?, @@ -48,7 +48,7 @@ impl BuildValidator for FloatValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); Ok(Self { @@ -65,7 +65,7 @@ impl Validator for FloatValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?; @@ -77,7 +77,7 @@ impl Validator for FloatValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { true @@ -87,7 +87,7 @@ impl Validator for FloatValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -109,7 +109,7 @@ impl Validator for ConstrainedFloatValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?; @@ -153,7 +153,7 @@ impl Validator for ConstrainedFloatValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { true @@ -163,7 +163,7 @@ impl Validator for ConstrainedFloatValidator { "constrained-float" } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -173,7 +173,7 @@ impl BuildValidator for ConstrainedFloatValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); Ok(Self { diff --git a/src/validators/frozenset.rs b/src/validators/frozenset.rs index 0afb3bff2..368b9d15f 100644 --- a/src/validators/frozenset.rs +++ b/src/validators/frozenset.rs @@ -8,7 +8,7 @@ use crate::recursion_guard::RecursionGuard; use super::list::{get_items_schema, length_check}; use super::set::set_build; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct FrozenSetValidator { @@ -31,7 +31,7 @@ impl Validator for FrozenSetValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let seq = input.validate_frozenset(extra.strict.unwrap_or(self.strict))?; @@ -47,7 +47,7 @@ impl Validator for FrozenSetValidator { self.generator_max_length, v, extra, - slots, + definitions, recursion_guard, )?, )?, @@ -62,12 +62,12 @@ impl Validator for FrozenSetValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { match self.item_validator { - Some(ref v) => v.different_strict_behavior(build_context, true), + Some(ref v) => v.different_strict_behavior(definitions, true), None => false, } } else { @@ -79,9 +79,9 @@ impl Validator for FrozenSetValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { match self.item_validator { - Some(ref mut v) => v.complete(build_context), + Some(ref mut v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/function.rs b/src/validators/function.rs index 81fac48ef..4f8b3bd87 100644 --- a/src/validators/function.rs +++ b/src/validators/function.rs @@ -11,7 +11,7 @@ use crate::input::Input; use crate::recursion_guard::RecursionGuard; use super::generator::InternalValidator; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; fn destructure_function_schema(schema: &PyDict) -> PyResult<(bool, bool, &PyAny)> { let func_dict: &PyDict = schema.get_as_req(intern!(schema.py(), "function"))?; @@ -33,10 +33,10 @@ macro_rules! impl_build { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let validator = build_validator(schema.get_as_req(intern!(py, "schema"))?, config, build_context)?; + 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 name = format!( "{}[{}(), {}]", @@ -69,11 +69,11 @@ macro_rules! impl_validator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let validate = - move |v: &'data PyAny, e: &Extra| self.validator.validate(py, v, e, slots, recursion_guard); + move |v: &'data PyAny, e: &Extra| self.validator.validate(py, v, e, definitions, recursion_guard); self._validate(validate, py, input.to_object(py).into_ref(py), extra) } fn validate_assignment<'s, 'data: 's>( @@ -83,24 +83,24 @@ macro_rules! impl_validator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let validate = move |v: &'data PyAny, e: &Extra| { self.validator - .validate_assignment(py, v, field_name, field_value, e, slots, recursion_guard) + .validate_assignment(py, v, field_name, field_value, e, definitions, recursion_guard) }; self._validate(validate, py, obj, extra) } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { self.validator - .different_strict_behavior(build_context, ultra_strict) + .different_strict_behavior(definitions, ultra_strict) } else { true } @@ -110,8 +110,8 @@ macro_rules! impl_validator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } }; @@ -198,7 +198,7 @@ impl BuildValidator for FunctionPlainValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let (is_field_validator, info_arg, function) = destructure_function_schema(schema)?; @@ -222,7 +222,7 @@ impl Validator for FunctionPlainValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let r = if self.info_arg { @@ -236,7 +236,7 @@ impl Validator for FunctionPlainValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { // best guess, should we change this? @@ -247,7 +247,7 @@ impl Validator for FunctionPlainValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -288,11 +288,18 @@ impl Validator for FunctionWrapValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let handler = ValidatorCallable { - validator: InternalValidator::new(py, "ValidatorCallable", &self.validator, slots, extra, recursion_guard), + validator: InternalValidator::new( + py, + "ValidatorCallable", + &self.validator, + definitions, + extra, + recursion_guard, + ), }; self._validate( Py::new(py, handler)?.into_ref(py), @@ -309,11 +316,18 @@ impl Validator for FunctionWrapValidator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let handler = AssignmentValidatorCallable { - validator: InternalValidator::new(py, "ValidatorCallable", &self.validator, slots, extra, recursion_guard), + validator: InternalValidator::new( + py, + "ValidatorCallable", + &self.validator, + definitions, + extra, + recursion_guard, + ), updated_field_name: field_name.to_string(), updated_field_value: field_value.to_object(py), }; @@ -322,11 +336,11 @@ impl Validator for FunctionWrapValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } else { true } @@ -336,8 +350,8 @@ impl Validator for FunctionWrapValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } diff --git a/src/validators/generator.rs b/src/validators/generator.rs index f99bd6f7a..ade276886 100644 --- a/src/validators/generator.rs +++ b/src/validators/generator.rs @@ -10,7 +10,7 @@ use crate::recursion_guard::RecursionGuard; use crate::ValidationError; use super::list::get_items_schema; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct GeneratorValidator { @@ -26,9 +26,9 @@ impl BuildValidator for GeneratorValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { - let item_validator = get_items_schema(schema, config, build_context)?; + let item_validator = get_items_schema(schema, config, definitions)?; let name = match item_validator { Some(ref v) => format!("{}[{}]", Self::EXPECTED_TYPE, v.get_name()), None => format!("{}[any]", Self::EXPECTED_TYPE), @@ -49,14 +49,14 @@ impl Validator for GeneratorValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, 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, slots, extra, recursion_guard)); + .map(|v| InternalValidator::new(py, "ValidatorIterator", v, definitions, extra, recursion_guard)); let v_iterator = ValidatorIterator { iterator, @@ -69,11 +69,11 @@ impl Validator for GeneratorValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if let Some(ref v) = self.item_validator { - v.different_strict_behavior(build_context, ultra_strict) + v.different_strict_behavior(definitions, ultra_strict) } else { false } @@ -83,9 +83,9 @@ impl Validator for GeneratorValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { match self.item_validator { - Some(ref mut v) => v.complete(build_context), + Some(ref mut v) => v.complete(definitions), None => Ok(()), } } @@ -195,7 +195,7 @@ impl ValidatorIterator { pub struct InternalValidator { name: String, validator: CombinedValidator, - slots: Vec, + definitions: Vec, // TODO, do we need data? data: Option>, strict: Option, @@ -215,14 +215,14 @@ impl InternalValidator { py: Python, name: &str, validator: &CombinedValidator, - slots: &[CombinedValidator], + definitions: &[CombinedValidator], extra: &Extra, recursion_guard: &RecursionGuard, ) -> Self { Self { name: name.to_string(), validator: validator.clone(), - slots: slots.to_vec(), + definitions: definitions.to_vec(), data: extra.data.map(|d| d.into_py(py)), strict: extra.strict, context: extra.context.map(|d| d.into_py(py)), @@ -254,7 +254,7 @@ impl InternalValidator { field_name, field_value, &extra, - &self.slots, + &self.definitions, &mut self.recursion_guard, ) .map_err(|e| { @@ -280,7 +280,7 @@ impl InternalValidator { self_instance: self.self_instance.as_ref().map(|data| data.as_ref(py)), }; self.validator - .validate(py, input, &extra, &self.slots, &mut self.recursion_guard) + .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) }) diff --git a/src/validators/int.rs b/src/validators/int.rs index 390cf95bd..605742d19 100644 --- a/src/validators/int.rs +++ b/src/validators/int.rs @@ -7,7 +7,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct IntValidator { @@ -20,7 +20,7 @@ impl BuildValidator for IntValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "multiple_of")).is_some() @@ -45,7 +45,7 @@ impl Validator for IntValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { Ok(input.validate_int(extra.strict.unwrap_or(self.strict))?.into_py(py)) @@ -53,7 +53,7 @@ impl Validator for IntValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -63,7 +63,7 @@ impl Validator for IntValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -84,7 +84,7 @@ impl Validator for ConstrainedIntValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let int = input.validate_int(extra.strict.unwrap_or(self.strict))?; @@ -123,7 +123,7 @@ impl Validator for ConstrainedIntValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -133,7 +133,7 @@ impl Validator for ConstrainedIntValidator { "constrained-int" } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/is_instance.rs b/src/validators/is_instance.rs index 21b442998..3854ee683 100644 --- a/src/validators/is_instance.rs +++ b/src/validators/is_instance.rs @@ -8,7 +8,7 @@ use crate::input::{Input, JsonType}; use crate::recursion_guard::RecursionGuard; use super::function::convert_err; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct IsInstanceValidator { @@ -25,7 +25,7 @@ impl BuildValidator for IsInstanceValidator { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let cls_key = intern!(py, "cls"); @@ -67,7 +67,7 @@ impl Validator for IsInstanceValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match input.input_is_instance(self.class.as_ref(py), self.json_types)? { @@ -92,7 +92,7 @@ impl Validator for IsInstanceValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { false @@ -102,7 +102,7 @@ impl Validator for IsInstanceValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/is_subclass.rs b/src/validators/is_subclass.rs index 071c864f8..668e771aa 100644 --- a/src/validators/is_subclass.rs +++ b/src/validators/is_subclass.rs @@ -7,7 +7,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct IsSubclassValidator { @@ -22,7 +22,7 @@ impl BuildValidator for IsSubclassValidator { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let class: &PyType = schema.get_as_req(intern!(py, "cls"))?; @@ -47,7 +47,7 @@ impl Validator for IsSubclassValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match input.input_is_subclass(self.class.as_ref(py))? { @@ -63,7 +63,7 @@ impl Validator for IsSubclassValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { false @@ -73,7 +73,7 @@ impl Validator for IsSubclassValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/json.rs b/src/validators/json.rs index dd7810628..de8044751 100644 --- a/src/validators/json.rs +++ b/src/validators/json.rs @@ -7,7 +7,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct JsonValidator { @@ -21,11 +21,11 @@ impl BuildValidator for JsonValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let validator = match schema.get_as(intern!(schema.py(), "schema"))? { Some(schema) => { - let validator = build_validator(schema, config, build_context)?; + let validator = build_validator(schema, config, definitions)?; match validator { CombinedValidator::Any(_) => None, _ => Some(Box::new(validator)), @@ -48,12 +48,12 @@ impl Validator for JsonValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let json_value = input.parse_json()?; match self.validator { - Some(ref validator) => match validator.validate(py, &json_value, extra, slots, recursion_guard) { + Some(ref validator) => match validator.validate(py, &json_value, extra, definitions, recursion_guard) { Ok(v) => Ok(v), Err(err) => Err(err.duplicate(py)), }, @@ -63,11 +63,11 @@ impl Validator for JsonValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if let Some(ref v) = self.validator { - v.different_strict_behavior(build_context, ultra_strict) + v.different_strict_behavior(definitions, ultra_strict) } else { false } @@ -77,9 +77,9 @@ impl Validator for JsonValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { match self.validator { - Some(ref mut v) => v.complete(build_context), + Some(ref mut v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/lax_or_strict.rs b/src/validators/lax_or_strict.rs index 3e29d3fd1..b482b39e9 100644 --- a/src/validators/lax_or_strict.rs +++ b/src/validators/lax_or_strict.rs @@ -7,7 +7,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct LaxOrStrictValidator { @@ -23,14 +23,14 @@ impl BuildValidator for LaxOrStrictValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let lax_schema = schema.get_as_req(intern!(py, "lax_schema"))?; - let lax_validator = Box::new(build_validator(lax_schema, config, build_context)?); + let lax_validator = Box::new(build_validator(lax_schema, config, definitions)?); let strict_schema = schema.get_as_req(intern!(py, "strict_schema"))?; - let strict_validator = Box::new(build_validator(strict_schema, config, build_context)?); + let strict_validator = Box::new(build_validator(strict_schema, config, definitions)?); let name = format!( "{}[lax={},strict={}]", @@ -54,23 +54,25 @@ impl Validator for LaxOrStrictValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if extra.strict.unwrap_or(self.strict) { - self.strict_validator.validate(py, input, extra, slots, recursion_guard) + self.strict_validator + .validate(py, input, extra, definitions, recursion_guard) } else { - self.lax_validator.validate(py, input, extra, slots, recursion_guard) + self.lax_validator + .validate(py, input, extra, definitions, recursion_guard) } } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { - self.strict_validator.different_strict_behavior(build_context, true) + self.strict_validator.different_strict_behavior(definitions, true) } else { true } @@ -80,8 +82,8 @@ impl Validator for LaxOrStrictValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.lax_validator.complete(build_context)?; - self.strict_validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.lax_validator.complete(definitions)?; + self.strict_validator.complete(definitions) } } diff --git a/src/validators/list.rs b/src/validators/list.rs index fde7818da..c12de8b55 100644 --- a/src/validators/list.rs +++ b/src/validators/list.rs @@ -6,7 +6,7 @@ use crate::errors::ValResult; use crate::input::{GenericCollection, Input}; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct ListValidator { @@ -21,11 +21,11 @@ pub struct ListValidator { pub fn get_items_schema( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult>> { match schema.get_item(pyo3::intern!(schema.py(), "items_schema")) { Some(d) => { - let validator = build_validator(d, config, build_context)?; + let validator = build_validator(d, config, definitions)?; match validator { CombinedValidator::Any(_) => Ok(None), _ => Ok(Some(Box::new(validator))), @@ -75,10 +75,10 @@ impl BuildValidator for ListValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let item_validator = get_items_schema(schema, config, build_context)?; + let item_validator = get_items_schema(schema, config, definitions)?; let inner_name = item_validator.as_ref().map(|v| v.get_name()).unwrap_or("any"); let name = format!("{}[{inner_name}]", Self::EXPECTED_TYPE); Ok(Self { @@ -99,7 +99,7 @@ impl Validator for ListValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let seq = input.validate_list(extra.strict.unwrap_or(self.strict), self.allow_any_iter)?; @@ -113,7 +113,7 @@ impl Validator for ListValidator { self.max_length, v, extra, - slots, + definitions, recursion_guard, )?, None => match seq { @@ -130,12 +130,12 @@ impl Validator for ListValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { match self.item_validator { - Some(ref v) => v.different_strict_behavior(build_context, true), + Some(ref v) => v.different_strict_behavior(definitions, true), None => false, } } else { @@ -147,9 +147,9 @@ impl Validator for ListValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { if let Some(ref mut v) = self.item_validator { - v.complete(build_context)?; + v.complete(definitions)?; let inner_name = v.get_name(); self.name = format!("{}[{inner_name}]", Self::EXPECTED_TYPE); } diff --git a/src/validators/literal.rs b/src/validators/literal.rs index d829962f7..7f181b35d 100644 --- a/src/validators/literal.rs +++ b/src/validators/literal.rs @@ -11,7 +11,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct LiteralValidator { @@ -33,7 +33,7 @@ impl BuildValidator for LiteralValidator { fn build( schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let expected: &PyList = schema.get_as_req(intern!(schema.py(), "expected"))?; if expected.is_empty() { @@ -72,7 +72,7 @@ impl Validator for LiteralValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if let Some(expected_ints) = &self.expected_int { @@ -105,7 +105,7 @@ impl Validator for LiteralValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -115,7 +115,7 @@ impl Validator for LiteralValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 9a97bed35..8b2ae7dc5 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -8,8 +8,8 @@ use pyo3::prelude::*; use pyo3::types::{PyAny, PyDict}; use pyo3::{intern, PyTraverseError, PyVisit}; -use crate::build_context::BuildContext; use crate::build_tools::{py_err, py_error_type, SchemaDict, SchemaError}; +use crate::definitions::{Definitions, DefinitionsBuilder}; use crate::errors::{ErrorMode, LocItem, ValError, ValResult, ValidationError}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; @@ -54,11 +54,13 @@ mod with_default; pub use with_default::DefaultType; +use self::definitions::DefinitionRefValidator; + #[pyclass(module = "pydantic_core._pydantic_core")] #[derive(Debug, Clone)] pub struct SchemaValidator { validator: CombinedValidator, - slots: Vec, + definitions: Vec, schema: PyObject, #[pyo3(get)] title: PyObject, @@ -71,11 +73,14 @@ impl SchemaValidator { let self_validator = SelfValidator::new(py)?; let schema = self_validator.validate_schema(py, schema)?; - let mut build_context = BuildContext::new(schema)?; + let mut definitions_builder = DefinitionsBuilder::new(); - let mut validator = build_validator(schema, config, &mut build_context)?; - validator.complete(&build_context)?; - let slots = build_context.into_slots_val()?; + let mut validator = build_validator(schema, config, &mut definitions_builder)?; + validator.complete(&definitions_builder)?; + let mut definitions = definitions_builder.clone().finish()?; + for val in definitions.iter_mut() { + val.complete(&definitions_builder)?; + } let config_title = match config { Some(c) => c.get_item("title"), None => None, @@ -86,7 +91,7 @@ impl SchemaValidator { }; Ok(Self { validator, - slots, + definitions, schema: schema.into_py(py), title, }) @@ -187,23 +192,23 @@ impl SchemaValidator { let guard = &mut RecursionGuard::default(); self.validator - .validate_assignment(py, obj, field_name, field_value, &extra, &self.slots, guard) + .validate_assignment(py, obj, field_name, field_value, &extra, &self.definitions, guard) .map_err(|e| self.prepare_validation_err(py, e, ErrorMode::Python)) } pub fn __repr__(&self, py: Python) -> String { format!( - "SchemaValidator(title={:?}, validator={:#?}, slots={:#?})", + "SchemaValidator(title={:?}, validator={:#?}, definitions={:#?})", self.title.extract::<&str>(py).unwrap(), self.validator, - self.slots, + self.definitions, ) } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { self.validator.py_gc_traverse(&visit)?; visit.call(&self.schema)?; - for slot in self.slots.iter() { + for slot in self.definitions.iter() { slot.py_gc_traverse(&visit)?; } Ok(()) @@ -211,7 +216,7 @@ impl SchemaValidator { fn __clear__(&mut self) { self.validator.py_gc_clear(); - for slot in self.slots.iter_mut() { + for slot in self.definitions.iter_mut() { slot.py_gc_clear(); } } @@ -233,7 +238,7 @@ impl SchemaValidator { py, input, &Extra::new(strict, context, self_instance), - &self.slots, + &self.definitions, &mut RecursionGuard::default(), ) } @@ -245,6 +250,7 @@ impl SchemaValidator { static SCHEMA_DEFINITION: GILOnceCell = GILOnceCell::new(); +#[derive(Debug, Clone)] pub struct SelfValidator<'py> { validator: &'py SchemaValidator, } @@ -263,7 +269,7 @@ impl<'py> SelfValidator<'py> { py, schema, &Extra::default(), - &self.validator.slots, + &self.validator.definitions, &mut RecursionGuard::default(), ) { Ok(schema_obj) => Ok(schema_obj.into_ref(py)), @@ -277,15 +283,20 @@ impl<'py> SelfValidator<'py> { py.run(code, None, Some(locals))?; let self_schema: &PyDict = locals.get_as_req(intern!(py, "self_schema"))?; - let mut build_context = BuildContext::for_self_schema(); + let mut definitions_builder = DefinitionsBuilder::new(); - let validator = match build_validator(self_schema, None, &mut build_context) { + let mut validator = match build_validator(self_schema, None, &mut definitions_builder) { Ok(v) => v, Err(err) => return py_err!("Error building self-schema:\n {}", err), }; + validator.complete(&definitions_builder)?; + let mut definitions = definitions_builder.clone().finish()?; + for val in definitions.iter_mut() { + val.complete(&definitions_builder)?; + } Ok(SchemaValidator { validator, - slots: build_context.into_slots_val()?, + definitions, schema: py.None(), title: "Self Schema".into_py(py), }) @@ -300,7 +311,7 @@ pub trait BuildValidator: Sized { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult; } @@ -309,49 +320,25 @@ fn build_specific_validator<'a, T: BuildValidator>( val_type: &str, schema_dict: &'a PyDict, config: Option<&'a PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema_dict.py(); if let Some(schema_ref) = schema_dict.get_as::(intern!(py, "ref"))? { - // if there's a ref, we **might** want to store the validator in slots and return a DefinitionRefValidator: - // * if the ref isn't used at all, we just want to return a normal validator, and ignore the ref completely - // * if the ref is used inside itself, we have to store the validator in slots, - // and return a DefinitionRefValidator - two step process with `prepare_slot` and `complete_slot` - // * if the ref is used elsewhere, we want to clone it each time it's used - if build_context.ref_used(&schema_ref) { - // the ref is used somewhere - // check the ref is unique - if build_context.ref_already_used(&schema_ref) { - return py_err!("Duplicate ref: `{}`", schema_ref); - } - - return if build_context.ref_used_within(schema_dict, &schema_ref)? { - // the ref is used within itself, so we have to store the validator in slots - // and return a DefinitionRefValidator - let slot_id = build_context.prepare_slot(schema_ref)?; - let inner_val = T::build(schema_dict, config, build_context)?; - let name = inner_val.get_name().to_string(); - build_context.complete_slot(slot_id, inner_val)?; - Ok(definitions::DefinitionRefValidator::from_id(slot_id, name)) - } else { - // ref is used, but only out side itself, we want to clone it everywhere it's used - let validator = T::build(schema_dict, config, build_context)?; - build_context.store_reusable(schema_ref, validator.clone()); - Ok(validator) - }; - } + let inner_val = T::build(schema_dict, config, definitions)?; + let validator_id = definitions.add_definition(schema_ref, inner_val)?; + return Ok(DefinitionRefValidator::new(validator_id).into()); } - T::build(schema_dict, config, build_context) + T::build(schema_dict, config, definitions) .map_err(|err| py_error_type!("Error building \"{}\" validator:\n {}", val_type, err)) } // macro to build the match statement for validator selection macro_rules! validator_match { - ($type:ident, $dict:ident, $config:ident, $build_context:ident, $($validator:path,)+) => { + ($type:ident, $dict:ident, $config:ident, $definitions:ident, $($validator:path,)+) => { match $type { $( - <$validator>::EXPECTED_TYPE => build_specific_validator::<$validator>($type, $dict, $config, $build_context), + <$validator>::EXPECTED_TYPE => build_specific_validator::<$validator>($type, $dict, $config, $definitions), )+ _ => return py_err!(r#"Unknown schema type: "{}""#, $type), } @@ -361,7 +348,7 @@ macro_rules! validator_match { pub fn build_validator<'a>( schema: &'a PyAny, config: Option<&'a PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let dict: &PyDict = schema.downcast()?; let type_: &str = dict.get_as_req(intern!(schema.py(), "type"))?; @@ -369,7 +356,7 @@ pub fn build_validator<'a>( type_, dict, config, - build_context, + definitions, // typed dict e.g. heterogeneous dicts or simply a model typed_dict::TypedDictValidator, // unions @@ -448,7 +435,7 @@ pub fn build_validator<'a>( url::MultiHostUrlValidator, // recursive (self-referencing) models definitions::DefinitionRefValidator, - definitions::DefinitionsBuilder, + definitions::DefinitionsValidatorBuilder, ) } @@ -597,7 +584,7 @@ pub trait Validator: Send + Sync + Clone + Debug { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject>; @@ -607,7 +594,7 @@ pub trait Validator: Send + Sync + Clone + Debug { _py: Python<'data>, _outer_loc: Option>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, Option> { Ok(None) @@ -622,7 +609,7 @@ pub trait Validator: Send + Sync + Clone + Debug { _field_name: &'data str, _field_value: &'data PyAny, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let py_err = PyTypeError::new_err(format!("validate_assignment is not supported for {}", self.get_name())); @@ -633,7 +620,7 @@ pub trait Validator: Send + Sync + Clone + Debug { /// implementations should return true if any of their sub-validators return true fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool; @@ -643,5 +630,5 @@ pub trait Validator: Send + Sync + Clone + Debug { /// this method must be implemented for any validator which holds references to other validators, /// it is used by `DefinitionRefValidator` to set its name - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()>; + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()>; } diff --git a/src/validators/model.rs b/src/validators/model.rs index 08f779cd8..9c023da26 100644 --- a/src/validators/model.rs +++ b/src/validators/model.rs @@ -13,7 +13,7 @@ use crate::input::{py_error_on_minusone, Input}; use crate::recursion_guard::RecursionGuard; use super::function::convert_err; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; const DUNDER_DICT: &str = "__dict__"; const DUNDER_FIELDS_SET_KEY: &str = "__pydantic_fields_set__"; @@ -63,7 +63,7 @@ impl BuildValidator for ModelValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); // models ignore the parent config and always use the config from this model @@ -71,7 +71,7 @@ impl BuildValidator for ModelValidator { let class: &PyType = schema.get_as_req(intern!(py, "cls"))?; let sub_schema: &PyAny = schema.get_as_req(intern!(py, "schema"))?; - let validator = build_validator(sub_schema, config, build_context)?; + let validator = build_validator(sub_schema, config, definitions)?; Ok(Self { // we don't use is_strict here since we don't want validation to be strict in this case if @@ -108,12 +108,12 @@ impl Validator for ModelValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if let Some(self_instance) = extra.self_instance { // in the case that self_instance is Some, we're calling validation from within `BaseModel.__init__` - return self.validate_init(py, self_instance, input, extra, slots, recursion_guard); + return self.validate_init(py, self_instance, input, extra, definitions, recursion_guard); } // if we're in strict mode, we require an exact instance of the class (from python, with JSON an object is ok) @@ -140,7 +140,7 @@ impl Validator for ModelValidator { let output = self .validator - .validate(py, full_model_dict, extra, slots, recursion_guard)?; + .validate(py, full_model_dict, extra, definitions, recursion_guard)?; let (model_dict, model_extra, _): (&PyAny, &PyAny, &PyAny) = output.extract(py)?; let instance = self.create_class(model_dict, model_extra, fields_set)?; @@ -157,7 +157,9 @@ impl Validator for ModelValidator { input, )) } else { - let output = self.validator.validate(py, input, extra, slots, recursion_guard)?; + let output = self + .validator + .validate(py, input, extra, definitions, recursion_guard)?; let (model_dict, model_extra, fields_set): (&PyAny, &PyAny, &PyAny) = output.extract(py)?; let instance = self.create_class(model_dict, model_extra, fields_set)?; self.call_post_init(py, instance, input, extra) @@ -171,7 +173,7 @@ impl Validator for ModelValidator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if self.frozen { @@ -183,9 +185,15 @@ impl Validator for ModelValidator { let new_dict = dict.copy()?; new_dict.set_item(field_name, field_value)?; - let output = - self.validator - .validate_assignment(py, new_dict, field_name, field_value, extra, slots, recursion_guard)?; + let output = self.validator.validate_assignment( + py, + new_dict, + field_name, + field_value, + extra, + definitions, + recursion_guard, + )?; let (output, _, updated_fields_set): (&PyDict, &PyAny, &PySet) = output.extract(py)?; @@ -203,11 +211,11 @@ impl Validator for ModelValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } else { true } @@ -217,8 +225,8 @@ impl Validator for ModelValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } @@ -230,7 +238,7 @@ impl ModelValidator { self_instance: &'s PyAny, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { // we need to set `self_instance` to None for nested validators as we don't want to operate on self_instance @@ -240,7 +248,9 @@ impl ModelValidator { ..*extra }; - let output = self.validator.validate(py, input, &new_extra, slots, recursion_guard)?; + let output = self + .validator + .validate(py, input, &new_extra, definitions, recursion_guard)?; let (model_dict, model_extra, fields_set): (&PyAny, &PyAny, &PyAny) = output.extract(py)?; set_model_attrs(self_instance, model_dict, model_extra, fields_set)?; self.call_post_init(py, self_instance.into_py(py), input, extra) diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index dd9ec2827..1e9335e0f 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -14,7 +14,7 @@ use crate::input::{ use crate::lookup_key::LookupKey; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] struct Field { @@ -41,7 +41,7 @@ impl BuildValidator for ModelFieldsValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let strict = is_strict(schema, config)?; @@ -52,7 +52,7 @@ impl BuildValidator for ModelFieldsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, build_context)?)), + (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), (Some(_), _) => return py_err!("extra_validator can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -66,7 +66,7 @@ impl BuildValidator for ModelFieldsValidator { let schema = field_info.get_as_req(intern!(py, "schema"))?; - let validator = match build_validator(schema, config, build_context) { + let validator = match build_validator(schema, config, definitions) { Ok(v) => v, Err(err) => return py_err!("Field \"{}\":\n {}", field_name, err), }; @@ -106,7 +106,7 @@ impl Validator for ModelFieldsValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let strict = extra.strict.unwrap_or(self.strict); @@ -154,7 +154,7 @@ impl Validator for ModelFieldsValidator { } match field .validator - .validate(py, value, &extra, slots, recursion_guard) + .validate(py, value, &extra, definitions, recursion_guard) { Ok(value) => { model_dict.set_item(&field.name_py, value)?; @@ -169,7 +169,7 @@ impl Validator for ModelFieldsValidator { Err(err) => return Err(err), } continue; - } else if let Some(value) = field.validator.default_value(py, Some(field.name.as_str()), &extra, slots, recursion_guard)? { + } else if let Some(value) = field.validator.default_value(py, Some(field.name.as_str()), &extra, definitions, recursion_guard)? { model_dict.set_item(&field.name_py, value)?; } else { errors.push(field.lookup_key.error( @@ -215,7 +215,7 @@ impl Validator for ModelFieldsValidator { ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); if let Some(ref validator) = self.extra_validator { - match validator.validate(py, value, &extra, slots, recursion_guard) { + match validator.validate(py, value, &extra, definitions, recursion_guard) { Ok(value) => { model_extra_dict.set_item(py_key, value)?; fields_set_vec.push(py_key.into_py(py)); @@ -260,7 +260,7 @@ impl Validator for ModelFieldsValidator { field_name: &'data str, field_value: &'data PyAny, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let dict: &PyDict = obj.downcast()?; @@ -308,7 +308,7 @@ impl Validator for ModelFieldsValidator { prepare_result( field .validator - .validate(py, field_value, &extra, slots, recursion_guard), + .validate(py, field_value, &extra, definitions, recursion_guard), ) } } else { @@ -320,7 +320,7 @@ impl Validator for ModelFieldsValidator { match self.extra_behavior { ExtraBehavior::Allow => match self.extra_validator { Some(ref validator) => { - prepare_result(validator.validate(py, field_value, &extra, slots, recursion_guard)) + prepare_result(validator.validate(py, field_value, &extra, definitions, recursion_guard)) } None => ok(field_value.to_object(py)), }, @@ -342,24 +342,24 @@ impl Validator for ModelFieldsValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.fields .iter() - .any(|f| f.validator.different_strict_behavior(build_context, ultra_strict)) + .any(|f| f.validator.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { Self::EXPECTED_TYPE } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.fields .iter_mut() - .try_for_each(|f| f.validator.complete(build_context))?; + .try_for_each(|f| f.validator.complete(definitions))?; match &mut self.extra_validator { - Some(v) => v.complete(build_context), + Some(v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/none.rs b/src/validators/none.rs index cf2428bc5..92ceb1c7c 100644 --- a/src/validators/none.rs +++ b/src/validators/none.rs @@ -5,7 +5,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct NoneValidator; @@ -16,7 +16,7 @@ impl BuildValidator for NoneValidator { fn build( _schema: &PyDict, _config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { Ok(Self.into()) } @@ -28,7 +28,7 @@ impl Validator for NoneValidator { py: Python<'data>, input: &'data impl Input<'data>, _extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match input.is_none() { @@ -39,7 +39,7 @@ impl Validator for NoneValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, _ultra_strict: bool, ) -> bool { false @@ -49,7 +49,7 @@ impl Validator for NoneValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/nullable.rs b/src/validators/nullable.rs index 327129acd..47413d608 100644 --- a/src/validators/nullable.rs +++ b/src/validators/nullable.rs @@ -7,7 +7,7 @@ use crate::errors::ValResult; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct NullableValidator { @@ -21,10 +21,10 @@ impl BuildValidator for NullableValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let schema: &PyAny = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(schema, config, build_context)?); + let validator = Box::new(build_validator(schema, config, definitions)?); let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); Ok(Self { validator, name }.into()) } @@ -36,28 +36,28 @@ impl Validator for NullableValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match input.is_none() { true => Ok(py.None()), - false => self.validator.validate(py, input, extra, slots, recursion_guard), + false => self.validator.validate(py, input, extra, definitions, recursion_guard), } } fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } diff --git a/src/validators/set.rs b/src/validators/set.rs index e16a6f08b..f8383b496 100644 --- a/src/validators/set.rs +++ b/src/validators/set.rs @@ -7,7 +7,7 @@ use crate::input::{GenericCollection, Input}; use crate::recursion_guard::RecursionGuard; use super::list::{get_items_schema, length_check}; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct SetValidator { @@ -25,10 +25,10 @@ macro_rules! set_build { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let item_validator = get_items_schema(schema, config, build_context)?; + let item_validator = get_items_schema(schema, config, definitions)?; let inner_name = item_validator.as_ref().map(|v| v.get_name()).unwrap_or("any"); let max_length = schema.get_as(pyo3::intern!(py, "max_length"))?; let generator_max_length = match schema.get_as(pyo3::intern!(py, "generator_max_length"))? { @@ -61,7 +61,7 @@ impl Validator for SetValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let seq = input.validate_set(extra.strict.unwrap_or(self.strict))?; @@ -77,7 +77,7 @@ impl Validator for SetValidator { self.generator_max_length, v, extra, - slots, + definitions, recursion_guard, )?, )?, @@ -92,12 +92,12 @@ impl Validator for SetValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { match self.item_validator { - Some(ref v) => v.different_strict_behavior(build_context, true), + Some(ref v) => v.different_strict_behavior(definitions, true), None => false, } } else { @@ -109,9 +109,9 @@ impl Validator for SetValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { match self.item_validator { - Some(ref mut v) => v.complete(build_context), + Some(ref mut v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/string.rs b/src/validators/string.rs index 41efe669c..92e2060a4 100644 --- a/src/validators/string.rs +++ b/src/validators/string.rs @@ -8,7 +8,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct StrValidator { @@ -21,7 +21,7 @@ impl BuildValidator for StrValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let con_str_validator = StrConstrainedValidator::build(schema, config)?; @@ -42,7 +42,7 @@ impl Validator for StrValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { Ok(input.validate_str(extra.strict.unwrap_or(self.strict))?.into_py(py)) @@ -50,7 +50,7 @@ impl Validator for StrValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -60,7 +60,7 @@ impl Validator for StrValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -83,7 +83,7 @@ impl Validator for StrConstrainedValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let either_str = input.validate_str(extra.strict.unwrap_or(self.strict))?; @@ -129,7 +129,7 @@ impl Validator for StrConstrainedValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -139,7 +139,7 @@ impl Validator for StrConstrainedValidator { "constrained-str" } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/time.rs b/src/validators/time.rs index 65b8013d5..04242431d 100644 --- a/src/validators/time.rs +++ b/src/validators/time.rs @@ -8,7 +8,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::{EitherTime, Input}; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct TimeValidator { @@ -30,7 +30,7 @@ impl BuildValidator for TimeValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let has_constraints = schema.get_item(intern!(py, "le")).is_some() @@ -60,7 +60,7 @@ impl Validator for TimeValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let time = input.validate_time(extra.strict.unwrap_or(self.strict))?; @@ -92,7 +92,7 @@ impl Validator for TimeValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -102,7 +102,7 @@ impl Validator for TimeValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/timedelta.rs b/src/validators/timedelta.rs index 6bf2a3f15..5d31d05ba 100644 --- a/src/validators/timedelta.rs +++ b/src/validators/timedelta.rs @@ -8,7 +8,7 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::{EitherTimedelta, Input}; use crate::recursion_guard::RecursionGuard; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct TimeDeltaValidator { @@ -29,7 +29,7 @@ impl BuildValidator for TimeDeltaValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let has_constraints = schema.get_item(intern!(py, "le")).is_some() @@ -59,7 +59,7 @@ impl Validator for TimeDeltaValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let timedelta = input.validate_timedelta(extra.strict.unwrap_or(self.strict))?; @@ -91,7 +91,7 @@ impl Validator for TimeDeltaValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -101,7 +101,7 @@ impl Validator for TimeDeltaValidator { Self::EXPECTED_TYPE } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index cbdaff221..57f3f49d7 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -8,7 +8,7 @@ use crate::input::{GenericCollection, Input}; use crate::recursion_guard::RecursionGuard; use super::list::{get_items_schema, length_check}; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct TupleVariableValidator { @@ -24,10 +24,10 @@ impl BuildValidator for TupleVariableValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); - let item_validator = get_items_schema(schema, config, build_context)?; + let item_validator = get_items_schema(schema, config, definitions)?; let inner_name = item_validator.as_ref().map(|v| v.get_name()).unwrap_or("any"); let name = format!("tuple[{inner_name}, ...]"); Ok(Self { @@ -47,7 +47,7 @@ impl Validator for TupleVariableValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let seq = input.validate_tuple(extra.strict.unwrap_or(self.strict))?; @@ -61,7 +61,7 @@ impl Validator for TupleVariableValidator { self.max_length, v, extra, - slots, + definitions, recursion_guard, )?, None => match seq { @@ -78,12 +78,12 @@ impl Validator for TupleVariableValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { match self.item_validator { - Some(ref v) => v.different_strict_behavior(build_context, true), + Some(ref v) => v.different_strict_behavior(definitions, true), None => false, } } else { @@ -95,9 +95,9 @@ impl Validator for TupleVariableValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { match self.item_validator { - Some(ref mut v) => v.complete(build_context), + Some(ref mut v) => v.complete(definitions), None => Ok(()), } } @@ -116,13 +116,13 @@ impl BuildValidator for TuplePositionalValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let items: &PyList = schema.get_as_req(intern!(py, "items_schema"))?; let validators: Vec = items .iter() - .map(|item| build_validator(item, config, build_context)) + .map(|item| build_validator(item, config, definitions)) .collect::>()?; let descr = validators.iter().map(|v| v.get_name()).collect::>().join(", "); @@ -130,7 +130,7 @@ impl BuildValidator for TuplePositionalValidator { strict: is_strict(schema, config)?, items_validators: validators, extra_validator: match schema.get_item(intern!(py, "extra_schema")) { - Some(v) => Some(Box::new(build_validator(v, config, build_context)?)), + Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, name: format!("tuple[{descr}]"), @@ -145,7 +145,7 @@ impl Validator for TuplePositionalValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let collection = input.validate_tuple(extra.strict.unwrap_or(self.strict))?; @@ -157,7 +157,7 @@ impl Validator for TuplePositionalValidator { ($collection_iter:expr) => {{ for (index, validator) in self.items_validators.iter().enumerate() { match $collection_iter.next() { - Some(item) => match validator.validate(py, item, extra, slots, recursion_guard) { + Some(item) => match validator.validate(py, item, extra, definitions, recursion_guard) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -170,7 +170,7 @@ impl Validator for TuplePositionalValidator { }, None => { if let Some(value) = - validator.default_value(py, Some(index), extra, slots, recursion_guard)? + validator.default_value(py, Some(index), extra, definitions, recursion_guard)? { output.push(value); } else { @@ -182,7 +182,7 @@ impl Validator for TuplePositionalValidator { for (index, item) in $collection_iter.enumerate() { match self.extra_validator { Some(ref extra_validator) => { - match extra_validator.validate(py, item, extra, slots, recursion_guard) { + match extra_validator.validate(py, item, extra, definitions, recursion_guard) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -240,18 +240,18 @@ impl Validator for TuplePositionalValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { if ultra_strict { if self .items_validators .iter() - .any(|v| v.different_strict_behavior(build_context, true)) + .any(|v| v.different_strict_behavior(definitions, true)) { true } else if let Some(ref v) = self.extra_validator { - v.different_strict_behavior(build_context, true) + v.different_strict_behavior(definitions, true) } else { false } @@ -264,12 +264,12 @@ impl Validator for TuplePositionalValidator { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.items_validators .iter_mut() - .try_for_each(|v| v.complete(build_context))?; + .try_for_each(|v| v.complete(definitions))?; match &mut self.extra_validator { - Some(v) => v.complete(build_context), + Some(v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index a11a61d06..6eca84ba4 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -13,7 +13,7 @@ use crate::input::{ use crate::lookup_key::LookupKey; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] struct TypedDictField { @@ -39,7 +39,7 @@ impl BuildValidator for TypedDictValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let strict = is_strict(schema, config)?; @@ -51,7 +51,7 @@ impl BuildValidator for TypedDictValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, build_context)?)), + (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), (Some(_), _) => return py_err!("extra_validator can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -65,7 +65,7 @@ impl BuildValidator for TypedDictValidator { let schema = field_info.get_as_req(intern!(py, "schema"))?; - let validator = match build_validator(schema, config, build_context) { + let validator = match build_validator(schema, config, definitions) { Ok(v) => v, Err(err) => return py_err!("Field \"{}\":\n {}", field_name, err), }; @@ -129,7 +129,7 @@ impl Validator for TypedDictValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let strict = extra.strict.unwrap_or(self.strict); @@ -175,7 +175,7 @@ impl Validator for TypedDictValidator { } match field .validator - .validate(py, value, &extra, slots, recursion_guard) + .validate(py, value, &extra, definitions, recursion_guard) { Ok(value) => { output_dict.set_item(&field.name_py, value)?; @@ -189,7 +189,7 @@ impl Validator for TypedDictValidator { Err(err) => return Err(err), } continue; - } else if let Some(value) = field.validator.default_value(py, Some(field.name.as_str()), &extra, slots, recursion_guard)? { + } else if let Some(value) = field.validator.default_value(py, Some(field.name.as_str()), &extra, definitions, recursion_guard)? { output_dict.set_item(&field.name_py, value)?; } else if field.required { errors.push(field.lookup_key.error( @@ -234,7 +234,7 @@ impl Validator for TypedDictValidator { ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); if let Some(ref validator) = self.extra_validator { - match validator.validate(py, value, &extra, slots, recursion_guard) { + match validator.validate(py, value, &extra, definitions, recursion_guard) { Ok(value) => { output_dict.set_item(py_key, value)?; } @@ -270,24 +270,24 @@ impl Validator for TypedDictValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.fields .iter() - .any(|f| f.validator.different_strict_behavior(build_context, ultra_strict)) + .any(|f| f.validator.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { Self::EXPECTED_TYPE } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.fields .iter_mut() - .try_for_each(|f| f.validator.complete(build_context))?; + .try_for_each(|f| f.validator.complete(definitions))?; match &mut self.extra_validator { - Some(v) => v.complete(build_context), + Some(v) => v.complete(definitions), None => Ok(()), } } diff --git a/src/validators/union.rs b/src/validators/union.rs index a4d5ff169..0499ec115 100644 --- a/src/validators/union.rs +++ b/src/validators/union.rs @@ -16,7 +16,7 @@ use crate::lookup_key::LookupKey; use crate::recursion_guard::RecursionGuard; use super::custom_error::CustomError; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub struct UnionValidator { @@ -34,13 +34,13 @@ impl BuildValidator for UnionValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let choices: Vec = schema .get_as_req::<&PyList>(intern!(py, "choices"))? .iter() - .map(|choice| build_validator(choice, config, build_context)) + .map(|choice| build_validator(choice, config, definitions)) .collect::>>()?; let auto_collapse = || schema.get_as_req(intern!(py, "auto_collapse")).unwrap_or(true); @@ -52,7 +52,7 @@ impl BuildValidator for UnionValidator { Ok(Self { choices, - custom_error: CustomError::build(schema, config, build_context)?, + custom_error: CustomError::build(schema, config, definitions)?, strict: is_strict(schema, config)?, name: format!("{}[{descr}]", Self::EXPECTED_TYPE), strict_required: true, @@ -84,7 +84,7 @@ impl Validator for UnionValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if self.ultra_strict_required { @@ -93,7 +93,7 @@ impl Validator for UnionValidator { if let Some(res) = self .choices .iter() - .map(|validator| validator.validate(py, input, &ultra_strict_extra, slots, recursion_guard)) + .map(|validator| validator.validate(py, input, &ultra_strict_extra, definitions, recursion_guard)) .find(ValResult::is_ok) { return res; @@ -108,7 +108,7 @@ impl Validator for UnionValidator { let strict_extra = extra.as_strict(false); for validator in &self.choices { - let line_errors = match validator.validate(py, input, &strict_extra, slots, recursion_guard) { + let line_errors = match validator.validate(py, input, &strict_extra, definitions, recursion_guard) { Err(ValError::LineErrors(line_errors)) => line_errors, otherwise => return otherwise, }; @@ -131,7 +131,7 @@ impl Validator for UnionValidator { if let Some(res) = self .choices .iter() - .map(|validator| validator.validate(py, input, &strict_extra, slots, recursion_guard)) + .map(|validator| validator.validate(py, input, &strict_extra, definitions, recursion_guard)) .find(ValResult::is_ok) { return res; @@ -145,7 +145,7 @@ impl Validator for UnionValidator { // 2nd pass: check if the value can be coerced into one of the Union types, e.g. use validate for validator in &self.choices { - let line_errors = match validator.validate(py, input, extra, slots, recursion_guard) { + let line_errors = match validator.validate(py, input, extra, definitions, recursion_guard) { Err(ValError::LineErrors(line_errors)) => line_errors, success => return success, }; @@ -165,22 +165,22 @@ impl Validator for UnionValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.choices .iter() - .any(|v| v.different_strict_behavior(build_context, ultra_strict)) + .any(|v| v.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.choices.iter_mut().try_for_each(|v| v.complete(build_context))?; - self.strict_required = self.different_strict_behavior(Some(build_context), false); - self.ultra_strict_required = self.different_strict_behavior(Some(build_context), true); + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.choices.iter_mut().try_for_each(|v| v.complete(definitions))?; + self.strict_required = self.different_strict_behavior(Some(definitions), false); + self.ultra_strict_required = self.different_strict_behavior(Some(definitions), true); Ok(()) } } @@ -280,7 +280,7 @@ impl BuildValidator for TaggedUnionValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let discriminator = Discriminator::new(py, schema.get_as_req(intern!(py, "discriminator"))?)?; @@ -301,7 +301,7 @@ impl BuildValidator for TaggedUnionValidator { continue; } - let validator = build_validator(value, config, build_context)?; + let validator = build_validator(value, config, definitions)?; let tag_repr = tag.repr(); if first { first = false; @@ -353,7 +353,7 @@ impl BuildValidator for TaggedUnionValidator { discriminator, from_attributes, strict: is_strict(schema, config)?, - custom_error: CustomError::build(schema, config, build_context)?, + custom_error: CustomError::build(schema, config, definitions)?, tags_repr, discriminator_repr, name: format!("{}[{descr}]", Self::EXPECTED_TYPE), @@ -368,7 +368,7 @@ impl Validator for TaggedUnionValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { match self.discriminator { @@ -396,7 +396,7 @@ impl Validator for TaggedUnionValidator { GenericMapping::PyMapping(mapping) => find_validator!(py_get_mapping_item, mapping), GenericMapping::JsonObject(mapping) => find_validator!(json_get, mapping), }?; - self.find_call_validator(py, &tag, input, extra, slots, recursion_guard) + self.find_call_validator(py, &tag, input, extra, definitions, recursion_guard) } Discriminator::Function(ref func) => { let tag = func.call1(py, (input.to_object(py),))?; @@ -404,7 +404,14 @@ impl Validator for TaggedUnionValidator { Err(self.tag_not_found(input)) } else { let tag: &PyAny = tag.downcast(py)?; - self.find_call_validator(py, &(ChoiceKey::from_py(tag)?), input, extra, slots, recursion_guard) + self.find_call_validator( + py, + &(ChoiceKey::from_py(tag)?), + input, + extra, + definitions, + recursion_guard, + ) } } Discriminator::SelfSchema => self.find_call_validator( @@ -412,7 +419,7 @@ impl Validator for TaggedUnionValidator { &ChoiceKey::Str(self.self_schema_tag(py, input)?.into_owned()), input, extra, - slots, + definitions, recursion_guard, ), } @@ -420,22 +427,22 @@ impl Validator for TaggedUnionValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { self.choices .values() - .any(|v| v.different_strict_behavior(build_context, ultra_strict)) + .any(|v| v.different_strict_behavior(definitions, ultra_strict)) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { self.choices .iter_mut() - .try_for_each(|(_, validator)| validator.complete(build_context)) + .try_for_each(|(_, validator)| validator.complete(definitions)) } } @@ -491,18 +498,18 @@ impl TaggedUnionValidator { tag: &ChoiceKey, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { if let Some(validator) = self.choices.get(tag) { - return match validator.validate(py, input, extra, slots, recursion_guard) { + return match validator.validate(py, input, extra, definitions, recursion_guard) { Ok(res) => Ok(res), Err(err) => Err(err.with_outer_location(tag.into())), }; } else if let Some(ref repeat_choices) = self.repeat_choices { if let Some(choice_tag) = repeat_choices.get(tag) { let validator = &self.choices[choice_tag]; - return match validator.validate(py, input, extra, slots, recursion_guard) { + return match validator.validate(py, input, extra, definitions, recursion_guard) { Ok(res) => Ok(res), Err(err) => Err(err.with_outer_location(tag.into())), }; diff --git a/src/validators/url.rs b/src/validators/url.rs index 6a1a4c9e3..8e7c77248 100644 --- a/src/validators/url.rs +++ b/src/validators/url.rs @@ -16,7 +16,7 @@ use crate::recursion_guard::RecursionGuard; use crate::url::{schema_is_special, PyMultiHostUrl, PyUrl}; use super::literal::expected_repr_name; -use super::{BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; type AllowedSchemas = Option<(AHashSet, String)>; @@ -38,7 +38,7 @@ impl BuildValidator for UrlValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let (allowed_schemes, name) = get_allowed_schemas(schema, Self::EXPECTED_TYPE)?; @@ -62,7 +62,7 @@ impl Validator for UrlValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let mut lib_url = self.get_url(input, extra.strict.unwrap_or(self.strict))?; @@ -88,7 +88,7 @@ impl Validator for UrlValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -98,7 +98,7 @@ impl Validator for UrlValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } @@ -161,7 +161,7 @@ impl BuildValidator for MultiHostUrlValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - _build_context: &mut BuildContext, + _definitions: &mut DefinitionsBuilder, ) -> PyResult { let (allowed_schemes, name) = get_allowed_schemas(schema, Self::EXPECTED_TYPE)?; @@ -191,7 +191,7 @@ impl Validator for MultiHostUrlValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - _slots: &'data [CombinedValidator], + _definitions: &'data Definitions, _recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { let mut multi_url = self.get_url(input, extra.strict.unwrap_or(self.strict))?; @@ -216,7 +216,7 @@ impl Validator for MultiHostUrlValidator { fn different_strict_behavior( &self, - _build_context: Option<&BuildContext>, + _definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { !ultra_strict @@ -226,7 +226,7 @@ impl Validator for MultiHostUrlValidator { &self.name } - fn complete(&mut self, _build_context: &BuildContext) -> PyResult<()> { + fn complete(&mut self, _definitions: &DefinitionsBuilder) -> PyResult<()> { Ok(()) } } diff --git a/src/validators/with_default.rs b/src/validators/with_default.rs index 1c59070f8..fe27feca8 100644 --- a/src/validators/with_default.rs +++ b/src/validators/with_default.rs @@ -7,7 +7,7 @@ use crate::errors::{LocItem, ValError, ValResult}; use crate::input::Input; use crate::recursion_guard::RecursionGuard; -use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator}; +use super::{build_validator, BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator}; #[derive(Debug, Clone)] pub enum DefaultType { @@ -61,7 +61,7 @@ impl BuildValidator for WithDefaultValidator { fn build( schema: &PyDict, config: Option<&PyDict>, - build_context: &mut BuildContext, + definitions: &mut DefinitionsBuilder, ) -> PyResult { let py = schema.py(); let default = DefaultType::new(schema)?; @@ -80,7 +80,7 @@ impl BuildValidator for WithDefaultValidator { }; let sub_schema: &PyAny = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(sub_schema, config, build_context)?); + let validator = Box::new(build_validator(sub_schema, config, definitions)?); let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); Ok(Self { @@ -100,15 +100,15 @@ impl Validator for WithDefaultValidator { py: Python<'data>, input: &'data impl Input<'data>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, PyObject> { - match self.validator.validate(py, input, extra, slots, recursion_guard) { + match self.validator.validate(py, input, extra, definitions, recursion_guard) { Ok(v) => Ok(v), Err(e) => match self.on_error { OnError::Raise => Err(e), OnError::Default => Ok(self - .default_value(py, None::, extra, slots, recursion_guard)? + .default_value(py, None::, extra, definitions, recursion_guard)? .unwrap()), OnError::Omit => Err(ValError::Omit), }, @@ -120,13 +120,13 @@ impl Validator for WithDefaultValidator { py: Python<'data>, outer_loc: Option>, extra: &Extra, - slots: &'data [CombinedValidator], + definitions: &'data Definitions, recursion_guard: &'s mut RecursionGuard, ) -> ValResult<'data, Option> { match self.default.default_value(py)? { Some(dft) => { if self.validate_default { - match self.validate(py, dft.into_ref(py), extra, slots, recursion_guard) { + match self.validate(py, dft.into_ref(py), extra, definitions, recursion_guard) { Ok(v) => Ok(Some(v)), Err(e) => { if let Some(outer_loc) = outer_loc { @@ -146,18 +146,18 @@ impl Validator for WithDefaultValidator { fn different_strict_behavior( &self, - build_context: Option<&BuildContext>, + definitions: Option<&DefinitionsBuilder>, ultra_strict: bool, ) -> bool { - self.validator.different_strict_behavior(build_context, ultra_strict) + self.validator.different_strict_behavior(definitions, ultra_strict) } fn get_name(&self) -> &str { &self.name } - fn complete(&mut self, build_context: &BuildContext) -> PyResult<()> { - self.validator.complete(build_context) + fn complete(&mut self, definitions: &DefinitionsBuilder) -> PyResult<()> { + self.validator.complete(definitions) } } diff --git a/tests/serializers/test_any.py b/tests/serializers/test_any.py index dcf63b650..30350f654 100644 --- a/tests/serializers/test_any.py +++ b/tests/serializers/test_any.py @@ -24,7 +24,7 @@ def any_serializer(): def test_repr(any_serializer): - assert plain_repr(any_serializer) == 'SchemaSerializer(serializer=Any(AnySerializer),slots=[])' + assert plain_repr(any_serializer) == 'SchemaSerializer(serializer=Any(AnySerializer),definitions=[])' @dataclasses.dataclass(frozen=True) @@ -116,7 +116,7 @@ def test_any_json(any_serializer, value, expected_json): def test_other_type(): """Types with no serializer, fall back to any serializer""" v = SchemaSerializer(core_schema.is_instance_schema(int)) - assert plain_repr(v) == 'SchemaSerializer(serializer=Any(AnySerializer),slots=[])' + assert plain_repr(v) == 'SchemaSerializer(serializer=Any(AnySerializer),definitions=[])' assert v.to_json('foobar') == b'"foobar"' diff --git a/tests/serializers/test_definitions.py b/tests/serializers/test_definitions.py index 4c8913788..b52e1e27f 100644 --- a/tests/serializers/test_definitions.py +++ b/tests/serializers/test_definitions.py @@ -2,8 +2,6 @@ from pydantic_core import SchemaError, SchemaSerializer, core_schema -from ..conftest import plain_repr - def test_custom_ser(): s = SchemaSerializer( @@ -13,7 +11,6 @@ def test_custom_ser(): ) ) assert s.to_python([1, 2, 3]) == ['1', '2', '3'] - assert plain_repr(s).endswith('slots=[])') def test_ignored_def(): @@ -24,7 +21,6 @@ def test_ignored_def(): ) ) assert s.to_python([1, 2, 3]) == [1, 2, 3] - assert plain_repr(s).endswith('slots=[])') def test_def_error(): diff --git a/tests/serializers/test_other.py b/tests/serializers/test_other.py index a760aeb41..06183d773 100644 --- a/tests/serializers/test_other.py +++ b/tests/serializers/test_other.py @@ -9,7 +9,7 @@ def test_chain(): s = SchemaSerializer(core_schema.chain_schema([core_schema.str_schema(), core_schema.int_schema()])) # insert_assert(plain_repr(s)) - assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),slots=[])' + assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),definitions=[])' assert s.to_python(1) == 1 assert s.to_json(1) == b'1' @@ -19,25 +19,25 @@ def test_function_plain(): s = SchemaSerializer(core_schema.general_plain_validator_function(lambda v, info: v + 1)) # can't infer the type from plain function validators # insert_assert(plain_repr(s)) - assert plain_repr(s) == 'SchemaSerializer(serializer=Any(AnySerializer),slots=[])' + assert plain_repr(s) == 'SchemaSerializer(serializer=Any(AnySerializer),definitions=[])' def test_function_before(): s = SchemaSerializer(core_schema.general_before_validator_function(lambda v, info: v + 1, core_schema.int_schema())) # insert_assert(plain_repr(s)) - assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),slots=[])' + assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),definitions=[])' def test_function_after(): s = SchemaSerializer(core_schema.general_after_validator_function(lambda v, info: v + 1, core_schema.int_schema())) # insert_assert(plain_repr(s)) - assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),slots=[])' + assert plain_repr(s) == 'SchemaSerializer(serializer=Int(IntSerializer),definitions=[])' def test_lax_or_strict(): s = SchemaSerializer(core_schema.lax_or_strict_schema(core_schema.int_schema(), core_schema.str_schema())) # insert_assert(plain_repr(s)) - assert plain_repr(s) == 'SchemaSerializer(serializer=Str(StrSerializer),slots=[])' + assert plain_repr(s) == 'SchemaSerializer(serializer=Str(StrSerializer),definitions=[])' assert s.to_json('abc') == b'"abc"' with pytest.warns(UserWarning, match='Expected `str` but got `int` - serialized value may not be as expected'): diff --git a/tests/test_build.py b/tests/test_build.py index 36e378d4d..210f5be8f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -3,6 +3,7 @@ import pytest from pydantic_core import SchemaError, SchemaValidator, __version__ +from pydantic_core import core_schema as cs def test_build_error_type(): @@ -98,3 +99,42 @@ def test_try_self_schema_discriminator(): """Trying to use self-schema when it shouldn't be used""" v = SchemaValidator({'type': 'tagged-union', 'choices': {'int': {'type': 'int'}}, 'discriminator': 'self-schema'}) assert 'discriminator: LookupKey' in repr(v) + + +def test_build_recursive_schema_from_defs() -> None: + """ + Validate a schema representing mutually recursive models, analogous to the following JSON schema: + + ```json + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [{"$ref": "#/$defs/a"}], + "$defs": { + "a": { + "type": "object", + "properties": {"b": {"type": "array", "items": {"$ref": "#/$defs/a"}}}, + "required": ["b"], + }, + "b": { + "type": "object", + "properties": {"a": {"type": "array", "items": {"$ref": "#/$defs/b"}}}, + "required": ["a"], + }, + }, + } + ``` + """ + + s = cs.definitions_schema( + cs.definition_reference_schema(schema_ref='a'), + [ + cs.typed_dict_schema( + {'b': cs.typed_dict_field(cs.list_schema(cs.definition_reference_schema('b')))}, ref='a' + ), + cs.typed_dict_schema( + {'a': cs.typed_dict_field(cs.list_schema(cs.definition_reference_schema('a')))}, ref='b' + ), + ], + ) + + SchemaValidator(s) diff --git a/tests/validators/test_bool.py b/tests/validators/test_bool.py index 9fbe8db2f..a193dbb09 100644 --- a/tests/validators/test_bool.py +++ b/tests/validators/test_bool.py @@ -77,9 +77,9 @@ def test_bool_error(): def test_bool_repr(): v = SchemaValidator({'type': 'bool'}) - assert plain_repr(v) == 'SchemaValidator(title="bool",validator=Bool(BoolValidator{strict:false}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="bool",validator=Bool(BoolValidator{strict:false}),definitions=[])' v = SchemaValidator({'type': 'bool', 'strict': True}) - assert plain_repr(v) == 'SchemaValidator(title="bool",validator=Bool(BoolValidator{strict:true}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="bool",validator=Bool(BoolValidator{strict:true}),definitions=[])' def test_bool_key(py_and_json: PyAndJson): diff --git a/tests/validators/test_definitions.py b/tests/validators/test_definitions.py index fce6b49ca..bff170890 100644 --- a/tests/validators/test_definitions.py +++ b/tests/validators/test_definitions.py @@ -16,8 +16,6 @@ def test_list_with_def(): assert v.validate_json(b'[1, 2, "3"]') == [1, 2, 3] r = plain_repr(v) assert r.startswith('SchemaValidator(title="list[int]",') - # definitions aren't used in slots - assert r.endswith('slots=[])') def test_ignored_def(): @@ -29,14 +27,12 @@ def test_ignored_def(): assert v.validate_python([1, 2, '3']) == [1, 2, 3] r = plain_repr(v) assert r.startswith('SchemaValidator(title="list[int]",') - # definitions aren't used in slots - assert r.endswith('slots=[])') def test_extract_used_refs_ignores_metadata(): v = SchemaValidator(core_schema.any_schema(metadata={'type': 'definition-ref'})) assert v.validate_python([1, 2, 3]) == [1, 2, 3] - assert plain_repr(v).endswith('slots=[])') + assert plain_repr(v).endswith('definitions=[])') def test_check_ref_used_ignores_metadata(): @@ -46,7 +42,7 @@ def test_check_ref_used_ignores_metadata(): ) ) assert v.validate_python([1, 2, 3]) == [1, 2, 3] - assert plain_repr(v).endswith('slots=[])') + # assert plain_repr(v).endswith('definitions=[])') def test_def_error(): @@ -74,7 +70,7 @@ def test_dict_repeat(): ) assert v.validate_python({'1': '2', 3: '4'}) == {1: 2, 3: 4} assert v.validate_json(b'{"1": 2, "3": "4"}') == {1: 2, 3: 4} - assert plain_repr(v).endswith('slots=[])') + # assert plain_repr(v).endswith('definitions=[])') def test_repeated_ref(): diff --git a/tests/validators/test_definitions_recursive.py b/tests/validators/test_definitions_recursive.py index dcec609f5..8d09ee5a6 100644 --- a/tests/validators/test_definitions_recursive.py +++ b/tests/validators/test_definitions_recursive.py @@ -1,9 +1,10 @@ -from typing import Optional +from dataclasses import dataclass +from typing import List, Optional import pytest from dirty_equals import AnyThing, HasAttributes, IsList, IsPartialDict, IsStr, IsTuple -from pydantic_core import SchemaError, SchemaValidator, ValidationError, core_schema +from pydantic_core import SchemaError, SchemaValidator, ValidationError, __version__, core_schema from ..conftest import Err, plain_repr from .test_typed_dict import Cls @@ -32,7 +33,7 @@ def test_branch_nullable(): assert plain_repr(v).startswith( 'SchemaValidator(title="typed-dict",validator=DefinitionRef(DefinitionRefValidator{' ) - assert ',slots=[TypedDict(TypedDictValidator{' in plain_repr(v) + assert ',definitions=[TypedDict(TypedDictValidator{' in plain_repr(v) assert v.validate_python({'name': 'root', 'sub_branch': {'name': 'b1'}}) == ( {'name': 'root', 'sub_branch': {'name': 'b1', 'sub_branch': None}} @@ -77,7 +78,7 @@ def test_branch_nullable_definitions(): assert v.validate_python({'name': 'root', 'sub_branch': {'name': 'b1', 'sub_branch': {'name': 'b2'}}}) == ( {'name': 'root', 'sub_branch': {'name': 'b1', 'sub_branch': {'name': 'b2', 'sub_branch': None}}} ) - assert ',slots=[TypedDict(TypedDictValidator{' in plain_repr(v) + assert ',definitions=[TypedDict(TypedDictValidator{' in plain_repr(v) def test_unused_ref(): @@ -91,9 +92,7 @@ def test_unused_ref(): }, } ) - assert plain_repr(v).startswith('SchemaValidator(title="typed-dict",validator=TypedDict(TypedDictValidator') assert v.validate_python({'name': 'root', 'other': '4'}) == {'name': 'root', 'other': 4} - assert ',slots=[]' in plain_repr(v) def test_nullable_error(): @@ -165,7 +164,7 @@ def test_list(): 'branches': [{'width': 2, 'branches': None}, {'width': 3, 'branches': [{'width': 4, 'branches': None}]}], } ) - assert ',slots=[TypedDict(TypedDictValidator{' in plain_repr(v) + assert ',definitions=[TypedDict(TypedDictValidator{' in plain_repr(v) def test_multiple_intertwined(): @@ -283,7 +282,7 @@ class Branch: def test_invalid_schema(): - with pytest.raises(SchemaError, match="Slots Error: ref 'Branch' not found"): + with pytest.raises(SchemaError, match='Definitions error: attempted to use `Branch` before it was filled'): SchemaValidator( { 'type': 'list', @@ -330,8 +329,6 @@ def test_outside_parent(): 'tuple1': (1, 1, 'frog'), 'tuple2': (2, 2, 'toad'), } - # the definition goes into reusable and gets "inlined" into the schema - assert ',slots=[]' in plain_repr(v) def test_recursion_branch(): @@ -353,7 +350,7 @@ def test_recursion_branch(): }, {'from_attributes': True}, ) - assert ',slots=[TypedDict(TypedDictValidator{' in plain_repr(v) + assert ',definitions=[TypedDict(TypedDictValidator{' in plain_repr(v) assert v.validate_python({'name': 'root'}) == {'name': 'root', 'branch': None} assert v.validate_python({'name': 'root', 'branch': {'name': 'b1', 'branch': None}}) == { @@ -427,7 +424,7 @@ def test_definition_list(): v = SchemaValidator( {'type': 'list', 'ref': 'the-list', 'items_schema': {'type': 'definition-ref', 'schema_ref': 'the-list'}} ) - assert ',slots=[List(ListValidator{' in plain_repr(v) + assert ',definitions=[List(ListValidator{' in plain_repr(v) assert v.validate_python([]) == [] assert v.validate_python([[]]) == [[]] @@ -813,3 +810,104 @@ def test_error_inside_definition_wrapper(): ' SchemaError: Error building "default" validator:\n' " SchemaError: 'default' and 'default_factory' cannot be used together" ) + + +def test_recursive_definitions_schema() -> None: + s = core_schema.definitions_schema( + core_schema.definition_reference_schema(schema_ref='a'), + [ + core_schema.typed_dict_schema( + { + 'b': core_schema.typed_dict_field( + core_schema.list_schema(core_schema.definition_reference_schema('b')) + ) + }, + ref='a', + ), + core_schema.typed_dict_schema( + { + 'a': core_schema.typed_dict_field( + core_schema.list_schema(core_schema.definition_reference_schema('a')) + ) + }, + ref='b', + ), + ], + ) + + v = SchemaValidator(s) + + assert v.validate_python({'b': [{'a': []}]}) == {'b': [{'a': []}]} + + with pytest.raises(ValidationError) as exc_info: + v.validate_python({'b': [{'a': {}}]}) + + assert exc_info.value.errors() == [ + { + 'type': 'list_type', + 'loc': ('b', 0, 'a'), + 'msg': 'Input should be a valid list', + 'input': {}, + 'url': f'https://errors.pydantic.dev/{__version__}/v/list_type', + } + ] + + +def test_unsorted_definitions_schema() -> None: + s = core_schema.definitions_schema( + core_schema.definition_reference_schema(schema_ref='td'), + [ + core_schema.typed_dict_schema( + {'x': core_schema.typed_dict_field(core_schema.definition_reference_schema('int'))}, ref='td' + ), + core_schema.int_schema(ref='int'), + ], + ) + + v = SchemaValidator(s) + + assert v.validate_python({'x': 123}) == {'x': 123} + + with pytest.raises(ValidationError): + v.validate_python({'x': 'abc'}) + + +def test_validate_assignment() -> None: + @dataclass + class Model: + x: List['Model'] + + schema = core_schema.dataclass_schema( + Model, + core_schema.dataclass_args_schema( + 'Model', + [ + core_schema.dataclass_field( + name='x', + schema=core_schema.list_schema(core_schema.definition_reference_schema('model')), + kw_only=False, + ) + ], + ), + ref='model', + ) + v = SchemaValidator(schema, config=core_schema.CoreConfig(revalidate_instances='always')) + + data = [Model(x=[Model(x=[])])] + instance = Model(x=[]) + v.validate_assignment(instance, 'x', data) + assert instance.x == data + + with pytest.raises(ValidationError) as exc_info: + v.validate_assignment(instance, 'x', [Model(x=[Model(x=[Model(x=[123])])])]) + + assert exc_info.value.errors() == [ + { + 'type': 'dataclass_type', + 'loc': ('x', 0, 'x', 0, 'x', 0, 'x', 0), + 'msg': 'Input should be a dictionary or an instance of Model', + 'input': 123, + 'ctx': {'dataclass_name': 'Model'}, + 'url': f'https://errors.pydantic.dev/{__version__}/v/dataclass_type', + } + ] diff --git a/tests/validators/test_float.py b/tests/validators/test_float.py index d6e1a8f50..808bdce1f 100644 --- a/tests/validators/test_float.py +++ b/tests/validators/test_float.py @@ -177,12 +177,12 @@ def test_float_repr(): v = SchemaValidator({'type': 'float'}) assert ( plain_repr(v) - == 'SchemaValidator(title="float",validator=Float(FloatValidator{strict:false,allow_inf_nan:true}),slots=[])' + == 'SchemaValidator(title="float",validator=Float(FloatValidator{strict:false,allow_inf_nan:true}),definitions=[])' # noqa: E501 ) v = SchemaValidator({'type': 'float', 'strict': True}) assert ( plain_repr(v) - == 'SchemaValidator(title="float",validator=Float(FloatValidator{strict:true,allow_inf_nan:true}),slots=[])' + == 'SchemaValidator(title="float",validator=Float(FloatValidator{strict:true,allow_inf_nan:true}),definitions=[])' # noqa: E501 ) v = SchemaValidator({'type': 'float', 'multiple_of': 7}) assert plain_repr(v).startswith('SchemaValidator(title="constrained-float",validator=ConstrainedFloat(') diff --git a/tests/validators/test_frozenset.py b/tests/validators/test_frozenset.py index ec814c550..1d932e291 100644 --- a/tests/validators/test_frozenset.py +++ b/tests/validators/test_frozenset.py @@ -250,7 +250,7 @@ def test_repr(): 'validator=FrozenSet(FrozenSetValidator{' 'strict:true,item_validator:None,min_length:Some(42),max_length:None,generator_max_length:None,' 'name:"frozenset[any]"' - '}),slots=[])' + '}),definitions=[])' ) diff --git a/tests/validators/test_int.py b/tests/validators/test_int.py index b04000917..6857025cd 100644 --- a/tests/validators/test_int.py +++ b/tests/validators/test_int.py @@ -205,9 +205,9 @@ def test_union_int_simple(py_and_json: PyAndJson): def test_int_repr(): v = SchemaValidator({'type': 'int'}) - assert plain_repr(v) == 'SchemaValidator(title="int",validator=Int(IntValidator{strict:false}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="int",validator=Int(IntValidator{strict:false}),definitions=[])' v = SchemaValidator({'type': 'int', 'strict': True}) - assert plain_repr(v) == 'SchemaValidator(title="int",validator=Int(IntValidator{strict:true}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="int",validator=Int(IntValidator{strict:true}),definitions=[])' v = SchemaValidator({'type': 'int', 'multiple_of': 7}) assert plain_repr(v).startswith('SchemaValidator(title="constrained-int",validator=ConstrainedInt(') diff --git a/tests/validators/test_string.py b/tests/validators/test_string.py index 9e772720e..5be5be744 100644 --- a/tests/validators/test_string.py +++ b/tests/validators/test_string.py @@ -185,7 +185,7 @@ def test_regex_error(): def test_default_validator(): v = SchemaValidator(core_schema.str_schema(strict=True, to_lower=False), {'str_strip_whitespace': False}) - assert plain_repr(v) == 'SchemaValidator(title="str",validator=Str(StrValidator{strict:true}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="str",validator=Str(StrValidator{strict:true}),definitions=[])' @pytest.fixture(scope='session', name='FruitEnum') diff --git a/tests/validators/test_union.py b/tests/validators/test_union.py index 493a2b655..e580550d1 100644 --- a/tests/validators/test_union.py +++ b/tests/validators/test_union.py @@ -256,7 +256,7 @@ def test_empty_choices(): def test_one_choice(): v = SchemaValidator({'type': 'union', 'choices': [{'type': 'str'}]}) - assert plain_repr(v) == 'SchemaValidator(title="str",validator=Str(StrValidator{strict:false}),slots=[])' + assert plain_repr(v) == 'SchemaValidator(title="str",validator=Str(StrValidator{strict:false}),definitions=[])' assert v.validate_python('hello') == 'hello'