openapi_handler
is a library translating OpenAPI requests
into native Erlang function calls. It takes schema (compiled single file JSON or YAML), extracts, validates
and converts request parameters to Erlang types.
First, you need to compile cowboy routes for your schema.
PetstoreRoutes = openapi_handler:routes(#{
schema => PetstorePath, % path/to/your/schema.{json,yaml}
prefix => <<"/api/prefix">>, % HTTP path prefix for your API
name => petstore_server_api, % API identifier, must be unique
module => petstore_impl, % A module with functions to call
schema_opts => #{validators => #{}} % (optional) schema processing options (see below)
}),
cowboy:start_clear(petstore_api_server, [{port, 8000}],
#{env => #{dispatch => cowboy_router:compile([{'_', PetstoreRoutes}])}}),
Your callback module needs authorize function:
authorize(#{authorization := AuthorizationHeader, operationId := OperationId, args := OperationParams,
ip := _, name := ApiName, accept := _}) ->
% Any map for success (will appear as auth_context),
% {error, _} on bad auth
#{user_id => 42}.
Then, for each operationId
you want to handle, create a function with that name in the callback module.
For example, assume part of your schema is:
paths:
'/user/{username}':
get:
operationId: getUserByName
parameters:
- name: username
in: path
required: true
schema:
type: string
When a client performs GET /api/prefix/user/Jack
, the corresponding function is called
with a map of parameters as a single argument:
petstore_impl:getUserByName(#{username => <<"Jack">>} = _OperationArgs) ->
#{id => 3418, email => <<"jack@example.com">>}.
OperationArgs
is OperationParams with some added fields:
auth_context
-- a term returned byauthorize/1
callback
Valid callback return values are:
{json, StatusCode, #{} = RespObject}
-- the server will respond with given status and RespObject encoded as JSON in body{json, StatusCode, undefined}
-- the server will respond with given status and no body{error, badrequest | enoent | unavailable}
-- shortcuts for statuses 400, 404, 503 accordingly with minimal status description in bodyok
-- status 204 with no body#{} = RespObject
-- shortcut to{json, 200, RespObject}
<<_/binary>>
-- status 200 and exactly this body{raw, Code, #{} = Headers, <<_/binary>> = Body}
-- the server will respond with given status Code, Headers and Body, Heade 7AD5 rs MUST include 'Content-Type' header
If your schema describes responses
, the callback return value is validated against response schema.
E.g., if you return an atom or a binary where schema requires integer, it will be an error, and response code will be 500.
Also see validation quirks below.
Schema | Erlang |
---|---|
string | binary |
integer | integer |
number | number |
enum | atom |
oneOf(const) | atom |
boolean | boolean |
You can customize the way objects are processed. Available options:
#{extra_obj_key => drop}
-- the original object may contain keys not described by schema, just ignore them instead of raising an error#{required_obj_keys => error}
-- raise an error when original object misses some required keys#{validators => #{Format :: atom() => Validator}}
Validator :: fun(Value) -> {ok, ConvertedValue} | {error, #{}}
If you useformat
in your schema, this option allows you to specify how these formats are processed.
Return value may contain converted input (e.g. when you want to be flexible in accepted time formats).
openapi_handler
allows undefined
value for non-nullable
fields and drops these fields.
This behaviour allows writing simple code like Response = #{key1 => key1_getter(State)}
without
further complex fillering of each key/value pair.