mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-06 04:19:26 +00:00
325 lines
10 KiB
ReStructuredText
325 lines
10 KiB
ReStructuredText
Validation
|
|
==========
|
|
|
|
One of the most powerful Connexion features is automatic validation based on your OpenAPI
|
|
specification.
|
|
|
|
Connexion validates:
|
|
|
|
- :ref:`Requests<validation:Request validation>`
|
|
|
|
- :ref:`Parameters<validation:Parameter validation>`
|
|
- :ref:`Body<validation:RequestBody validation>`
|
|
- :ref:`Headers<validation:Request headers validation>`
|
|
|
|
- :ref:`Response<validation:Response validation>`
|
|
|
|
- :ref:`Body<validation:ResponseBody validation>`
|
|
- :ref:`Headers<validation:Response headers validation>`
|
|
|
|
The validation behavior can easily be customized with :ref:`validation:Custom validators`
|
|
|
|
Request validation
|
|
------------------
|
|
|
|
Connexion will validate any incoming requests against your specification and automatically
|
|
returns the correct 4XX error on failure.
|
|
|
|
Parameter validation
|
|
````````````````````
|
|
|
|
By default, Connexion checks all the request for any parameters defined in your specification and
|
|
validates them against their definition. This includes their schema (``type``, ``format``,
|
|
``range``, ...) and whether or not they are required or whether they can be ``null``.
|
|
|
|
You can turn on ``strict_validation`` if you want Connexion to disallow any extra parameters
|
|
that are not defined in your specification. You can set it either on the application or API level:
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: AsyncApp
|
|
:sync: AsyncApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import AsyncApp
|
|
|
|
app = AsyncApp(__name__, strict_validation=True)
|
|
app.add_api("openapi.yaml", strict_validation=True)
|
|
|
|
|
|
.. tab-item:: FlaskApp
|
|
:sync: FlaskApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import FlaskApp
|
|
|
|
app = FlaskApp(__name__, strict_validation=True)
|
|
app.add_api("openapi.yaml", strict_validation=True)
|
|
|
|
.. tab-item:: ConnexionMiddleware
|
|
:sync: ConnexionMiddleware
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from asgi_framework import App
|
|
from connexion import ConnexionMiddleware
|
|
|
|
app = App(__name__)
|
|
app = ConnexionMiddleware(app, strict_validation=True)
|
|
app.add_api("openapi.yaml", strict_validation=True)
|
|
|
|
If parameter validation fails, Connexion will return a ``400 Bad Request`` response with
|
|
information on the failure in the description.
|
|
|
|
For more information on how parameters are handled in general, see
|
|
:ref:`request:Request handling`.
|
|
|
|
RequestBody validation
|
|
``````````````````````
|
|
|
|
Connexion can automatically validate a ``requestBody`` for ``json`` and ``formData`` content
|
|
types, for which it relies on `jsonschema`_. You can plug in your own validator for other content
|
|
types (see :ref:`validation:Custom validators`).
|
|
|
|
.. note::
|
|
If the ``Content-Type`` header is not set in the request, Connexion will check your
|
|
specification for which content types it accepts. If it only accepts a single content type,
|
|
Connexion assumes the request to have this content type and will validate it accordingly. If
|
|
your specification specifies no or multiple content types it accepts, Connexion will assume
|
|
the request to have content type ``application/octet-stream; charset=utf-8`` and will skip
|
|
``requestBody`` validation.
|
|
|
|
If ``requestBody`` validation fails, Connexion will return a ``400 Bad Request`` response with
|
|
information on the failure in the description.
|
|
|
|
For more information on how the ``requestBody`` is handled in general, see
|
|
:ref:`request:Body`.
|
|
|
|
Request headers validation
|
|
``````````````````````````
|
|
|
|
Headers and cookies are also validated against your specification. If their validation fails,
|
|
Connexion will return a ``400 Bad Request`` response with information on the failure in the
|
|
description.
|
|
|
|
The ``Content-Type`` header is validated separately. If it fails validation, Connexion returns a
|
|
``415 Unsupported Media Type`` error.
|
|
|
|
.. note::
|
|
If the ``Content-Type`` header is not set in the request, Connexion will make an assumption
|
|
on the content type (see :ref:`validation:RequestBody validation`) and validate it against your
|
|
spec, which might fail.
|
|
|
|
Response validation
|
|
-------------------
|
|
|
|
Connexion **will not validate outgoing responses by default** , but you can activate this by passing
|
|
the ``validate_responses`` argument to either your application or API:
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: AsyncApp
|
|
:sync: AsyncApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import AsyncApp
|
|
|
|
app = AsyncApp(__name__, validate_responses=True)
|
|
app.add_api("openapi.yaml", validate_responses=True)
|
|
|
|
|
|
.. tab-item:: FlaskApp
|
|
:sync: FlaskApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import FlaskApp
|
|
|
|
app = FlaskApp(__name__, validate_responses=True)
|
|
app.add_api("openapi.yaml", validate_responses=True)
|
|
|
|
.. tab-item:: ConnexionMiddleware
|
|
:sync: ConnexionMiddleware
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from asgi_framework import App
|
|
from connexion import ConnexionMiddleware
|
|
|
|
app = App(__name__)
|
|
app = ConnexionMiddleware(app, validate_responses=True)
|
|
app.add_api("openapi.yaml", validate_responses=True)
|
|
|
|
ResponseBody validation
|
|
```````````````````````
|
|
|
|
Connexion has built-in validators for the ``application/json`` and ``text/plain`` content types.
|
|
If the content type is not explicitly set, Connexion will infer it (see :ref:`response:Headers`),
|
|
and validate the body using the corresponding validator.
|
|
|
|
Response headers validation
|
|
```````````````````````````
|
|
|
|
Connexion will check for any required response headers that are missing and will validate the
|
|
``Content-Type`` header against the responses defined in your specification.
|
|
|
|
.. note::
|
|
If the content type is not explicitly set, Connexion will infer it
|
|
(see :ref:`response:Headers`), and validate the inferred content type, which can still fail.
|
|
|
|
Custom validators
|
|
-----------------
|
|
|
|
Connexion provides a ``validator_map`` argument which you can use to pass in custom validators.
|
|
The default validators are defined in ``connexion.validators.VALIDATOR_MAP``:
|
|
|
|
.. code-block:: python
|
|
:caption: **connexion.validators**
|
|
|
|
VALIDATOR_MAP = {
|
|
"parameter": ParameterValidator,
|
|
"body": MediaTypeDict(
|
|
{
|
|
"*/*json": JSONRequestBodyValidator,
|
|
"application/x-www-form-urlencoded": FormDataValidator,
|
|
"multipart/form-data": MultiPartFormDataValidator,
|
|
}
|
|
),
|
|
"response": MediaTypeDict(
|
|
{
|
|
"*/*json": JSONResponseBodyValidator,
|
|
"text/plain": TextResponseBodyValidator,
|
|
}
|
|
),
|
|
}
|
|
|
|
Note that the ``"body"`` and ``"response"`` values are instances of the special ``MediaTypeDict``
|
|
datastructure, which can handle Media Type ranges:
|
|
|
|
.. autoclass:: connexion.datastructures.MediaTypeDict
|
|
|
|
You can create your own custom Validator by subclassing the
|
|
``connexion.validators.AbstractRequestBodyValidator`` or
|
|
``connexion.validators.AbstractResponseBodyValidator`` class and override the defaults by passing
|
|
in a custom ``validator_map`` to your application or API:
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion.datastructures import MediaTypeDict
|
|
from connexion.validators import AbstractResponseBodyValidator, TextResponseBodyValidator
|
|
|
|
|
|
class MyCustomXMLResponseValidator(AbstractResponseBodyValidator):
|
|
|
|
def _parse(self, stream: t.Generator[bytes, None, None]) -> t.Any:
|
|
...
|
|
|
|
def _validate(self, body: dict):
|
|
...
|
|
|
|
|
|
validator_map = {
|
|
"response": MediaTypeDict(
|
|
{
|
|
"*/*json": JSONResponseBodyValidator,
|
|
"*/*xml": MyCustomXMLResponseValidator,
|
|
"text/plain": TextResponseBodyValidator,
|
|
}
|
|
),
|
|
}
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: AsyncApp
|
|
:sync: AsyncApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import AsyncApp
|
|
|
|
app = AsyncApp(__name__, validator_map=validator_map)
|
|
app.add_api("openapi.yaml", validator_map=validator_map)
|
|
|
|
|
|
.. tab-item:: FlaskApp
|
|
:sync: FlaskApp
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion import FlaskApp
|
|
|
|
app = FlaskApp(__name__, validator_map=validator_map)
|
|
app.add_api("openapi.yaml", validator_map=validator_map)
|
|
|
|
.. tab-item:: ConnexionMiddleware
|
|
:sync: ConnexionMiddleware
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from asgi_framework import App
|
|
from connexion import ConnexionMiddleware
|
|
|
|
app = App(__name__)
|
|
app = ConnexionMiddleware(app, validator_map=validator_map)
|
|
app.add_api("openapi.yaml", validator_map=validator_map)
|
|
|
|
|
|
Note that this will override the ``"response"`` section of the default ``VALIDATOR_MAP``, and
|
|
the ``"response"`` section only. This means that you need to include all ``ResponseValidators``
|
|
that you want to be active, or they will be removed.
|
|
|
|
If you want to deactivate request validation, you can pass in an empty dictionary:
|
|
|
|
.. code-block:: python
|
|
|
|
validator_map = {
|
|
"body": {}
|
|
}
|
|
|
|
Which you then pass into your application or API as mentioned above.
|
|
|
|
Inserting requestBody defaults
|
|
``````````````````````````````
|
|
|
|
You can let Connexion automatically insert default values as defined in your specification into
|
|
an incoming ``requestBody`` by configuring the ``DefaultsJSONRequestBodyValidator``:
|
|
|
|
.. code-block:: python
|
|
:caption: **app.py**
|
|
|
|
from connexion.datastructures import MediaTypeDict
|
|
from connexion.validators import (
|
|
DefaultsJSONRequestBodyValidator,
|
|
FormDataValidator,
|
|
MultiPartFormDataValidator,
|
|
)
|
|
|
|
validator_map = {
|
|
"body": MediaTypeDict(
|
|
{
|
|
"*/*json": DefaultsJSONRequestBodyValidator,
|
|
"application/x-www-form-urlencoded": FormDataValidator,
|
|
"multipart/form-data": MultiPartFormDataValidator,
|
|
}
|
|
),
|
|
}
|
|
|
|
Which you then pass into your application or API as mentioned above.
|
|
|
|
See our `enforce defaults`_ example for a full example.
|
|
|
|
.. _enforce defaults: https://github.com/spec-first/connexion/tree/main/examples/enforcedefaults
|
|
.. _jsonschema: https://github.com/python-jsonschema/jsonschema |