# `Oaskit.Controller`
[🔗](https://github.com/lud/oaskit/blob/v0.13.1/lib/oaskit/controller.ex#L1)

Provides macros to define OpenAPI operations directly from controllers.

Macros requires to `use Oaskit.Controller` from your controllers. This
can be done wherever `use Phoenix.Controller` is called. With Phoenix, this is
generally in your `MyAppWeb` module, in the `controller` function:

    defmodule MyAppWeb do
      def controller do
        quote do
          use Phoenix.Controller,
            formats: [:html, :json],
            layouts: [html: MyAppWeb.Layouts]

          use Oaskit.Controller # <-- Add it there once for all

          use Gettext, backend: MyAppWeb.Gettext

          import Plug.Conn

          # This is also where you will plug the validation
          plug Oaskit.Plugs.ValidateRequest

          unquote(verified_routes())
        end
      end
    end

It can also be useful to define a new `api_controller` function, to separate
controllers that define an HTTP API.

You would then use that function in your API controllers:

    defmodule MyAppWeb.UserController do
      use MyAppWeb, :api_controller

      # ...
    end

# `body_params`

Returns the validated body from the given `Plug.Conn` struct.

# `header_param`

Accepts a `Plug.Conn` struct, a parameter name (as atom) and a default value.

Returns the validated parameter from `conn.oaskit.private.header_params` if
found, or the default value.

# `path_param`

Accepts a `Plug.Conn` struct, a parameter name (as atom) and a default value.

Returns the validated parameter from `conn.oaskit.private.path_params` if
found, or the default value.

# `query_param`

Accepts a `Plug.Conn` struct, a parameter name (as atom) and a default value.

Returns the validated parameter from `conn.oaskit.private.query_params` if
found, or the default value.

# `runtime?`

# `operation`
*macro* 

Defines an OpenAPI operation for the given Phoenix action (the function listed
in the router that will handle the conn) that can be validated automatically
with the `Oaskit.Plugs.ValidateRequest` plug automatically.

This macro accepts the function name and a list of options that will define an
`Oaskit.Spec.Operation`.

## Options

* `:operation_id` - The ID of the operation that is used throughout the
  validation features. If missing, an id is automatically generated. Operation
  IDs must be unique.
* `:tags` - A list of tags (strings) to attach to the operation.
* `:description` - An optional string to describe the operation in the OpenAPI
  spec.
* `:summary` - A short summary of what the operation does.
* `:parameters` - A keyword list with parameter names as keys and parameter
  definitions as values. Parameters are query params but also path params. See
  below for more information.
* `:request_body` - A map of possible content types and responses definitions.
  A schema module can be given directly to define a single
* `:responses` - A map or keyword list where keys are status codes (integers
  or atoms) and values are responses definitions. See below for responses
  formats.

Pass `false` instead of the options to ignore an action function.

## Defining parameters

Parameters are organized by their name and their `:in` option. Two parameters
with the same key can coexist if their `:in` option is different. The `:query`
and `:path` values for `:in` are currently supported.

Parameters support the following options:

* `:in` - Either `:path` or `:query`. Required.
* `:schema` - A JSON schema or Module name exporting a `json_schema/0` function.
* `:required` - A boolean, defaults to `true` for `:path` params, `false`
  otherwise.
* `:examples` - A list of examples.

Parameters are stored into `conn.private.oaskit.path_params` and
`conn.private.oaskit.query_params`. They do not override the `params`
argument passed to your phoenix action function. Those original `params` are
still the ones as decoded by phoenix.

### Parameters example

    # Imaginary GET /api/users/:organization route

    operation :list_users,
      operation_id: "ListUsers",
      parameters: [
        organization: [in: :path,  required: true,  schema: %{type: :string}],
        page:         [in: :query, required: false, schema: %{type: :integer, minimum: 1}],
        per_page:     [in: :query, required: false, schema: %{type: :integer, minimum: 1}]
      ],
      # ...

    def list_users(conn, _params) do
      page = query_param(conn, :page)
      per_page = query_param(conn, :per_page)
      do_something_with(conn, page, per_page)
    end

## Defining the request body

Request bodies can be defined in two ways: Either by providing a mapping of
content-type to media type objects, or with a shortcut by providing only a
schema for an unique `"application/json"` content-type.

The body can be retrieved in `conn.oaskit.private.body_params`.

Options supported with a generic definition, for each content type:

* `:content` - A map of content-type to bodies definitions. Content-types
  should be strings.
* `:required` - A boolean. When `false`, the body can be missing and will not
  be validated. In that case, `conn.oaskit.private.body_params` will be
  `nil`. The default value is `false`.

When using the shortcut, a single atom or 2-tuple is expected.

* Supported atoms are `true` (a JSON schema that accepts anything), `false` (a
  JSON schema that rejects everything) or a module name. The module must
  export a `json_schema/0` function that returns a JSON schema.
* When passing a tuple, the first element is a schema (boolean or module), but
  a direct JSON schema map (like `%{type: :object, ...}`) is also accepted.
  The second tuple element is a list of options for the response body object.

**Important**, when using the shortcut, we chose to automatically define the
`:required` option of the media type object to `true`.

### Request body examples

A short form using a module schema:

    operation :create_user,
      operation_id: "CreateUser",
      request_body: UserSchema,
      # ...

    def create_user(conn, _params) do
      case Users.create_user(conn.private.oaskit.body_params) do
        # ...
      end
    end

The operation definition above is equivalent to this:

    operation :create_user,
      operation_id: "CreateUser",
      request_body: [
        content: %{"application/json" => %{schema: CreateUserPayload}},
        required: true
      ],
      # ...

To make the body non-required in the short form, use the tuple version:

    operation :create_user,
      operation_id: "CreateUser",
      request_body: {UserSchema, required: false},
      # ...

Multiple content-types can be supported. Content-types with wildcards will be
tried last by `Plug.Parsers`, as well as oaskit when choosing
the schema for validation.

    operation :create_user,
      request_body: [
        content: %{
          "application/x-www-form-urlencoded" => %{schema: CreateUserPayload},
          "application/json" => %{schema: CreateUserPayload},
          "*/*" => %{schema: %{type: :string}}
        }
      ]

## Defining responses

Responses are defined by a mapping of HTTP statuses to response objects.

* HTTP statuses can be given as integers (`200`, `404`, _etc._) or atoms
  supported by `Plug.Conn.Status` like `:ok`, `:not_found`, _etc_.
* `:default` can be given instead of a status to define the default option
  supported by the OpenAPI speficication. This is often used to define a
  generic error response.

Response objects accept the following options:

* `:description` - This is mandatory for responses.
* `:headers` and `:links` - This is not used by the validation mechanisms of
  this library, but is useful to be defined in the OpenAPI specification JSON
  document.
* `:content` - A mapping of content-type to media type objects, exactly as in
  the request bodies definitions.

Finally, the response for each status can also be defined with a shortcut, by
using a single schema that will be associated to the `"application/json"`
content-type. The mandatory description can be provided when using the tuple
shortcut, or will otherwise being pulled from the schema `description`
keyword.

### Reponse examples

A first example using the atom statuses, and a shortcut for the full response
definition:

    operation :list_users,
      operation_id: "ListUsers",
      responses: [ok: UsersListPage]

The definition above is equivalent to the following:

    operation :list_users,
      operation_id: "ListUsers",
      responses: %{
        200 => [
          description: UsersListPage.json_schema().description,
          content: %{
            "application/json" => %{schema: UsersListPage}
          }
        ]
      }

The description can be overriden when using the shortcut:

    operation :list_users,
      operation_id: "ListUsers",
      responses: [ok: {UsersListPage, description: "A page of users"}]

Multiple status codes are generally expected. The shortcut can be used in only
a part of them.

    operation :list_users,
      operation_id: "ListUsers",
      responses: [
        ok: UsersListPage,
        not_found: {GenericErrorSchema, description: "not found generic response"},
        forbidden: {%{type: :array}, description: "missing-role messages"},
        internal_server_error: [
          description: "Error with stacktrace",
          content: %{
            "application/json" => [
              schema: %{type: :array, items: %{type: :string, description: "trace item"}}
            ],
            "text/plain" => [schema: true]
          }
        ]
      ]

Of course, mixing all styles together is discouraged for readability.

## Ignore operations

# `parameter`
*macro* 

Defines a parameter for all operations defined _later_ in the module body with
the `operation/2` macro.

Takes the same options as the `:parameters` option items from that macro.

If an operation also directly defines a parameter with the same `name` and
`:in` option, it will take precedence and the parameter defined with
`parameter/2` will be ignored.

## Example

In the following example, the second operation defines its own version of the
`per_page` parameter to limit the number of users returned in a single page.

    # This macro can be called multiple times
    parameter :slug, in: :path, schema: %{type: :string, pattern: "[0-9a-z-]+"}
    parameter :page, in: :query, schema: %{type: :integer, minimum: 1}
    parameter :per_page, in: :query, schema: %{type: :integer, minimum: 1, maximum: 100}

    operation :list_users, operation_id: "ListUsers", responses: [ok: UsersListPage]

    def list_users(conn, params) do
      # ...
    end

    operation :list_users_deep,
      operation_id: "ListUsersDeep",
      parameters: [
        per_page: [in: :query, schema: %{type: :integer, minimum: 1, maximum: 20}]
      ],
      responses: [
        ok:
          {DeepUsersListPage,
           description: "Returns users with all associated organization and blog posts data"}
      ]

    def list_users_deep(conn, params) do
      # ...
    end

# `tags`
*macro* 

Defines tags for all operations defined _later_ in the module body with the
`operation/2` macro.

If an operation also directly defines tags, they will be merged.

## Example

    # This macro can be called multiple times
    tags ["users", "v1"]
    tags ["other-tag"]

    operation :list_users,
      operation_id: "ListUsers",
      responses: [ok: UsersListPage]

    def list_users(conn, params) do
      # ...
    end

    operation :list_users_deep,
      operation_id: "ListUsersDeep",
      tags: ["slow"],
      responses: [ok: DeepUsersListPage]

    def list_users_deep(conn, params) do
      # ...
    end

# `use_operation`
*macro* 

This macro allows Oaskit to validate request bodies, query and path
parameters (and responses in tests) when an OpenAPI specification is not
defined with the `operation/2` macro but rather provided directly in an
external spec document.

For instance with the following spec module:

    defmodule MyAppWeb.ExternalAPISpec do
      use Oaskit
      @api_spec JSON.decode!(File.read!("priv/api/spec.json"))

      @impl true
      def spec, do: @api_spec
    end

Given the `spec.json` file decribes an operation whose `operationId` is
`"ListUsers"`, then the request/response validation can be enabled like this:

    use_operation :list_users, "ListUsers"

    def list_users(conn, params) do
      # ...
    end

> #### Parameter names always create atoms {: .warning}
>
> Query and path parameters defined in OpenAPI specifications always define
> the corresponding atoms, even if that specification is read from a JSON
> file, or defined manually in code with string keys.
>
> For that reason it is ill advised to use specs generated dynamically at
> runtime without validating their content.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
