Packages

An elixir library for Phoenix that generates Swagger documentation and performs parameter validation.

Current section

Files

Jump to
valspec lib controller.ex
Raw

lib/controller.ex

defmodule Valspec.Controller do
import Valspec.Utils
@moduledoc """
A module to conveniently generate valspec definitions within Phoenix controllers.
## Example
defmodule MyAppWeb.ExampleUsersController do
use MyAppWeb, :controller
use Valspec.Controller
valspec_create :create_user, summary: "Creates a User" do
required(:first_name, :string, example: "Greg")
required(:role, :enum, values: [:admin, :normal], default: :normal)
optional(:last_name, :string, example: "Jones")
optional(:age, :integer, minimum: 18)
end
def create(conn, params) do
# `valspec_validate/2` - validates a map of parameters. Returns:
# - {:ok, map()} when params are valid
# - {:error, Ecto.%Changeset{}} when params are invalid
with {:ok, user_params} <- valspec_validate(:create_user, params) do
...
else
{:error, %Ecto.Changeset{}} ->
...
end
end
end
# Options:
* `tags`- the Swagger tags for the module. Example: [tags: ["V3 Clients Api"]].
* `default_response_schema`- defines a default response schema for use in Swagger docs
* `default_callback_schema`- defines a default callback schema for use in Swagger docs (Callbacks)
"""
require OpenApiSpex
defmacro __using__(opts) do
quote do
use OpenApiSpex.ControllerSpecs
import Goal
import Valspec.Utils
import Valspec.Controller
if opts_tags = Keyword.get(unquote(opts), :tags) do
tags(opts_tags)
end
default_response_schema = Keyword.get(unquote(opts), :default_response_schema, nil)
Module.put_attribute(__MODULE__, :default_response_schema, default_response_schema)
default_callback_schema = Keyword.get(unquote(opts), :default_callback_schema, nil)
Module.put_attribute(__MODULE__, :default_callback_schema, default_callback_schema)
def valspec_validate(name, params) do
validate_params(
unquote(__CALLER__.module).schema(name),
params
)
end
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for a create controller action.
Example:
valspec_create :create_client,
summary: "Creates a client",
response_schema: MyAppWeb.Response do
required(:name, :string, example: "Client 1")
required(:type, :enum, values: [:vendor, :customer])
required(:account_id, :uuid, example: "82b557d1-c529-4aff-b0fb-55df16add1b5")
end
# Options:
* `summary`- a summary of what the post request does.
* `response_schema`- a response schema defined with the valspec_schema macro.
* `callback_schemas`- a list of tuples with the name of a callback to a schema. Example:
`
[{"Create User Webhook", MyApp.User.WebhookSchema}, {"Create Account Webhook", MyApp.User.WehookSchema}]
`
"""
@spec valspec_create(name :: String.t(), opts :: Keyword.t(), block :: any()) :: Macro.t()
defmacro valspec_create(name, opts, do: expression) do
quote do
valspec_custom(
unquote(name),
:create,
unquote(opts),
do: unquote(expression)
)
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for an update controller action.
Example:
valspec_update :update_client,
summary: "Updates a client",
response_schema: MyAppWeb.Response do
optional(:client_id, :uuid, example: "82b557d1-c529-4aff-b0fb-55df16add1b5")
optional(:location_id, :string,
format: "uuid",
example: "b388a4da-a3d6-42d4-8a98-4149f0007e6b",
description: "the location_id used to do stuff..."
)
end
# Options:
* `summary`- a summary of what the post request does.
* `response_schema`- a response schema defined with the valspec_schema macro.
* `callback_schemas`- a list of tuples with the name of a callback to a schema. Example:
`
[{"Update User Webhook", MyApp.User.WebhookSchema}, {"Update Account Webhook", MyApp.User.WehookSchema}]
`
"""
@spec valspec_update(name :: String.t(), opts :: Keyword.t(), block :: any()) :: Macro.t()
defmacro valspec_update(name, opts, do: expression) do
quote do
valspec_custom(
unquote(name),
:update,
unquote(opts),
do: unquote(expression)
)
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for an index controller action.
Example:
valspec_index(
summary: "Lists all clients",
response_schema: MyAppWeb.IndexResponse,
)
# Options:
* `summary`- a summary of what the post request does.
* `response_schema`- a response struct defined with the valspec_schema macro.
"""
@spec valspec_index(opts :: Keyword.t()) :: Macro.t()
defmacro valspec_index(opts) do
quote do
require OpenApiSpex
summary = Keyword.get(unquote(opts), :summary, "")
open_api_opts = Keyword.get(unquote(opts), :open_api_opts, [])
swagger_opts =
[summary: summary, type: :object]
|> maybe_add_response_schema(unquote(opts), @default_response_schema)
|> maybe_add_open_api_opts(open_api_opts)
operation(:index, swagger_opts)
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for a show controller action.
Example:
valspec_show(
summary: "Show a single client",
response_schema: MyAppApi.V3.Client.JSON.IndexResponse
)
# Options:
* `summary`- a summary of what the post request does.
* `response_schema`- a response struct defined with the valspec_schema macro.
"""
defmacro valspec_show(opts) do
quote do
require OpenApiSpex
summary = Keyword.get(unquote(opts), :summary, "")
open_api_opts = Keyword.get(unquote(opts), :open_api_opts, [])
swagger_opts =
[summary: summary, type: :object]
|> maybe_add_response_schema(unquote(opts), @default_response_schema)
|> maybe_add_open_api_opts(open_api_opts)
operation(:show, swagger_opts)
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for a delete controller action.
## Example:
valspec_delete(
summary: "Deletes a client",
response_schema: MyAppWeb.Response
)
## Options:
* `summary` - Swagger endpoint summary
* `response_schema` - The schema module to generating the response schema
* `callback_schemas`- a list of tuples with the name of a callback to a schema. Example:
`
[{"Delete User Webhook", MyApp.User.WebhookSchema}, {"Delete Account Webhook", MyApp.User.WehookSchema}]
`
"""
@spec valspec_delete(opts :: Keyword.t()) :: Macro.t()
defmacro valspec_delete(opts) do
quote do
valspec_custom(:delete, unquote(opts))
end
end
@doc """
Generates both a Goal validation struct and Swagger schema for a custom controller action.
## Example 1:
valspec_custom(:my_action, summary: "Hello world")
## Example 2:
valspec_custom
:my_validation_name
:my_action,
summary: "Hello world" do
required(:id, :string)
optional(:name, :string)
optional(:location_id, :string,
format: "uuid",
example: "b388a4da-a3d6-42d4-8a98-4149f0007e6b",
description: "the location_id of something..."
)
end
## Options:
* `summary` - Swagger endpoint summary
* `response_schema` - The schema module to generating the response schema
"""
@spec valspec_custom(
name :: atom(),
action :: atom(),
opts :: Keyword.t(),
block :: any()
) :: Macro.t()
defmacro valspec_custom(action, opts) do
quote do
require OpenApiSpex
summary = Keyword.get(unquote(opts), :summary, "")
open_api_opts = Keyword.get(unquote(opts), :open_api_opts, [])
swagger_opts =
[summary: summary, type: :object]
|> maybe_add_response_schema(unquote(opts), @default_response_schema)
|> maybe_add_callback_schemas(unquote(opts), @default_callback_schema)
|> maybe_add_open_api_opts(open_api_opts)
operation(unquote(action), swagger_opts)
end
end
defmacro valspec_custom(name, action, opts, do: expression) do
new_module_name = generate_schema_module_name(__CALLER__.module, name)
new_module = generate_new_schema_module(new_module_name, expression)
quote do
require OpenApiSpex
# Goal...
defparams unquote(name) do
unquote(expression)
end
unquote(new_module)
summary = Keyword.get(unquote(opts), :summary, "")
open_api_opts = Keyword.get(unquote(opts), :open_api_opts, [])
request_body =
{"", "application/json", apply(unquote(Macro.escape(new_module_name)), :schema, [])}
swagger_opts =
[summary: summary, type: :object, request_body: request_body]
|> maybe_add_response_schema(unquote(opts), @default_response_schema)
|> maybe_add_callback_schemas(unquote(opts), @default_callback_schema)
|> maybe_add_open_api_opts(open_api_opts)
operation(unquote(action), swagger_opts)
end
end
defp generate_new_schema_module(module, expr) do
quote do
defmodule unquote(module) do
def schema do
unquote(Macro.escape(generate_swagger_schema(expr)))
# A single schema 'field' macro (field, embeds_one, embeds_many, etc.) may not
# be passed in with a __block__ attribute, which is where we normally add the :type, and
# :properties map needed to correctly generate the schema within generate_swagger_schema/1...
|> then(fn maybe_schema ->
if Map.has_key?(maybe_schema, :properties) do
maybe_schema
else
%{:type => :object, :properties => maybe_schema}
end
end)
|> OpenApiSpex.build_schema()
end
end
end
end
defp generate_schema_module_name(module, name) do
module
|> Module.concat(Schemas)
|> Module.concat(Macro.camelize(Atom.to_string(name)))
|> Macro.escape()
end
end