PhoenixSpectral.Controller (phoenix_spectral v0.5.0)

Copy Markdown View Source

A Phoenix controller module that validates requests and responses using typespecs.

When you use PhoenixSpectral.Controller, your controller actions use the convention (conn, path_args, query_params, headers, body) instead of the standard Phoenix (conn, params). Request data is decoded and validated against your typespecs, and responses are encoded automatically.

Usage

defmodule MyAppWeb.UserController do
  use PhoenixSpectral.Controller

  @spec show(Plug.Conn.t(), %{id: String.t()}, %{}, %{}, nil) :: {200, %{}, User.t()}
  def show(_conn, path_args, _query_params, _headers, _body) do
    user = Repo.get!(User, path_args.id)
    {200, %{}, user}
  end

  @spec create(Plug.Conn.t(), %{}, %{}, %{}, UserInput.t()) :: {201, %{}, User.t()} | {422, %{}, Error.t()}
  def create(_conn, _path_args, _query_params, _headers, body) do
    case Repo.insert(body) do
      {:ok, user} -> {201, %{}, user}
      {:error, changeset} -> {422, %{}, format_errors(changeset)}
    end
  end
end

Options

Options are forwarded to use Phoenix.Controller (e.g. formats: [:json]).

Required vs optional query params and headers

  • %{required(:key) => type} — missing key returns 400 Bad Request
  • %{optional(:key) => type} — missing key is omitted from the decoded map

Path parameters are always required.

OpenAPI annotations with spectral/1

use PhoenixSpectral.Controller implies use Spectral. Use spectral/1 before @spec to annotate the endpoint with summary: and description:. On type aliases, spectral description: "..." adds a description to the parameter in the OpenAPI output.

Actions without @spec

Actions without a @spec crash on dispatch and during OpenAPI generation — use a plain use Phoenix.Controller module to bypass PhoenixSpectral entirely.

Using conn

conn is always passed as the first argument. Use it for out-of-band context — conn.assigns (auth from upstream plugs), conn.remote_ip, conn.host, conn.method, conn.private, etc.

Do not use conn to access data that is already decoded and validated by the framework: use path_args, query_params, headers, and body instead. Reading from conn.path_params, conn.query_params, conn.req_headers, or conn.body_params directly bypasses type validation.

An action may also return conn directly for streaming, file sends, or any other response that cannot be expressed as {status, headers, body}. In that case, PhoenixSpectral passes the conn through without schema validation — the typespec still documents the endpoint, but the response is the caller's responsibility.

How It Works

  1. Extracts path params, query params, headers, and body from conn
  2. Decodes and validates them against the action's typespec via Spectral.decode
  3. Calls your handler as action(conn, path_args, query_params, headers, decoded_body)
  4. Encodes the {status, headers, body} response via Spectral.encode
  5. Sends the response on conn
  6. On validation failure, returns a 400 response

Summary

Functions

dispatch(conn, controller, action)