Oaskit is an OpenAPI 3.1 library for Elixir and Phoenix: spec generation, request validation and casting, built on JSON Schema 2020-12.
It provides macros and plugs to automatically validate incoming HTTP requests against the OpenAPI Specification v3.1.
- Request bodies, path, query and header parameters validation with JSON Schema 2020-12, powered by JSV, a validator that passes the official JSON Schema test suite.
- Full OpenAPI 3.1 support:
type: ["string", "null"]instead ofnullable: true,oneOf/anyOf/allOf,$refand$defsbehave as the JSON Schema specification says. - Schemas are modules, plain Elixir maps, or JSON files: anything JSV accepts.
- Response validation helpers for your tests.
- Mix task for JSON file specification generation, and a controller to serve the spec and a Redoc UI.
- Heavily inspired by OpenApiSpex, which targets OpenAPI 3.0. Oaskit is the choice when you need OpenAPI 3.1.
The full documentation is available on hexdocs:
defp deps do
[
{:oaskit, "~> 0.16"},
]
endYou can also import formatter rules in your .formatter.exs file:
[
import_deps: [:oaskit]
]A condensed tour. The Quickstart Guide walks through each step in more detail.
The spec module is the root of your OpenAPI document. Paths are collected from the Phoenix router.
defmodule MyAppWeb.ApiSpec do
alias Oaskit.Spec.Paths
alias Oaskit.Spec.Server
use Oaskit
@impl true
def spec do
%{
openapi: "3.1.1",
info: %{title: "My App API", version: "1.0.0"},
servers: [Server.from_config(:my_app, MyAppWeb.Endpoint)],
paths: Paths.from_router(MyAppWeb.Router, filter: &String.starts_with?(&1.path, "/api/"))
}
end
endDeclare the spec in a router pipeline, and add the validation plug to your controllers.
# router.ex
pipeline :api do
plug :accepts, ["json"]
plug Oaskit.Plugs.SpecProvider, spec: MyAppWeb.ApiSpec
end
scope "/api", MyAppWeb do
pipe_through :api
get "/users", UserController, :index
post "/users", UserController, :create
patch "/users/:id", UserController, :update
end
# my_app_web.ex
def controller do
quote do
use Phoenix.Controller, formats: [:json]
use Oaskit.Controller
plug Oaskit.Plugs.ValidateRequest
# ...
end
endOaskit validates with JSV, so a schema can be a module, a plain Elixir map, or a JSON document decoded from a file.
A schema module defined with JSV.defschema/3 is referenced by name, and
valid request bodies are cast to its struct. Properties are required unless
wrapped with optional/1, and the struct can be encoded to JSON.
defmodule MyAppWeb.Schemas do
use JSV.Schema
defschema User,
name: non_empty_string(),
email: email(),
# OpenAPI 3.1: a type union, no more `nullable: true`
nickname: optional(%{type: [:string, :null]})
endAn inline schema is plain Elixir data, given directly or with the
{schema, options} form.
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyAppWeb.Schemas.User
# Parameters are validated and cast: `limit` is an integer here
operation :index,
parameters: [
limit: [in: :query, schema: %{type: :integer, minimum: 1, maximum: 100}]
],
responses: [ok: {%{type: :array, items: User}, []}]
def index(conn, _params) do
users = MyApp.Users.list(limit: query_param(conn, :limit, 20))
json(conn, users)
end
# Using a schema module
operation :create,
request_body: User,
responses: [created: User]
def create(conn, _params) do
%User{} = user = body_params(conn)
# ...
end
# Using an inline schema
operation :update,
parameters: [id: [in: :path, schema: %{type: :integer}]],
request_body:
{%{
type: :object,
properties: %{email: %{type: :string, format: :email}},
required: [:email],
additionalProperties: false
}, required: true},
responses: [ok: User]
def update(conn, _params) do
id = path_param(conn, :id)
%{"email" => email} = body_params(conn)
# ...
end
endInvalid requests are rejected with a JSON response describing the errors: 400
for invalid parameters, 422 for an invalid body and 415 for an unsupported
content type. Errors can be rendered your own way with the :error_handler
option of Oaskit.Plugs.ValidateRequest.
Oaskit.Test.valid_response/3 checks the status, content type and body of a
response against your spec, and returns the decoded body.
test "create user", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post(~p"/api/users", %{name: "Alice", email: "alice@example.com"})
assert %{"name" => "Alice"} = Oaskit.Test.valid_response(MyAppWeb.ApiSpec, conn, 201)
endWrite the spec to a file, for client generators or CI checks:
mix openapi.dump MyAppWeb.ApiSpec --pretty -o priv/openapi.jsonOr serve it, with a Redoc UI:
get "/openapi.json", Oaskit.SpecController, spec: MyAppWeb.ApiSpec
get "/docs", Oaskit.SpecController, redoc: "/openapi.json"Pull requests are welcome, provided they include appropriate tests and documentation.
- Serve Swagger UI.
- Allow custom formatters for the
openapi.dumpMix task, to support other output formats such as YAML.