Skip to content

Commit 3b70322

Browse files
committed
Add public iter_* validation error APIs
1 parent c6910b1 commit 3b70322

File tree

6 files changed

+480
-2
lines changed

6 files changed

+480
-2
lines changed

docs/validation.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,41 @@ The webhook request object should implement the OpenAPI WebhookRequest protocol
4545

4646
You can also define your own request validator (See [Request Validator](configuration.md#request-validator)).
4747

48+
### Iterating request errors
49+
50+
If you want to collect errors instead of raising on the first one, use iterator-based APIs:
51+
52+
```python
53+
errors = list(openapi.iter_request_errors(request))
54+
if errors:
55+
for error in errors:
56+
print(type(error), str(error))
57+
```
58+
59+
You can also call `iter_errors` directly on a validator class:
60+
61+
```python
62+
from openapi_core import V31RequestValidator
63+
64+
errors = list(V31RequestValidator(spec).iter_errors(request))
65+
```
66+
67+
Some high-level errors wrap detailed schema errors. To access nested schema details:
68+
69+
```python
70+
for error in openapi.iter_request_errors(request):
71+
cause = getattr(error, "__cause__", None)
72+
schema_errors = getattr(cause, "schema_errors", None)
73+
if schema_errors:
74+
for schema_error in schema_errors:
75+
print(schema_error.message)
76+
```
77+
4878
## Response validation
4979

5080
Use the `validate_response` function to validate response data against a given spec. By default, the OpenAPI spec version is detected:
5181

5282
```python
53-
from openapi_core import validate_response
54-
5583
# raises error if response is invalid
5684
openapi.validate_response(request, response)
5785
```
@@ -70,3 +98,11 @@ openapi.validate_response(webhook_request, response)
7098
```
7199

72100
You can also define your own response validator (See [Response Validator](configuration.md#response-validator)).
101+
102+
### Iterating response errors
103+
104+
Use `iter_response_errors` to collect validation errors for a response:
105+
106+
```python
107+
errors = list(openapi.iter_response_errors(request, response))
108+
```

openapi_core/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
from openapi_core.app import OpenAPI
44
from openapi_core.configurations import Config
5+
from openapi_core.shortcuts import iter_apicall_request_errors
6+
from openapi_core.shortcuts import iter_apicall_response_errors
7+
from openapi_core.shortcuts import iter_request_errors
8+
from openapi_core.shortcuts import iter_response_errors
9+
from openapi_core.shortcuts import iter_webhook_request_errors
10+
from openapi_core.shortcuts import iter_webhook_response_errors
511
from openapi_core.shortcuts import unmarshal_apicall_request
612
from openapi_core.shortcuts import unmarshal_apicall_response
713
from openapi_core.shortcuts import unmarshal_request
@@ -64,6 +70,12 @@
6470
"validate_webhook_response",
6571
"validate_request",
6672
"validate_response",
73+
"iter_apicall_request_errors",
74+
"iter_webhook_request_errors",
75+
"iter_apicall_response_errors",
76+
"iter_webhook_response_errors",
77+
"iter_request_errors",
78+
"iter_response_errors",
6779
"V30RequestUnmarshaller",
6880
"V30ResponseUnmarshaller",
6981
"V31RequestUnmarshaller",

openapi_core/app.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from functools import cached_property
44
from pathlib import Path
55
from typing import Any
6+
from typing import Iterator
67
from typing import Optional
78

89
from jsonschema._utils import Unset
@@ -590,6 +591,32 @@ def validate_request(
590591
else:
591592
self.validate_apicall_request(request)
592593

594+
def iter_request_errors(
595+
self,
596+
request: Annotated[
597+
AnyRequest,
598+
Doc("""
599+
Request object to be validated.
600+
"""),
601+
],
602+
) -> Iterator[Exception]:
603+
"""Iterates over request validation errors.
604+
605+
Args:
606+
request (AnyRequest): Request object to be validated.
607+
608+
Returns:
609+
Iterator[Exception]: Iterator over request validation errors.
610+
611+
Raises:
612+
TypeError: If the request object is not of the expected type.
613+
SpecError: If the validator class is not found.
614+
"""
615+
if isinstance(request, WebhookRequest):
616+
return self.iter_webhook_request_errors(request)
617+
else:
618+
return self.iter_apicall_request_errors(request)
619+
593620
def validate_response(
594621
self,
595622
request: Annotated[
@@ -620,6 +647,39 @@ def validate_response(
620647
else:
621648
self.validate_apicall_response(request, response)
622649

650+
def iter_response_errors(
651+
self,
652+
request: Annotated[
653+
AnyRequest,
654+
Doc("""
655+
Request object associated with the response.
656+
"""),
657+
],
658+
response: Annotated[
659+
Response,
660+
Doc("""
661+
Response object to be validated.
662+
"""),
663+
],
664+
) -> Iterator[Exception]:
665+
"""Iterates over response validation errors.
666+
667+
Args:
668+
request (AnyRequest): Request object associated with the response.
669+
response (Response): Response object to be validated.
670+
671+
Returns:
672+
Iterator[Exception]: Iterator over response validation errors.
673+
674+
Raises:
675+
TypeError: If the request or response object is not of the expected type.
676+
SpecError: If the validator class is not found.
677+
"""
678+
if isinstance(request, WebhookRequest):
679+
return self.iter_webhook_response_errors(request, response)
680+
else:
681+
return self.iter_apicall_response_errors(request, response)
682+
623683
def validate_apicall_request(
624684
self,
625685
request: Annotated[
@@ -633,6 +693,19 @@ def validate_apicall_request(
633693
raise TypeError("'request' argument is not type of Request")
634694
self.request_validator.validate(request)
635695

696+
def iter_apicall_request_errors(
697+
self,
698+
request: Annotated[
699+
Request,
700+
Doc("""
701+
API call request object to be validated.
702+
"""),
703+
],
704+
) -> Iterator[Exception]:
705+
if not isinstance(request, Request):
706+
raise TypeError("'request' argument is not type of Request")
707+
return self.request_validator.iter_errors(request)
708+
636709
def validate_apicall_response(
637710
self,
638711
request: Annotated[
@@ -654,6 +727,27 @@ def validate_apicall_response(
654727
raise TypeError("'response' argument is not type of Response")
655728
self.response_validator.validate(request, response)
656729

730+
def iter_apicall_response_errors(
731+
self,
732+
request: Annotated[
733+
Request,
734+
Doc("""
735+
API call request object associated with the response.
736+
"""),
737+
],
738+
response: Annotated[
739+
Response,
740+
Doc("""
741+
API call response object to be validated.
742+
"""),
743+
],
744+
) -> Iterator[Exception]:
745+
if not isinstance(request, Request):
746+
raise TypeError("'request' argument is not type of Request")
747+
if not isinstance(response, Response):
748+
raise TypeError("'response' argument is not type of Response")
749+
return self.response_validator.iter_errors(request, response)
750+
657751
def validate_webhook_request(
658752
self,
659753
request: Annotated[
@@ -667,6 +761,19 @@ def validate_webhook_request(
667761
raise TypeError("'request' argument is not type of WebhookRequest")
668762
self.webhook_request_validator.validate(request)
669763

764+
def iter_webhook_request_errors(
765+
self,
766+
request: Annotated[
767+
WebhookRequest,
768+
Doc("""
769+
Webhook request object to be validated.
770+
"""),
771+
],
772+
) -> Iterator[Exception]:
773+
if not isinstance(request, WebhookRequest):
774+
raise TypeError("'request' argument is not type of WebhookRequest")
775+
return self.webhook_request_validator.iter_errors(request)
776+
670777
def validate_webhook_response(
671778
self,
672779
request: Annotated[
@@ -688,6 +795,27 @@ def validate_webhook_response(
688795
raise TypeError("'response' argument is not type of Response")
689796
self.webhook_response_validator.validate(request, response)
690797

798+
def iter_webhook_response_errors(
799+
self,
800+
request: Annotated[
801+
WebhookRequest,
802+
Doc("""
803+
Webhook request object associated with the response.
804+
"""),
805+
],
806+
response: Annotated[
807+
Response,
808+
Doc("""
809+
Webhook response object to be validated.
810+
"""),
811+
],
812+
) -> Iterator[Exception]:
813+
if not isinstance(request, WebhookRequest):
814+
raise TypeError("'request' argument is not type of WebhookRequest")
815+
if not isinstance(response, Response):
816+
raise TypeError("'response' argument is not type of Response")
817+
return self.webhook_response_validator.iter_errors(request, response)
818+
691819
def unmarshal_request(
692820
self,
693821
request: Annotated[

0 commit comments

Comments
 (0)