Skip to content

Commit adf99b8

Browse files
committed
Add missing file
1 parent 9e196fe commit adf99b8

File tree

1 file changed

+326
-0
lines changed

1 file changed

+326
-0
lines changed

src/validators/sets.rs

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
use pyo3::prelude::*;
2+
use pyo3::types::{PyDict, PyFrozenSet, PySet};
3+
4+
use crate::build_tools::SchemaDict;
5+
use crate::errors::{ErrorType, ValResult};
6+
use crate::input::Input;
7+
use crate::recursion_guard::RecursionGuard;
8+
9+
use super::list::get_items_schema;
10+
use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator};
11+
12+
use crate::errors::{LocItem, ValError};
13+
use crate::input::{iterator, GenericIterable, JsonInput};
14+
15+
const MAX_LENGTH_GEN_MULTIPLE: usize = 10;
16+
17+
#[derive(Debug, Clone)]
18+
struct IntoSetValidator {
19+
strict: bool,
20+
item_validator: Option<Box<CombinedValidator>>,
21+
min_length: Option<usize>,
22+
max_length: Option<usize>,
23+
generator_max_length: Option<usize>,
24+
name: String,
25+
}
26+
27+
// Grouping of parameters that get passed around to reduce number of fn params
28+
struct Extras<'s, 'data> {
29+
py: Python<'data>,
30+
extra: &'s Extra<'s>,
31+
definitions: &'data Definitions<CombinedValidator>,
32+
recursion_guard: &'s mut RecursionGuard,
33+
}
34+
35+
enum SetType {
36+
FrozenSet,
37+
Set,
38+
}
39+
40+
impl IntoSetValidator {
41+
pub fn build(
42+
schema: &PyDict,
43+
config: Option<&PyDict>,
44+
definitions: &mut DefinitionsBuilder<CombinedValidator>,
45+
expected_type: &str,
46+
) -> PyResult<Self> {
47+
let py = schema.py();
48+
let item_validator = get_items_schema(schema, config, definitions)?;
49+
let inner_name = item_validator.as_ref().map(|v| v.get_name()).unwrap_or("any");
50+
let max_length = schema.get_as(pyo3::intern!(py, "max_length"))?;
51+
let generator_max_length = match schema.get_as(pyo3::intern!(py, "generator_max_length"))? {
52+
Some(v) => Some(v),
53+
None => max_length.map(|v| v * MAX_LENGTH_GEN_MULTIPLE),
54+
};
55+
let name = format!("{}[{}]", expected_type, inner_name);
56+
Ok(Self {
57+
strict: crate::build_tools::is_strict(schema, config)?,
58+
item_validator,
59+
min_length: schema.get_as(pyo3::intern!(py, "min_length"))?,
60+
max_length,
61+
generator_max_length,
62+
name,
63+
})
64+
}
65+
66+
#[allow(clippy::too_many_arguments)]
67+
pub fn validate_into_set<'s, 'data>(
68+
&'s self,
69+
py: Python<'data>,
70+
input: &'data impl Input<'data>,
71+
extra: &'s Extra<'s>,
72+
definitions: &'data Definitions<CombinedValidator>,
73+
recursion_guard: &'s mut RecursionGuard,
74+
// small breakage of encapsulation to avoid a lot of code duplication / macros
75+
set_type: SetType,
76+
) -> ValResult<'data, &'data PySet> {
77+
let strict = extra.strict.unwrap_or(self.strict);
78+
79+
let mut extras: Extras = Extras {
80+
py,
81+
extra,
82+
definitions,
83+
recursion_guard,
84+
};
85+
86+
let length_constraints = iterator::LengthConstraints {
87+
min_length: self.min_length,
88+
max_length: self.max_length,
89+
max_input_length: self.generator_max_length,
90+
};
91+
92+
let make_output = |_capacity: usize| PySet::new(py, Vec::<&PyAny>::new());
93+
94+
let mut output_func = |output: &mut &PySet, ob: PyObject| -> ValResult<'data, usize> {
95+
output.add(ob)?;
96+
Ok(output.len())
97+
};
98+
99+
let mut json_validator_func =
100+
|extras: &mut Extras<'s, 'data>, loc: LocItem, ob: &'data JsonInput| -> ValResult<'data, PyObject> {
101+
match &self.item_validator {
102+
Some(v) => v
103+
.validate(extras.py, ob, extras.extra, extras.definitions, extras.recursion_guard)
104+
.map_err(|e| e.with_outer_location(loc)),
105+
None => Ok(ob.to_object(py)),
106+
}
107+
};
108+
109+
let mut python_validator_func =
110+
|extras: &mut Extras<'s, 'data>, loc: LocItem, ob: &'data PyAny| -> ValResult<'data, PyObject> {
111+
match &self.item_validator {
112+
Some(v) => v
113+
.validate(extras.py, ob, extras.extra, extras.definitions, extras.recursion_guard)
114+
.map_err(|e| e.with_outer_location(loc)),
115+
None => Ok(ob.to_object(py)),
116+
}
117+
};
118+
119+
let (field_type, error_type) = match set_type {
120+
SetType::FrozenSet => ("Frozenset", ErrorType::FrozenSetType),
121+
SetType::Set => ("Set", ErrorType::SetType),
122+
};
123+
124+
let generic_iterable = input
125+
.extract_iterable()
126+
.map_err(|_| ValError::new(error_type.clone(), input))?;
127+
let output = match (generic_iterable, strict, set_type) {
128+
// Always allow actual frozensets or JSON arrays
129+
(GenericIterable::JsonArray(iter), _, _) => iterator::validate_iterable(
130+
py,
131+
iter.iter().map(Ok),
132+
&mut json_validator_func,
133+
&mut output_func,
134+
length_constraints,
135+
field_type,
136+
input,
137+
&mut extras,
138+
make_output,
139+
Some(iter.len()),
140+
false,
141+
),
142+
// See note above about accept_set_in_strict_mode and breaking encapsulation
143+
// accept_set_in_strict_mode is being used as a flag to determine if we should treat Set or FrozenSet as our "strict" type
144+
(GenericIterable::FrozenSet(iter), _, SetType::FrozenSet) => iterator::validate_iterable(
145+
py,
146+
iter.iter().map(Ok),
147+
&mut python_validator_func,
148+
&mut output_func,
149+
length_constraints,
150+
field_type,
151+
input,
152+
&mut extras,
153+
make_output,
154+
Some(iter.len()),
155+
false,
156+
),
157+
(GenericIterable::Set(iter), _, SetType::Set) => iterator::validate_iterable(
158+
py,
159+
iter.iter().map(Ok),
160+
&mut python_validator_func,
161+
&mut output_func,
162+
length_constraints,
163+
field_type,
164+
input,
165+
&mut extras,
166+
make_output,
167+
Some(iter.len()),
168+
false,
169+
),
170+
// If not in strict mode we also accept any iterable except str/bytes
171+
(GenericIterable::String(_) | GenericIterable::Bytes(_), _, _) => {
172+
return Err(ValError::new(error_type, input))
173+
}
174+
(generic_iterable, false, _) => match generic_iterable.into_sequence_iterator(py) {
175+
Ok(iter) => {
176+
let len = iter.size_hint().1;
177+
iterator::validate_iterable(
178+
py,
179+
iter,
180+
&mut python_validator_func,
181+
&mut output_func,
182+
length_constraints,
183+
field_type,
184+
input,
185+
&mut extras,
186+
make_output,
187+
len,
188+
false,
189+
)
190+
}
191+
Err(_) => return Err(ValError::new(error_type, input)),
192+
},
193+
_ => return Err(ValError::new(error_type, input)),
194+
}?;
195+
196+
Ok(output)
197+
}
198+
199+
pub fn different_strict_behavior(
200+
&self,
201+
definitions: Option<&DefinitionsBuilder<CombinedValidator>>,
202+
ultra_strict: bool,
203+
) -> bool {
204+
if ultra_strict {
205+
match self.item_validator {
206+
Some(ref v) => v.different_strict_behavior(definitions, true),
207+
None => false,
208+
}
209+
} else {
210+
true
211+
}
212+
}
213+
214+
pub fn get_name(&self) -> &str {
215+
&self.name
216+
}
217+
218+
pub fn complete(&mut self, definitions: &DefinitionsBuilder<CombinedValidator>) -> PyResult<()> {
219+
match self.item_validator {
220+
Some(ref mut v) => v.complete(definitions),
221+
None => Ok(()),
222+
}
223+
}
224+
}
225+
226+
#[derive(Debug, Clone)]
227+
pub struct FrozenSetValidator {
228+
inner: IntoSetValidator,
229+
}
230+
231+
impl BuildValidator for FrozenSetValidator {
232+
const EXPECTED_TYPE: &'static str = "frozenset";
233+
fn build(
234+
schema: &PyDict,
235+
config: Option<&PyDict>,
236+
definitions: &mut DefinitionsBuilder<CombinedValidator>,
237+
) -> PyResult<CombinedValidator> {
238+
Ok(Self {
239+
inner: IntoSetValidator::build(schema, config, definitions, Self::EXPECTED_TYPE)?,
240+
}
241+
.into())
242+
}
243+
}
244+
245+
impl Validator for FrozenSetValidator {
246+
fn validate<'s, 'data>(
247+
&'s self,
248+
py: Python<'data>,
249+
input: &'data impl Input<'data>,
250+
extra: &Extra,
251+
definitions: &'data Definitions<CombinedValidator>,
252+
recursion_guard: &'s mut RecursionGuard,
253+
) -> ValResult<'data, PyObject> {
254+
let set = self
255+
.inner
256+
.validate_into_set(py, input, extra, definitions, recursion_guard, SetType::FrozenSet)?;
257+
Ok(PyFrozenSet::new(py, set)?.into_py(py))
258+
}
259+
260+
fn different_strict_behavior(
261+
&self,
262+
definitions: Option<&DefinitionsBuilder<CombinedValidator>>,
263+
ultra_strict: bool,
264+
) -> bool {
265+
self.inner.different_strict_behavior(definitions, ultra_strict)
266+
}
267+
268+
fn get_name(&self) -> &str {
269+
self.inner.get_name()
270+
}
271+
272+
fn complete(&mut self, definitions: &DefinitionsBuilder<CombinedValidator>) -> PyResult<()> {
273+
self.inner.complete(definitions)
274+
}
275+
}
276+
277+
#[derive(Debug, Clone)]
278+
pub struct SetValidator {
279+
inner: IntoSetValidator,
280+
}
281+
282+
impl BuildValidator for SetValidator {
283+
const EXPECTED_TYPE: &'static str = "set";
284+
fn build(
285+
schema: &PyDict,
286+
config: Option<&PyDict>,
287+
definitions: &mut DefinitionsBuilder<CombinedValidator>,
288+
) -> PyResult<CombinedValidator> {
289+
Ok(Self {
290+
inner: IntoSetValidator::build(schema, config, definitions, Self::EXPECTED_TYPE)?,
291+
}
292+
.into())
293+
}
294+
}
295+
296+
impl Validator for SetValidator {
297+
fn validate<'s, 'data>(
298+
&'s self,
299+
py: Python<'data>,
300+
input: &'data impl Input<'data>,
301+
extra: &Extra,
302+
definitions: &'data Definitions<CombinedValidator>,
303+
recursion_guard: &'s mut RecursionGuard,
304+
) -> ValResult<'data, PyObject> {
305+
let set = self
306+
.inner
307+
.validate_into_set(py, input, extra, definitions, recursion_guard, SetType::Set)?;
308+
Ok(set.into_py(py))
309+
}
310+
311+
fn different_strict_behavior(
312+
&self,
313+
definitions: Option<&DefinitionsBuilder<CombinedValidator>>,
314+
ultra_strict: bool,
315+
) -> bool {
316+
self.inner.different_strict_behavior(definitions, ultra_strict)
317+
}
318+
319+
fn get_name(&self) -> &str {
320+
self.inner.get_name()
321+
}
322+
323+
fn complete(&mut self, definitions: &DefinitionsBuilder<CombinedValidator>) -> PyResult<()> {
324+
self.inner.complete(definitions)
325+
}
326+
}

0 commit comments

Comments
 (0)