OpenAPI Errors#
By default, rororo.openapi.setup_openapi()
enables usage of
aiohttp_middlewares.error.error_middleware()
, which in same time provides
human readable JSON errors for:
Security error
Request parameter validation error
Request body validation error
Response data validation error
Document below describes how exactly those errors handled, and what changes to OpenAPI Schema you might add to support given error responses.
Security Errors#
OpenAPI 3 schema provide a way to “secure” operation via security schemes. As of rororo 2.0.0 version next security schemes are supported:
HTTP
Basic
Bearer
API Key
Unfortunately OpenID & OAuth 2 security schemas not yet supported.
Under the hood rororo uses next logic for generating security error,
When an operation is guarded with one and only security scheme of HTTP Basic authentication and
Authorization
header is missed or contains wrong data -401 Unauthenticated
error raisedIf an operation guarded with other security scheme or with multiple security schemes and security data is missed in request -
403 Access Denied
error raised
Both of given errors results in next JSON:
{
"detail": "Not authenticated"
}
Only difference is response status code & additional www-authenticate: basic
header in case of missing HTTP Basic Auth details.
Important
rororo does not intend to check, whether authentication data is valid
or not, so aiohttp.web
application should make the authentication by
itself. Most reliable way of doing that by providing @login_required
decorator as done in Hobotnica example.
Request Parameter Validation Errors#
When request parameter is missed, when required, missed, has empty, or invalid
value openapi-core raises an
OpenAPIParameterError
or EmptyParameterValue
exception. rororo
handles given error and wraps it into own ValidationError
.
For example, when operation is required X-GitHub-Username
header parameter,
missing it in request will result in 422 Unprocessable Entity
response
with next JSON content:
{
"detail": [
{
"loc": [
"parameters",
"X-GitHub-Username"
],
"message": "Required parameter"
}
]
}
Request Body Validation Errors#
When request body contains invalid data rororo converts any
openapi-core
or jsonschema
exceptions into own ValidationError
.
For example, when request body missed name
field and have invalid email
field next response will be supplied:
{
"detail": [
{
"loc": [
"body",
"name"
],
"message": "Field required"
},
{
"loc": [
"body",
"email"
],
"message": "'not-email' is not an 'email'"
}
]
}
Response Data Validation Errors#
Similarly to Request Body Validation Errors rororo converts any
openapi-core
or jsonschema
exceptions raised by validating response
data into own ValidationError
.
Important
For performance reasons, you might want to disable response data validation
entirely by passing is_validate_response=False
into
rororo.openapi.setup_openapi()
. In that case rororo will not
run any validation for response data.
For example, when response data contains wrong uid
format field next error
response will be supplied,
{
"detail": [
{
"loc": [
"response",
"uid"
],
"message": "'not-uid' is not a 'uuid'"
}
]
}
OpenAPI Error Schemas#
You might need to update your OpenAPI 3 Schemas by using next responses components.
Default Error#
components:
responses:
DefaultError:
description: "Unhandled error."
content:
application/json:
schema:
type: "object"
properties:
detail:
type: "string"
minLength: 1
required: ["detail"]
Validation Error#
components:
responses:
ValidationError:
description: "Validation error."
content:
application/json:
schema:
type: "object"
properties:
detail:
type: "array"
items:
type: "object"
properties:
loc:
type: "array"
items:
type: "string"
minLength: 1
message:
type: "string"
minLength: 1
required: ["loc", "message"]
required: ["detail"]
Custom Error Handling#
In case if aiohttp.web
application doesn’t want or cannot use described way
of handling errors via aiohttp_middlewares.error.error_middleware()
, it
needs to disable error middleware usage entirely by passing
use_error_middleware=False
on setting up OpenAPI support,
from pathlib import Path
from aiohttp import web
from rororo import setup_openapi
app = setup_openapi(
web.Application(),
Path(__file__).parent / "openapi.yaml",
operations,
use_error_middleware=False,
)
In that case aiohttp.web
application need to implement its own way of
handling OpenAPI (and other) errors.
Extra. Raising OpenAPI Errors from aiohttp.web Applications#
rororo provides bunch of custom exceptions for providing errors in
aiohttp.web
handlers and related code:
rororo.openapi.SecurityError
(androroro.openapi.BasicSecurityError
)rororo.openapi.InvalidCredentials
(androroro.openapi.BasicInvalidCredentials
)
While you might still use aiohttp.web HTTP Exceptions, the purpose of rororo HTTP Exceptions to simplify process of generating and raising custom errors from your OpenAPI server handlers.
For example, to raise a Bad Request error
with "Check your request"`
message use next code,
from aiohttp import web
from rororo.openapi import (
BadRequest,
get_validated_data,
OperationTableDef,
)
operations = OperationTableDef()
@operations.register
async def create_item(request: web.Request) -> web.Response:
data = get_validated_data(request)
if data["field"] != 42:
raise BadRequest("Check your request")
...
Similarly you can use SecurityError
, InvalidCredentials
, and
ServerError
to generate 403 or 500 errors.
On top of that rororo provides custom way to generate validation errors & not found errors.
Validation Error#
Use rororo.openapi.ValidationError
to generate and raise
Unprocessable Entity errors.
For example, when you need to generate the error response as follows,
{
"detail": [
{
"loc": ["body", "field"],
"message": "Invalid value"
}
]
}
Use the code below,
from aiohttp import web
from rororo.openapi import (
get_validated_data,
OperationTableDef,
ValidationError,
)
operations = OperationTableDef()
@operations.register
async def create_item(request: web.Request) -> web.Response:
data = get_validated_data(request)
if data["field"] != 42:
raise ValidationError.from_dict(
body={"field": "Invalid value"}
)
...
There is alos a possibility to use rororo.openapi.validation_error_context()
to nest error messages.
For example, when you need to validate some subitem in received data via some external validation, you can organize this process as follows,
Implement external validator function in
validators
moduleWrap validation call into
validation_error_context
context manager
validators.py
from rororo.annotations import DictStrAny
from rororo.openapi import (
ValidationError,
validation_error_context,
)
def validate_field(value: int) -> int:
if value != 42:
raise ValidationError(message="Invalid value")
return value
def validate_item(data: DictStrAny) -> DictStrAny:
with validation_error_context("subitem"):
subitem = validate_subitem(data["subitem"])
return {**data, "subitem": subitem}
def validate_subitem(data: DictStrAny) -> DictStrAny:
with validation_error_context("field"):
value = validate_field(data["field"])
return {**data, "field": value}
views.py
@operations.register
async def create_item(request: web.Request) -> web.Response:
with validation_error_context("body"):
data = validate_data(get_validated_data(request))
...
Object Does Not Exist#
Another common case is to generate errors, when request object does not exist in database for some reason.
rororo.exceptions.ObjectDoesNotExist
aims to simplify that process
as follows,
from aiohttp import web
from rororo.openapi import (
get_openapi_context,
ObjectDoesNotExist,
OperationTableDef,
)
operations = OperationTableDef()
@operations.register
async def retrieve_item(request: web.Request) -> web.Response:
ctx = get_openapi_context(request)
if ctx.parameters.path["item_id"] != 42:
raise ObjectDoesNotExist("Item")
...