Skip to content

Commit 57adf5c

Browse files
committed
fix set length validation
1 parent 651ac49 commit 57adf5c

File tree

6 files changed

+154
-153
lines changed

6 files changed

+154
-153
lines changed

pydantic_core/core_schema.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,6 @@ class SetSchema(TypedDict, total=False):
13531353
items_schema: CoreSchema
13541354
min_length: int
13551355
max_length: int
1356-
generator_max_length: int
13571356
strict: bool
13581357
ref: str
13591358
metadata: Any
@@ -1365,7 +1364,6 @@ def set_schema(
13651364
*,
13661365
min_length: int | None = None,
13671366
max_length: int | None = None,
1368-
generator_max_length: int | None = None,
13691367
strict: bool | None = None,
13701368
ref: str | None = None,
13711369
metadata: Any = None,
@@ -1388,9 +1386,6 @@ def set_schema(
13881386
items_schema: The value must be a set with items that match this schema
13891387
min_length: The value must be a set with at least this many items
13901388
max_length: The value must be a set with at most this many items
1391-
generator_max_length: At most this many items will be read from a generator before failing validation
1392-
This is important because generators can be infinite, and even with a `max_length` on the set,
1393-
an infinite generator could run forever without producing more than `max_length` distinct items.
13941389
strict: The value must be a set with exactly this many items
13951390
ref: optional unique identifier of the schema, used to reference the schema in other places
13961391
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -1401,7 +1396,6 @@ def set_schema(
14011396
items_schema=items_schema,
14021397
min_length=min_length,
14031398
max_length=max_length,
1404-
generator_max_length=generator_max_length,
14051399
strict=strict,
14061400
ref=ref,
14071401
metadata=metadata,
@@ -1414,7 +1408,6 @@ class FrozenSetSchema(TypedDict, total=False):
14141408
items_schema: CoreSchema
14151409
min_length: int
14161410
max_length: int
1417-
generator_max_length: int
14181411
strict: bool
14191412
ref: str
14201413
metadata: Any
@@ -1426,7 +1419,6 @@ def frozenset_schema(
14261419
*,
14271420
min_length: int | None = None,
14281421
max_length: int | None = None,
1429-
generator_max_length: int | None = None,
14301422
strict: bool | None = None,
14311423
ref: str | None = None,
14321424
metadata: Any = None,
@@ -1449,7 +1441,6 @@ def frozenset_schema(
14491441
items_schema: The value must be a frozenset with items that match this schema
14501442
min_length: The value must be a frozenset with at least this many items
14511443
max_length: The value must be a frozenset with at most this many items
1452-
generator_max_length: The value must generate a frozenset with at most this many items
14531444
strict: The value must be a frozenset with exactly this many items
14541445
ref: optional unique identifier of the schema, used to reference the schema in other places
14551446
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -1460,7 +1451,6 @@ def frozenset_schema(
14601451
items_schema=items_schema,
14611452
min_length=min_length,
14621453
max_length=max_length,
1463-
generator_max_length=generator_max_length,
14641454
strict=strict,
14651455
ref=ref,
14661456
metadata=metadata,

src/input/return_enums.rs

Lines changed: 108 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,54 @@ fn validate_iter_to_vec<'a, 's>(
131131
}
132132
}
133133

134+
#[allow(clippy::too_many_arguments)]
135+
fn validate_iter_to_set<'a, 's>(
136+
py: Python<'a>,
137+
iter: impl Iterator<Item = PyResult<&'a (impl Input<'a> + 'a)>>,
138+
input: &'a (impl Input<'a> + 'a),
139+
field_type: &'static str,
140+
max_length: Option<usize>,
141+
validator: &'s CombinedValidator,
142+
extra: &Extra,
143+
definitions: &'a [CombinedValidator],
144+
recursion_guard: &'s mut RecursionGuard,
145+
) -> ValResult<'a, &'a PySet> {
146+
let set = PySet::empty(py)?;
147+
let mut errors: Vec<ValLineError> = Vec::new();
148+
for (index, item_result) in iter.enumerate() {
149+
let item = item_result.map_err(|e| any_next_error!(py, e, input, index))?;
150+
match validator.validate(py, item, extra, definitions, recursion_guard) {
151+
Ok(item) => {
152+
set.add(item)?;
153+
if let Some(max_length) = max_length {
154+
let actual_length = set.len();
155+
if actual_length > max_length {
156+
return Err(ValError::new(
157+
ErrorType::TooLong {
158+
field_type: field_type.to_string(),
159+
max_length,
160+
actual_length,
161+
},
162+
input,
163+
));
164+
}
165+
}
166+
}
167+
Err(ValError::LineErrors(line_errors)) => {
168+
errors.extend(line_errors.into_iter().map(|err| err.with_outer_location(index.into())));
169+
}
170+
Err(ValError::Omit) => (),
171+
Err(err) => return Err(err),
172+
}
173+
}
174+
175+
if errors.is_empty() {
176+
Ok(set)
177+
} else {
178+
Err(ValError::LineErrors(errors))
179+
}
180+
}
181+
134182
fn no_validator_iter_to_vec<'a, 's>(
135183
py: Python<'a>,
136184
iter: impl Iterator<Item = &'a (impl Input<'a> + 'a)>,
@@ -174,67 +222,67 @@ impl<'a> GenericCollection<'a> {
174222
.generic_len()
175223
.unwrap_or_else(|_| max_length.unwrap_or(DEFAULT_CAPACITY));
176224
let max_length_check = MaxLengthCheck::new(max_length, field_type, input);
225+
226+
macro_rules! validate {
227+
($iter:expr) => {
228+
validate_iter_to_vec(
229+
py,
230+
$iter,
231+
capacity,
232+
max_length_check,
233+
validator,
234+
extra,
235+
definitions,
236+
recursion_guard,
237+
)
238+
};
239+
}
240+
241+
match self {
242+
Self::List(collection) => validate!(collection.iter().map(Ok)),
243+
Self::Tuple(collection) => validate!(collection.iter().map(Ok)),
244+
Self::Set(collection) => validate!(collection.iter().map(Ok)),
245+
Self::FrozenSet(collection) => validate!(collection.iter().map(Ok)),
246+
Self::PyAny(collection) => validate!(collection.iter()?),
247+
Self::JsonArray(collection) => validate!(collection.iter().map(Ok)),
248+
}
249+
}
250+
251+
#[allow(clippy::too_many_arguments)]
252+
pub fn validate_to_set<'s>(
253+
&'s self,
254+
py: Python<'a>,
255+
input: &'a impl Input<'a>,
256+
max_length: Option<usize>,
257+
field_type: &'static str,
258+
validator: &'s CombinedValidator,
259+
extra: &Extra,
260+
definitions: &'a [CombinedValidator],
261+
recursion_guard: &'s mut RecursionGuard,
262+
) -> ValResult<'a, &'a PySet> {
263+
macro_rules! validate_set {
264+
($iter:expr) => {
265+
validate_iter_to_set(
266+
py,
267+
$iter,
268+
input,
269+
field_type,
270+
max_length,
271+
validator,
272+
extra,
273+
definitions,
274+
recursion_guard,
275+
)
276+
};
277+
}
278+
177279
match self {
178-
Self::List(collection) => validate_iter_to_vec(
179-
py,
180-
collection.iter().map(Ok),
181-
capacity,
182-
max_length_check,
183-
validator,
184-
extra,
185-
definitions,
186-
recursion_guard,
187-
),
188-
Self::Tuple(collection) => validate_iter_to_vec(
189-
py,
190-
collection.iter().map(Ok),
191-
capacity,
192-
max_length_check,
193-
validator,
194-
extra,
195-
definitions,
196-
recursion_guard,
197-
),
198-
Self::Set(collection) => validate_iter_to_vec(
199-
py,
200-
collection.iter().map(Ok),
201-
capacity,
202-
max_length_check,
203-
validator,
204-
extra,
205-
definitions,
206-
recursion_guard,
207-
),
208-
Self::FrozenSet(collection) => validate_iter_to_vec(
209-
py,
210-
collection.iter().map(Ok),
211-
capacity,
212-
max_length_check,
213-
validator,
214-
extra,
215-
definitions,
216-
recursion_guard,
217-
),
218-
Self::PyAny(collection) => validate_iter_to_vec(
219-
py,
220-
collection.iter()?,
221-
capacity,
222-
max_length_check,
223-
validator,
224-
extra,
225-
definitions,
226-
recursion_guard,
227-
),
228-
Self::JsonArray(collection) => validate_iter_to_vec(
229-
py,
230-
collection.iter().map(Ok),
231-
capacity,
232-
max_length_check,
233-
validator,
234-
extra,
235-
definitions,
236-
recursion_guard,
237-
),
280+
Self::List(collection) => validate_set!(collection.iter().map(Ok)),
281+
Self::Tuple(collection) => validate_set!(collection.iter().map(Ok)),
282+
Self::Set(collection) => validate_set!(collection.iter().map(Ok)),
283+
Self::FrozenSet(collection) => validate_set!(collection.iter().map(Ok)),
284+
Self::PyAny(collection) => validate_set!(collection.iter()?),
285+
Self::JsonArray(collection) => validate_set!(collection.iter().map(Ok)),
238286
}
239287
}
240288

src/validators/frozenset.rs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@ use crate::errors::ValResult;
66
use crate::input::Input;
77
use crate::recursion_guard::RecursionGuard;
88

9-
use super::list::{get_items_schema, min_length_check};
9+
use super::list::min_length_check;
1010
use super::set::set_build;
1111
use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator};
1212

1313
#[derive(Debug, Clone)]
1414
pub struct FrozenSetValidator {
1515
strict: bool,
16-
item_validator: Option<Box<CombinedValidator>>,
16+
item_validator: Box<CombinedValidator>,
1717
min_length: Option<usize>,
1818
max_length: Option<usize>,
19-
generator_max_length: Option<usize>,
2019
name: String,
2120
}
2221

@@ -34,25 +33,20 @@ impl Validator for FrozenSetValidator {
3433
definitions: &'data Definitions<CombinedValidator>,
3534
recursion_guard: &'s mut RecursionGuard,
3635
) -> ValResult<'data, PyObject> {
37-
let seq = input.validate_frozenset(extra.strict.unwrap_or(self.strict))?;
36+
let collection = input.validate_frozenset(extra.strict.unwrap_or(self.strict))?;
3837

39-
let f_set = match self.item_validator {
40-
Some(ref v) => PyFrozenSet::new(
41-
py,
42-
&seq.validate_to_vec(
43-
py,
44-
input,
45-
self.max_length,
46-
"Frozenset",
47-
v,
48-
extra,
49-
definitions,
50-
recursion_guard,
51-
)?,
52-
)?,
53-
None => PyFrozenSet::new(py, &seq.to_vec(py, input, "Frozenset", self.generator_max_length)?)?,
54-
};
55-
min_length_check!(input, "Frozenset", self.min_length, f_set);
38+
let set = collection.validate_to_set(
39+
py,
40+
input,
41+
self.max_length,
42+
"Frozenset",
43+
&self.item_validator,
44+
extra,
45+
definitions,
46+
recursion_guard,
47+
)?;
48+
min_length_check!(input, "Frozenset", self.min_length, set);
49+
let f_set = PyFrozenSet::new(py, set)?;
5650
Ok(f_set.into_py(py))
5751
}
5852

@@ -62,10 +56,7 @@ impl Validator for FrozenSetValidator {
6256
ultra_strict: bool,
6357
) -> bool {
6458
if ultra_strict {
65-
match self.item_validator {
66-
Some(ref v) => v.different_strict_behavior(definitions, true),
67-
None => false,
68-
}
59+
self.item_validator.different_strict_behavior(definitions, true)
6960
} else {
7061
true
7162
}
@@ -76,9 +67,6 @@ impl Validator for FrozenSetValidator {
7667
}
7768

7869
fn complete(&mut self, definitions: &DefinitionsBuilder<CombinedValidator>) -> PyResult<()> {
79-
match self.item_validator {
80-
Some(ref mut v) => v.complete(definitions),
81-
None => Ok(()),
82-
}
70+
self.item_validator.complete(definitions)
8371
}
8472
}

0 commit comments

Comments
 (0)