Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,41 @@ The webhook request object should implement the OpenAPI WebhookRequest protocol

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

### Iterating request errors

If you want to collect errors instead of raising on the first one, use iterator-based APIs:

```python
errors = list(openapi.iter_request_errors(request))
if errors:
for error in errors:
print(type(error), str(error))
```

You can also call `iter_errors` directly on a validator class:

```python
from openapi_core import V31RequestValidator

errors = list(V31RequestValidator(spec).iter_errors(request))
```

Some high-level errors wrap detailed schema errors. To access nested schema details:

```python
for error in openapi.iter_request_errors(request):
cause = getattr(error, "__cause__", None)
schema_errors = getattr(cause, "schema_errors", None)
if schema_errors:
for schema_error in schema_errors:
print(schema_error.message)
```

## Response validation

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

```python
from openapi_core import validate_response

# raises error if response is invalid
openapi.validate_response(request, response)
```
Expand All @@ -70,3 +98,11 @@ openapi.validate_response(webhook_request, response)
```

You can also define your own response validator (See [Response Validator](configuration.md#response-validator)).

### Iterating response errors

Use `iter_response_errors` to collect validation errors for a response:

```python
errors = list(openapi.iter_response_errors(request, response))
```
12 changes: 12 additions & 0 deletions openapi_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

from openapi_core.app import OpenAPI
from openapi_core.configurations import Config
from openapi_core.shortcuts import iter_apicall_request_errors
from openapi_core.shortcuts import iter_apicall_response_errors
from openapi_core.shortcuts import iter_request_errors
from openapi_core.shortcuts import iter_response_errors
from openapi_core.shortcuts import iter_webhook_request_errors
from openapi_core.shortcuts import iter_webhook_response_errors
from openapi_core.shortcuts import unmarshal_apicall_request
from openapi_core.shortcuts import unmarshal_apicall_response
from openapi_core.shortcuts import unmarshal_request
Expand Down Expand Up @@ -64,6 +70,12 @@
"validate_webhook_response",
"validate_request",
"validate_response",
"iter_apicall_request_errors",
"iter_webhook_request_errors",
"iter_apicall_response_errors",
"iter_webhook_response_errors",
"iter_request_errors",
"iter_response_errors",
"V30RequestUnmarshaller",
"V30ResponseUnmarshaller",
"V31RequestUnmarshaller",
Expand Down
128 changes: 128 additions & 0 deletions openapi_core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from functools import cached_property
from pathlib import Path
from typing import Any
from typing import Iterator
from typing import Optional

from jsonschema._utils import Unset
Expand Down Expand Up @@ -590,6 +591,32 @@ def validate_request(
else:
self.validate_apicall_request(request)

def iter_request_errors(
self,
request: Annotated[
AnyRequest,
Doc("""
Request object to be validated.
"""),
],
) -> Iterator[Exception]:
"""Iterates over request validation errors.

Args:
request (AnyRequest): Request object to be validated.

Returns:
Iterator[Exception]: Iterator over request validation errors.

Raises:
TypeError: If the request object is not of the expected type.
SpecError: If the validator class is not found.
"""
if isinstance(request, WebhookRequest):
return self.iter_webhook_request_errors(request)
else:
return self.iter_apicall_request_errors(request)

def validate_response(
self,
request: Annotated[
Expand Down Expand Up @@ -620,6 +647,39 @@ def validate_response(
else:
self.validate_apicall_response(request, response)

def iter_response_errors(
self,
request: Annotated[
AnyRequest,
Doc("""
Request object associated with the response.
"""),
],
response: Annotated[
Response,
Doc("""
Response object to be validated.
"""),
],
) -> Iterator[Exception]:
"""Iterates over response validation errors.

Args:
request (AnyRequest): Request object associated with the response.
response (Response): Response object to be validated.

Returns:
Iterator[Exception]: Iterator over response validation errors.

Raises:
TypeError: If the request or response object is not of the expected type.
SpecError: If the validator class is not found.
"""
if isinstance(request, WebhookRequest):
return self.iter_webhook_response_errors(request, response)
else:
return self.iter_apicall_response_errors(request, response)

def validate_apicall_request(
self,
request: Annotated[
Expand All @@ -633,6 +693,19 @@ def validate_apicall_request(
raise TypeError("'request' argument is not type of Request")
self.request_validator.validate(request)

def iter_apicall_request_errors(
self,
request: Annotated[
Request,
Doc("""
API call request object to be validated.
"""),
],
) -> Iterator[Exception]:
if not isinstance(request, Request):
raise TypeError("'request' argument is not type of Request")
return self.request_validator.iter_errors(request)

def validate_apicall_response(
self,
request: Annotated[
Expand All @@ -654,6 +727,27 @@ def validate_apicall_response(
raise TypeError("'response' argument is not type of Response")
self.response_validator.validate(request, response)

def iter_apicall_response_errors(
self,
request: Annotated[
Request,
Doc("""
API call request object associated with the response.
"""),
],
response: Annotated[
Response,
Doc("""
API call response object to be validated.
"""),
],
) -> Iterator[Exception]:
if not isinstance(request, Request):
raise TypeError("'request' argument is not type of Request")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
return self.response_validator.iter_errors(request, response)

def validate_webhook_request(
self,
request: Annotated[
Expand All @@ -667,6 +761,19 @@ def validate_webhook_request(
raise TypeError("'request' argument is not type of WebhookRequest")
self.webhook_request_validator.validate(request)

def iter_webhook_request_errors(
self,
request: Annotated[
WebhookRequest,
Doc("""
Webhook request object to be validated.
"""),
],
) -> Iterator[Exception]:
if not isinstance(request, WebhookRequest):
raise TypeError("'request' argument is not type of WebhookRequest")
return self.webhook_request_validator.iter_errors(request)

def validate_webhook_response(
self,
request: Annotated[
Expand All @@ -688,6 +795,27 @@ def validate_webhook_response(
raise TypeError("'response' argument is not type of Response")
self.webhook_response_validator.validate(request, response)

def iter_webhook_response_errors(
self,
request: Annotated[
WebhookRequest,
Doc("""
Webhook request object associated with the response.
"""),
],
response: Annotated[
Response,
Doc("""
Webhook response object to be validated.
"""),
],
) -> Iterator[Exception]:
if not isinstance(request, WebhookRequest):
raise TypeError("'request' argument is not type of WebhookRequest")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
return self.webhook_response_validator.iter_errors(request, response)

def unmarshal_request(
self,
request: Annotated[
Expand Down
Loading
Loading