Oaskit.Plugs.ValidateRequest (oaskit v0.6.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
endThis 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
endFinally, 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
typeofbooleanorintegerwill be cast to that type. Arrays of such types are supported as well. - Parameters with type
stringand aformatsupported byJSVwill 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: 1000000, validate_utf8: true].:pretty_errors- A boolean to control pretty printing of JSON errors payload in error handlers. Defaults totruewhenMix.env() != :prod, defaults tofalseotherwise.: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.:security- A plug module or{module, options}. This plug will be invoked when an operation declares the:securityoption, or when the OpenAPI specification declares security at the root level.:error_handler- A module or{module, argument}tuple. The error handler must implement theOaskit.ErrorHandlerbehaviour. It will be called on validation errors. Defaults toOaskit.ErrorHandler.Default.- Other unknown options are preserved 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.
Security
To enable security validation, set the :security option on this plug:
plug Oaskit.Plugs.ValidateRequest,
security: MyApp.Plugs.ApiSecurity
# Or with custom options. The argument MUST be a keyword list.
plug Oaskit.Plugs.ValidateRequest,
security: {MyApp.Plugs.ApiSecurity, log_level: :debug}For details on implementing security plugs and handling authorization, see the Security Guide.
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, that argument
contains the options passed to the plug.
plug Oaskit.Plugs.ValidateRequest,
error_handler: MyErrorHandler,
pretty_errors: true,
custom_opt: "foo"This will allow the handler to receive :pretty_errors and :custom_opt.
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.