Oaskit.Plugs.ValidateRequest (oaskit v0.2.0)
View SourceThis plug will match incoming requests to operations defined with the
Oaskit.Controller.operation/2
or
Oaskit.Controller.use_operation/2
macros and retrieved from a
provided OpenAPI Specification module.
Pluggin' in
To use this plug in a controller, the
Oaskit.Plugs.SpecProvider
plug must be used in the router for
the corresponding routes.
defmodule MyAppWeb.Router do
use Phoenix.Router
pipeline :api do
plug Oaskit.Plugs.SpecProvider, spec: MyAppWeb.OpenAPISpec
end
scope "/api" do
pipe_through :api
scope "/users", MyAppWeb do
get "/", UserController, :list_users
end
end
end
This plug must then be used in controllers. It is possible to call the plug in
every controller where you want validation, or to define it globally in your
MyAppWeb
module.
While you can directly patch the MyAppWeb.controller
function if all your
controllers belong to the HTTP API, we suggest to create a new
api_controller
function in your MyAppWeb
module.
Duplicate the controller
function and add this plug and also use Oaskit.Controller
.
defmodule MyAppWeb do
def controller do
# ...
end
def api_controller do
quote do
use Phoenix.Controller, formats: [:json], layouts: []
# Use the controller helpers to define operations
use Oaskit.Controller
use Gettext, backend: MyAppWeb.Gettext
import Plug.Conn
# Use the plug here. This has to be after `use Phoenix.Controller`.
plug Oaskit.Plugs.ValidateRequest
unquote(verified_routes())
end
end
end
Finally, request body validation will only work if the body is fetched from
the conn. This is generally done by the Plug.Parsers
plug. It can also be
done by a custom plug if you are implementing an API that is working with
plaintext or custom formats.
Request validation
Requests will be validated according to the request body schemas and parameter schemas defined in operations. The data will also be cast to the expected types:
- Parameters whose schemas define a
type
ofboolean
orinteger
will be cast to that type. Arrays of such types are supported as well. - Parameters with type
string
and aformat
supported byJSV
will be also cast according to that format. Output values for formats are described in the JSV documentation. This includes, URI, Date, DateTime and Duration. - Request bodies are cast to their given schema too. When using raw schemas
defined as maps, the main changes to the data is the cast of formats, just
as for parameters. When schemas are defined using module names, and when
those modules' structs are created with
JSV.defschema/1
, the request bodies will be cast to those structs.
Request bodies will be validated according to the expected operation content-types, and the actual content-type of the request.
Options
:query_reader_opts
- If a Plug.Conn struct enters this plug without its query parameters being fetched, this plug will fetch them automatically usingConn.fetch_query_params(conn, query_reader_opts)
. The default value is[length: 1_000_000, validate_utf8: true]
.:error_handler
- A module or{module, argument}
tuple. The error handler must implement theOaskit.ErrorHandler
behaviour. It will be called on validation errors. Defaults toOaskit.ErrorHandler.Default
.:pretty_errors
- A boolean to control pretty printing of JSON errors payload in error handlers. Defaults totrue
whenMix.env() != :prod
, defaults tofalse
otherwise.:html_errors
- A boolean to control whether the default error handler is allowed to return HTML errors when the request accepts HTML. This is useful to quickly read errors when opening an url directly from the browser. Defaults totrue
.- Unknown options are collected and passed to the error handler.
Non-required bodies
A request body is considered empty if ""
, nil
or an empty map (%{}
). In
that case, if the operation defines the request body with required: false
(which is the default value!), the body validation will be skipped.
The empty map is a special case because Plug.Parsers
implementations cannot
return anything else than a map. If a client sends an HTTP request with an
"application/json"
content-type but no body, the JSON parser in the Plug
library will still return an empty map.
To avoid problems, always define request bodies as required if you can. This
is made automatically when using the "definition shortcuts" described in
Oaskit.Controller.operation/2
.
Error handling
Validation can fail at various steps:
- Parameters validation
- Content-type matching
- Body validation
On failure, the validation stops immediately. If a parameter is invalid, the body is not validated and the error handler is called with a single category of errors. In the case of parameters, multiple errors can be passed to the handler if multiple parameters are invalid or missing.
Custom error handlers must implement the Oaskit.ErrorHandler
behaviour and
be ready to accept all error reasons that this plug can generate. Such reasons
are described in the Oaskit.ErrorHandler.reason/0
type.
The 3rd argment passed to the Oaskit.ErrorHandler.handle_error/3
depends
on the :error_handler
function. When defined as a module, the argument is
all the other options given to this plug:
plug Oaskit.Plugs.ValidateRequest,
error_handler: MyErrorHandler,
pretty_errors: true,
custom_opt: "foo"
This will allow the handler to receive :pretty_errors
, :custom_opt
and
other options with default values.
When passing a tuple, the second element will be passed as-is:
plug Oaskit.Plugs.ValidateRequest,
error_handler: {MyErrorHandler, "foo"}
In the example above, the error handler will only receive "foo"
as the 3rd
argument of Oaskit.ErrorHandler.handle_error/3
.