Skip to content

Commit 343b147

Browse files
committed
Cast form values when matching allOf subschemas
For application/x-www-form-urlencoded requests, iter_any_of_schemas casts scalar form values (which always arrive as strings) before validating each subschema, but iter_all_of_schemas did not. An allOf part containing a non-string field (boolean/integer/object) therefore failed validation against the raw string value and was silently dropped (only a 'invalid allOf schema found' warning), losing every field in that part. Give iter_all_of_schemas the same optional caster as iter_any_of_schemas and pass it from the media-type deserializer. Fixes #1212.
1 parent 0337b43 commit 343b147

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

openapi_core/deserializing/media_types/deserializers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,11 @@ def iter_composed_all_of_schemas(
379379
assert self.schema_validator is not None
380380

381381
if not self.mimetype.startswith("multipart"):
382-
return list(self.schema_validator.iter_all_of_schemas(location))
382+
return list(
383+
self.schema_validator.iter_all_of_schemas(
384+
location, caster=self.schema_caster
385+
)
386+
)
383387

384388
if self.schema is None or "allOf" not in self.schema:
385389
return []

openapi_core/validation/schemas/validators.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ def iter_any_of_schemas(
428428
def iter_all_of_schemas(
429429
self,
430430
value: Any,
431+
caster: Optional["SchemaCaster"] = None,
431432
) -> Iterator[SchemaPath]:
432433
if "allOf" not in self.schema:
433434
return
@@ -438,7 +439,23 @@ def iter_all_of_schemas(
438439
continue
439440
validator = self.evolve(subschema)
440441
try:
441-
validator.validate(value)
442+
test_value = value
443+
# Only cast if caster provided (opt-in behavior). Useful for
444+
# form-encoded data where scalar values arrive as strings and
445+
# need casting before validation against non-string schemas.
446+
if caster is not None:
447+
try:
448+
# Convert to dict if it's not exactly a plain dict
449+
if type(value) is not dict:
450+
test_value = dict(value)
451+
else:
452+
test_value = value
453+
test_value = caster.evolve(subschema).cast(test_value)
454+
except (ValueError, TypeError, Exception):
455+
# If casting fails, validate with the original value
456+
test_value = value
457+
458+
validator.validate(test_value)
442459
except ValidateError:
443460
log.warning("invalid allOf schema found")
444461
else:

tests/unit/deserializing/test_media_types_deserializers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,44 @@ def test_urlencoded_oneof_string_field(self, spec, deserializer_factory):
564564
"fieldA": "value",
565565
}
566566

567+
def test_urlencoded_allof_non_string_field(
568+
self, spec, deserializer_factory
569+
):
570+
"""Test issue #1212: allOf with urlencoded must not drop a subschema
571+
that has a non-string (e.g. boolean) field."""
572+
mimetype = "application/x-www-form-urlencoded"
573+
schema_dict = {
574+
"allOf": [
575+
{
576+
"type": "object",
577+
"properties": {
578+
"enabled": {"type": "boolean"},
579+
"label": {"type": "string"},
580+
},
581+
},
582+
{
583+
"type": "object",
584+
"properties": {"name": {"type": "string"}},
585+
},
586+
]
587+
}
588+
schema = SchemaPath.from_dict(schema_dict)
589+
schema_validator = oas31_schema_validators_factory.create(
590+
spec, schema
591+
)
592+
deserializer = deserializer_factory(
593+
mimetype, schema=schema, schema_validator=schema_validator
594+
)
595+
value = b"name=widget&label=hello&enabled=true"
596+
597+
result = deserializer.deserialize(value)
598+
599+
assert result == {
600+
"name": "widget",
601+
"label": "hello",
602+
"enabled": True,
603+
}
604+
567605
def test_urlencoded_anyof_with_types(self, spec, deserializer_factory):
568606
"""Test anyOf with urlencoded and type coercion"""
569607
mimetype = "application/x-www-form-urlencoded"

0 commit comments

Comments
 (0)