apigenie-generatorOpenAPI serversserver phoenix
server phoenix
Generate a Phoenix 1.7 application from an OpenAPI document.
From an AsyncAPI document, the command generates Phoenix channels and Kafka pipelines instead; see apigenie-generator server phoenix (AsyncAPI).
Usage
apigenie-generator server phoenix [...OPTIONS] INPUT_FILE
Description
The generator writes a Mix project (Elixir 1.18 or later) depending on Phoenix 1.7, Bandit and
Jason, without Ecto, HTML, assets or a mailer. Its code passes mix format --check-formatted,
mix compile --warnings-as-errors and mix credo --strict, and mix phx.server serves it.
| File | Contents |
|---|---|
lib/<app>_web/router.ex |
the routes of the operations, under the path of the first server |
lib/<app>_web/api/<tag>.ex |
a behaviour per tag (Default for operations without tags), with a callback per operation |
lib/<app>_web/controllers/<tag>_controller.ex |
an action per operation, calling the implementation of the behaviour |
lib/<app>/models/*.ex |
a module per schema: structs, enums, unions and aliases, with new/1, new!/1 and to_map/1 |
lib/<app>_web/runtime/*.ex |
the checks of credentials, the decoding of parameters and bodies, the encoding of responses |
lib/<app>_web/authenticator.ex |
the behaviour checking credentials |
lib/<app>_web/endpoint.ex, error_json.ex, lib/<app>/application.ex |
the endpoint, the errors Phoenix renders, the application |
config/*.exs, mix.exs, .formatter.exs, .credo.exs, .gitignore, README.md |
the project |
mix deps.get
PORT=8080 mix phx.server
Routes
Every operation has a route in <App>Web.Router, in the scope of the path of the first server
(/v2 for https://api.example.com/v2, or the base_path option). Methods without a Phoenix macro,
such as QUERY and the COPY of additionalOperations, are routed with match. Static segments
come before parameters (/shipments/search before /shipments/{shipmentId}), as Phoenix tries the
routes in order. A segment holding parameters is captured whole and split by the runtime with its
template, so /labels/{labelId}.{extension} works.
Behaviours and implementations
The operations of each tag are the callbacks of a behaviour, <App>Web.Api.<Tag>. A callback
receives the connection, the parameters and the body:
defmodule MyApp.Shipments do
@behaviour CarpetExpressWeb.Api.Shipments
@impl true
def get_shipment(_conn, %{shipment_id: id}, nil) do
case MyApp.Store.fetch(id) do
{:ok, shipment} -> {200, shipment}
:error -> {404, %{"type" => "about:blank", "title" => "Not Found", "status" => 404}}
end
end
end
and is configured as the implementation:
config :carpet_express, CarpetExpressWeb.Api.Shipments, impl: MyApp.Shipments
Operations without an implementation answer 501 Not Implemented. A callback returns
{status, body}, {status, body, headers}, or the connection after sending a response itself
(e.g. a chunked stream). The body is encoded in the media type the document gives the status (the
exact code, then its range, then default): JSON with the type of the response, so a model struct
or a plain map; text and bytes as they are; nothing for responses without content.
Parameters and bodies
The actions check the request before calling the implementation, and answer a problem document
(RFC 9457, application/problem+json) when it doesn’t match the description:
- 401 when the request has none of the credentials of the operation’s security requirements;
- 400 when a parameter is missing or invalid (the
errorslist the parameters), or the body is missing, isn’t JSON or doesn’t match its schema; - 415 for a media type the operation doesn’t accept.
Parameters are decoded from their styles into a map with atom keys (%{shipment_id: "...", limit: 20}): simple, label and matrix path parameters, form, spaceDelimited,
pipeDelimited and deepObject query parameters, headers, cookies, and parameters described with
content as JSON. Integers, numbers and booleans are converted from text, models and enums
decoded with their schemas.
| Media type | Body |
|---|---|
JSON (application/json, *+json) |
the model, a list or the value its schema describes |
application/x-www-form-urlencoded |
the map of the fields |
multipart/form-data |
the map of the parts, files as Plug.Upload structs |
text/* |
a string |
| anything else | the bytes |
Plug.Parsers reads JSON, form and multipart bodies of up to 20 MB; the runtime parses the JSON
and form bodies of methods Plug.Parsers leaves alone, such as QUERY.
Security
The first security requirement of an operation whose credentials the request presents is checked:
API keys in headers, the query or cookies, HTTP bearer and basic, and OAuth2 and OpenID Connect
tokens as bearer tokens. Without an implementation of <App>Web.Authenticator, every presented
credential is accepted; with one,
config :carpet_express, CarpetExpressWeb.Authenticator, impl: MyApp.Authenticator
its authenticate(conn, scheme, credential, scopes) returns {:ok, principal}, assigned to the
connection as :principal, or {:error, reason}, answered with 401.
Types
The models are the modules of the Elixir models,
shared with client elixir: a struct per object (with allOf flattened), atoms for string enums,
a module dispatching on the discriminator for oneOf and anyOf, Date, DateTime,
NaiveDateTime and Time for dates and times, binaries for format: byte (decoded from base64)
and format: binary, additional_properties maps, and | nil for nullable values.
Extensions
| Extension | Effect |
|---|---|
x-apigenie.codegen.name |
name of the module of a component schema, unless module_overrides names it |
x-apigenie.codegen.action-name |
name of the action and callback of an operation without an operationId (with the default action_naming, which lists id first) |
x-apigenie.codegen.disabled |
true on an operation or its path item leaves the operation out |
x-internal |
true on an operation or its path item leaves the operation out, unless internal_operations is set |
x-defaultMapping |
defaultMapping of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn’t mapped |
The extensions can live in an overlay for this target instead of in the document, e.g.
phoenix.overlay.yaml:
overlay: 1.0.0
info:
title: Elixir names
version: 1.0.0
actions:
- target: $.components.schemas.Pet
update:
x-apigenie:
codegen:
name: Animal
- target: $.paths['/admin']
update:
x-internal: true
apigenie-generator server phoenix openapi.yaml --overlay phoenix.overlay.yaml -o out
Limitations
text/event-streamand JSON Lines responses are sent whole unless the implementation sends the response itself.- The models don’t validate
minimum,maximum,patternand the other constraints, and ignoreprefixItems,patternPropertiesand conditional schemas. readOnlyandwriteOnlyproperties are fields of the same struct.- Every operation is served under the base path; operation and path servers are ignored.
- Callbacks, webhooks and links aren’t generated.
- The endpoint serves HTTP; mutual TLS needs an
httpsconfiguration of the endpoint.
Options
-c, –config
Path to the configuration file to use. See the “Configuration” section for more information.
–overlay OVERLAY
Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the
extensions of the “Extensions” section. Can be repeated: the overlays apply in order, after those
listed under commands.generate.server_phoenix.overlay in the configuration file (with paths
relative to it). Warnings and errors about a value an overlay set point to the overlay.
–strict
Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated)
instead of a warning (OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.).
-o, –output
Directory where the project is generated.
–package-name
Name of the Mix application, converted to snake_case (carpet-express gives carpet_express,
the root module CarpetExpress and the web modules CarpetExpressWeb). Defaults to the name of
the output directory.
–package-version
Version of the Mix project; 0.1.0 by default.
-p, –property NAME=VALUE
Sets a generator option (see below), e.g. -p base_path=/api. Options given on the command line
take precedence over the configuration file. Lists are comma-separated (-p include_tags=pets,store)
and mappings too (-p module_overrides=pet-owner=Owner).
Configuration
version: '1.0'
commands:
generate:
server_phoenix:
app: carpet_express
base_path: /v2
exclude_tags: [internal]
| Option | Type | Default | Description |
|---|---|---|---|
app |
string | name of the output directory | Name of the Mix application (snake_case, the directory under lib/); also set by --package-name. |
namespace |
string | the application in PascalCase | Root module of the generated modules, e.g. CarpetExpress; the web modules are in CarpetExpressWeb. |
base_path |
string | the path of the first server | Path the routes are mounted at, e.g. /v2; / mounts them at the root. |
include_tags |
list of strings | Generates only the operations with one of these tags. | |
exclude_tags |
list of strings | Skips the operations with one of these tags. | |
skip_prune |
boolean | false |
Keeps the component schemas that no generated operation uses. |
internal_operations |
boolean | false |
Also generates the operations marked x-internal: true. |
dev_deps |
boolean | true |
Lists credo as a dependency of mix.exs (only: [:dev, :test]). |
action_naming |
list of id, extension, url |
id, extension, url |
Where the names of actions and callbacks come from, in order of preference. |
field_prefix |
string | field_ |
Prefix of struct fields whose property names give no valid name. |
module_overrides |
map of strings | Module names of component schemas, e.g. pet-owner: Owner. |