diff --git a/.github/workflows/samples-erlang.yaml b/.github/workflows/samples-erlang.yaml
index c9e0981e49c4..95cda5dc5a3b 100644
--- a/.github/workflows/samples-erlang.yaml
+++ b/.github/workflows/samples-erlang.yaml
@@ -3,33 +3,33 @@ name: Samples Erlang
on:
push:
paths:
- # comment out due to errors
- # ===> Compiling src/openapi_pet_handler.erl failed
- # src/openapi_pet_handler.erl:278: function is_authorized/2 already defined
- #- samples/server/petstore/erlang-server/**
+ - samples/server/echo_api/erlang-server/**
+ - samples/server/petstore/erlang-server/**
- samples/client/petstore/erlang-client/**
- samples/client/petstore/erlang-proper/**
pull_request:
paths:
- #- samples/server/petstore/erlang-server/**
+ - samples/server/echo_api/erlang-server/**
+ - samples/server/petstore/erlang-server/**
- samples/client/petstore/erlang-client/**
- samples/client/petstore/erlang-proper/**
jobs:
build:
name: Build Erlang projects
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
sample:
- #- samples/server/petstore/erlang-server/
+ - samples/server/echo_api/erlang-server/
+ - samples/server/petstore/erlang-server/
- samples/client/petstore/erlang-client/
- samples/client/petstore/erlang-proper/
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
- otp-version: '22.2'
- rebar3-version: '3.14.3'
+ otp-version: '27'
+ rebar3-version: '3.23.0'
- run: rebar3 compile
working-directory: ${{ matrix.sample }}
diff --git a/bin/configs/erlang-server-echo.yaml b/bin/configs/erlang-server-echo.yaml
new file mode 100644
index 000000000000..7ed821f1c3b1
--- /dev/null
+++ b/bin/configs/erlang-server-echo.yaml
@@ -0,0 +1,4 @@
+generatorName: erlang-server
+outputDir: samples/server/echo_api/erlang-server
+inputSpec: modules/openapi-generator/src/test/resources/3_0/echo_api.yaml
+templateDir: modules/openapi-generator/src/main/resources/erlang-server
diff --git a/docs/generators/erlang-server.md b/docs/generators/erlang-server.md
index 66692f029e7d..824149119699 100644
--- a/docs/generators/erlang-server.md
+++ b/docs/generators/erlang-server.md
@@ -59,6 +59,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
fun
if
let
+maybe
not
of
or
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java
index d41136eb6761..5f8a50a312b0 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java
@@ -99,9 +99,9 @@ public ErlangServerCodegen() {
*/
setReservedWordsLowerCase(
Arrays.asList(
- "after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
- "catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive",
- "rem", "try", "when", "xor"
+ "after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor",
+ "case", "catch", "cond", "div", "end", "fun", "if", "let", "maybe", "not",
+ "of", "or", "orelse", "receive", "rem", "try", "when", "xor"
)
);
@@ -172,10 +172,8 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl")));
supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl")));
supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl")));
- supportingFiles.add(new SupportingFile("utils.mustache", "", toSourceFilePath("utils", "erl")));
supportingFiles.add(new SupportingFile("auth.mustache", "", toSourceFilePath("auth", "erl")));
supportingFiles.add(new SupportingFile("openapi.mustache", "", toPrivFilePath(this.openApiSpecName, "json")));
- supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl")));
supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl")));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
.doNotOverwrite());
@@ -223,7 +221,7 @@ public String getHelp() {
@Override
public String toApiName(String name) {
if (name.length() == 0) {
- return this.packageName + "_default_handler";
+ return this.packageName + "_handler";
}
return this.packageName + "_" + underscore(name) + "_handler";
}
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/README.mustache b/modules/openapi-generator/src/main/resources/erlang-server/README.mustache
index 5d36ca07576b..887f730062fe 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/README.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/README.mustache
@@ -4,54 +4,33 @@
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
-Dependency: [Cowboy](https://github.com/ninenines/cowboy)
+Dependencies: Erlang OTP/27 and rebar3. Also:
+- [Cowboy](https://hex.pm/packages/cowboy)
+- [Ranch](https://hex.pm/packages/ranch)
+- [Jesse](https://hex.pm/packages/jesse)
## Prerequisites
-TODO
-
## Getting started
-Use erlang-server with erlang.mk
-
- 1, Create an application by using erlang.mk
- $ mkdir http_server
- $ cd http_server
- $ wget https://erlang.mk/erlang.mk
- $ make -f erlang.mk bootstrap bootstrap-rel
- $ make run
-
- 2, Modify the Makefile in the http_server directory to the following to introduce the dependency library:
- PROJECT = http_server
- PROJECT_DESCRIPTION = New project
- PROJECT_VERSION = 0.1.0
-
- DEPS = cowboy jesse jsx
- dep_cowboy_commit = 2.5.0
- dep_jesse_commit = 1.5.2
- dep_jsx_commit = 2.9.0
- DEP_PLUGINS = cowboy jesse jsx
-
- PACKAGES += rfc3339
- pkg_rfc3339_name = rfc3339
- pkg_rfc3339_description = an erlang/elixir rfc3339 lib
- pkg_rfc3339_homepage = https://github.com/talentdeficit/rfc3339
- pkg_rfc3339_fetch = git
- pkg_rfc3339_repo = https://github.com/talentdeficit/rfc3339
- pkg_rfc3339_commit = master
-
- include erlang.mk
-
- 3, Generate erlang-server project using openapi-generator
+Use erlang-server with rebar3
+
+ 1, Create an application by using rebar3
+ $ rebar3 new app http_server
+
+ 2, Generate erlang-server project using openapi-generator
https://github.com/OpenAPITools/openapi-generator#2---getting-started
- 4, Copy erlang-server file to http_server project,Don't forget the 'priv' folder.
+ 3, Copy erlang-server file to http_server project, and don't forget the 'priv' folder.
- 5, Start in the http_server project:
+ 4, Start in the http_server project:
1, Introduce the following line in the http_server_app:start(_Type, _Args) function
- openapi_server:start(http_server, #{ip=>{127,0,0,1}, port=>8080, net_opts=>[]})
- 2, Compilation http_server project
- $ make
+ openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
+ 2, Compile your http_server project
+ $ rebar3 compile
3, Start erlang virtual machine
- $erl -pa ./deps/cowboy/ebin -pa ./deps/cowlib/ebin -pa ./deps/ranch/ebin -pa ./deps/jsx/ebin -pa ./deps/jesse/ebin -pa ./deps/rfc3339/ebin -pa ./ebin
+ $ rebar3 shell
4, Start project
application:ensure_all_started(http_server).
+
+To implement your own business logic, create a module called `http_server_logic` that implements the
+behaviour `openapi_logic_handler`. Refer to `openapi_logic_handler` documentation for details.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/api.mustache b/modules/openapi-generator/src/main/resources/erlang-server/api.mustache
index b97e4a2c5eca..ba8f13854f61 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/api.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/api.mustache
@@ -1,37 +1,60 @@
-module({{packageName}}_api).
+-moduledoc """
+This module offers an API for JSON schema validation, using `jesse` under the hood.
+
+If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
+and request and response can be validated using `populate_request/3`
+and `validate_response/4` respectively.
+
+For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
+```
+-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ValidatorState = openapi_api:prepare_validator(),
+ case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
+ {ok, Populated, Req1} ->
+ {Code, Headers, Body} = openapi_logic_handler:handle_request(
+ LogicHandler,
+ OperationID,
+ Req1,
+ maps:merge(State#state.context, Populated)
+ ),
+ _ = openapi_api:validate_response(
+ OperationID,
+ Code,
+ Body,
+ ValidatorState
+ ),
+ PreparedBody = prepare_body(Code, Body),
+ Response = {ok, {Code, Headers, PreparedBody}},
+ process_response(Response, Req1, State);
+ {error, Reason, Req1} ->
+ process_response({error, Reason}, Req1, State)
+ end.
+```
+""".
+
+-export([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
+-export([populate_request/3, validate_response/4]).
--export([request_params/1]).
--export([request_param_info/2]).
--export([populate_request/3]).
--export([validate_response/4]).
-%% exported to silence openapi complains
--export([get_value/3, validate_response_body/4]).
+-ignore_xref([populate_request/3, validate_response/4]).
+-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-type operation_id() :: atom().
-type request_param() :: atom().
-export_type([operation_id/0]).
--spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
-{{#apiInfo}}{{#apis}}
-{{#operations}}{{#operation}}
-request_params('{{operationId}}') ->
- [{{#allParams}}{{^isBodyParam}}
- '{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
- '{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
- ];
-{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
-request_params(_) ->
- error(unknown_operation).
+-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
-type rule() ::
- {type, 'binary'} |
- {type, 'integer'} |
- {type, 'float'} |
- {type, 'binary'} |
- {type, 'boolean'} |
- {type, 'date'} |
- {type, 'datetime'} |
+ {type, binary} |
+ {type, integer} |
+ {type, float} |
+ {type, boolean} |
+ {type, date} |
+ {type, datetime} |
{enum, [atom()]} |
{max, Max :: number()} |
{exclusive_max, Max :: number()} |
@@ -44,59 +67,99 @@ request_params(_) ->
required |
not_required.
--spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{
- source => qs_val | binding | header | body,
- rules => [rule()]
-}.
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator() -> jesse_state:state().
+prepare_validator() ->
+ prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
+
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator(binary()) -> jesse_state:state().
+prepare_validator(SchemaVer) ->
+ prepare_validator(get_openapi_path(), SchemaVer).
+
+-doc """
+Loads the JSON schema and the desired validation draft into a `t:jesse_state:state()`.
+""".
+-spec prepare_validator(file:name_all(), binary()) -> jesse_state:state().
+prepare_validator(OpenApiPath, SchemaVer) ->
+ {ok, FileContents} = file:read_file(OpenApiPath),
+ R = json:decode(FileContents),
+ jesse_state:new(R, [{default_schema_ver, SchemaVer}]).
+
+-doc """
+Automatically loads the entire body from the cowboy req
+and validates the JSON body against the schema.
+""".
+-spec populate_request(
+ OperationID :: operation_id(),
+ Req :: cowboy_req:req(),
+ ValidatorState :: jesse_state:state()) ->
+ {ok, Model :: #{}, Req :: cowboy_req:req()} |
+ {error, Reason :: any(), Req :: cowboy_req:req()}.
+populate_request(OperationID, Req, ValidatorState) ->
+ Params = request_params(OperationID),
+ populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
+
+-doc """
+Validates that the provided `Code` and `Body` comply with the `ValidatorState` schema
+for the `OperationID` operation.
+""".
+-spec validate_response(
+ OperationID :: operation_id(),
+ Code :: 200..599,
+ Body :: jesse:json_term(),
+ ValidatorState :: jesse_state:state()) ->
+ ok | {ok, term()} | [ok | {ok, term()}] | no_return().
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#responses}}validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
+ validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
+{{/responses}}
+{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
+ ok.
-{{#apiInfo}}{{#apis}}
-{{#operations}}{{#operation}}{{#allParams}}
-request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
+%%%
+-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}request_params('{{operationId}}') ->
+ [{{#allParams}}{{^isBodyParam}}
+ '{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
+ '{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
+ ];
+{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_params(_) ->
+ error(unknown_operation).
+
+-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
+ #{source => qs_val | binding | header | body, rules => [rule()]}.
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#allParams}}request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
#{
- source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
+ source => {{#isQueryParam}}qs_val{{/isQueryParam}}{{#isPathParam}}binding{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
rules => [{{#isString}}
- {type, 'binary'},{{/isString}}{{#isInteger}}
- {type, 'integer'},{{/isInteger}}{{#isLong}}
- {type, 'integer'},{{/isLong}}{{#isFloat}}
- {type, 'float'},{{/isFloat}}{{#isDouble}}
- {type, 'float'},{{/isDouble}}{{#isByteArray}}
- {type, 'binary'},{{/isByteArray}}{{#isBinary}}
- {type, 'binary'},{{/isBinary}}{{#isBoolean}}
- {type, 'boolean'},{{/isBoolean}}{{#isDate}}
- {type, 'date'},{{/isDate}}{{#isDateTime}}
- {type, 'datetime'},{{/isDateTime}}{{#isEnum}}
+ {type, binary},{{/isString}}{{#isInteger}}
+ {type, integer},{{/isInteger}}{{#isLong}}
+ {type, integer},{{/isLong}}{{#isFloat}}
+ {type, float},{{/isFloat}}{{#isDouble}}
+ {type, float},{{/isDouble}}{{#isByteArray}}
+ {type, binary},{{/isByteArray}}{{#isBinary}}
+ {type, binary},{{/isBinary}}{{#isBoolean}}
+ {type, boolean},{{/isBoolean}}{{#isDate}}
+ {type, date},{{/isDate}}{{#isDateTime}}
+ {type, datetime},{{/isDateTime}}{{#isEnum}}
{enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}}
- {max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}}
- {exclusive_max, {{exclusiveMaximum}} },{{/exclusiveMaximum}}{{#minimum}}
- {min, {{minimum}} },{{/minimum}}{{#exclusiveMinimum}}
- {exclusive_min, {{exclusiveMinimum}} },{{/exclusiveMinimum}}{{#maxLength}}
- {max_length, {{maxLength}} },{{/maxLength}}{{#minLength}}
- {min_length, {{minLength}} },{{/minLength}}{{#pattern}}
- {pattern, "{{{pattern}}}" },{{/pattern}}{{#isBodyParam}}
+ {max, {{maximum}}},{{/maximum}}{{#exclusiveMaximum}}
+ {exclusive_max, {{exclusiveMaximum}}},{{/exclusiveMaximum}}{{#minimum}}
+ {min, {{minimum}}},{{/minimum}}{{#exclusiveMinimum}}
+ {exclusive_min, {{exclusiveMinimum}}},{{/exclusiveMinimum}}{{#maxLength}}
+ {max_length, {{maxLength}}},{{/maxLength}}{{#minLength}}
+ {min_length, {{minLength}}},{{/minLength}}{{#pattern}}
+ {pattern, "{{{pattern}}}"},{{/pattern}}{{#isBodyParam}}
schema,{{/isBodyParam}}{{#required}}
required{{/required}}{{^required}}
not_required{{/required}}
]
};
-{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
-request_param_info(OperationID, Name) ->
+{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_param_info(OperationID, Name) ->
error({unknown_param, OperationID, Name}).
--spec populate_request(
- OperationID :: operation_id(),
- Req :: cowboy_req:req(),
- ValidatorState :: jesse_state:state()
-) ->
- {ok, Model :: #{}, Req :: cowboy_req:req()} |
- {error, Reason :: any(), Req :: cowboy_req:req()}.
-
-populate_request(OperationID, Req, ValidatorState) ->
- Params = request_params(OperationID),
- populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
-
populate_request_params(_, [], Req, _, Model) ->
{ok, Model, Req};
-
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
{ok, K, V, Req} ->
@@ -118,24 +181,9 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
end
end.
--spec validate_response(
- OperationID :: operation_id(),
- Code :: 200..599,
- Body :: jesse:json_term(),
- ValidatorState :: jesse_state:state()
-) -> ok | no_return().
-{{#apiInfo}}{{#apis}}
-{{#operations}}{{#operation}}
-{{#responses}}
-validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
- validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
-{{/responses}}
-{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
+-include_lib("kernel/include/logger.hrl").
-validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
- ok.
-
-validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
+validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, ReturnBaseType, Item, ValidatorState)
|| Item <- Body];
@@ -143,45 +191,37 @@ validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
validate(schema, ReturnBaseType, Body, ValidatorState).
-%%%
validate(Rule = required, Name, Value, _ValidatorState) ->
case Value of
undefined -> validation_error(Rule, Name);
_ -> ok
end;
-
validate(not_required, _Name, _Value, _ValidatorState) ->
ok;
-
validate(_, _Name, undefined, _ValidatorState) ->
ok;
-
-validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
try
- {ok, {{packageName}}_utils:to_int(Value)}
+ {ok, to_int(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
try
- {ok, {{packageName}}_utils:to_float(Value)}
+ {ok, to_float(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
-validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
+validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
{ok, Value};
-
-validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
V = binary_to_lower(Value),
try
case binary_to_existing_atom(V, utf8) of
@@ -192,19 +232,16 @@ validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
try
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
@@ -216,52 +253,44 @@ validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
-
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
case Value =< Max of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
case Value > ExclusiveMax of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
case Value >= Min of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
case Value =< ExclusiveMin of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
case size(Value) =< MaxLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
case size(Value) >= MinLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
{ok, MP} = re:compile(Pattern),
case re:run(Value, MP) of
{match, _} -> ok;
_ -> validation_error(Rule, Name)
end;
-
validate(Rule = schema, Name, Value, ValidatorState) ->
- Definition = list_to_binary("#/components/schemas/" ++ {{packageName}}_utils:to_list(Name)),
+ Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
@@ -281,18 +310,15 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
},
validation_error(Rule, Name, Info)
end;
-
validate(Rule, Name, _Value, _ValidatorState) ->
- error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]),
+ ?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
error({unknown_validation_rule, Rule}).
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
-
validation_error(ViolatedRule, Name) ->
validation_error(ViolatedRule, Name, #{}).
--spec validation_error(Rule :: any(), Name :: any(), Info :: #{}) -> no_return().
-
+-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
validation_error(ViolatedRule, Name, Info) ->
throw({wrong_param, Name, ViolatedRule, Info}).
@@ -307,31 +333,26 @@ get_value(body, _Name, Req0) ->
Value ->
{Value, Req}
end;
-
get_value(qs_val, Name, Req) ->
QS = cowboy_req:parse_qs(Req),
- Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_qs(Name), QS),
+ Value = get_opt(to_qs(Name), QS),
{Value, Req};
-
get_value(header, Name, Req) ->
Headers = cowboy_req:headers(Req),
- Value = maps:get({{packageName}}_utils:to_header(Name), Headers, undefined),
+ Value = maps:get(to_header(Name), Headers, undefined),
{Value, Req};
-
get_value(binding, Name, Req) ->
- Value = cowboy_req:binding({{packageName}}_utils:to_binding(Name), Req),
+ Value = cowboy_req:binding(to_binding(Name), Req),
{Value, Req}.
+prepare_body(<<>>) ->
+ <<>>;
prepare_body(Body) ->
- case Body of
- <<"">> -> <<"">>;
- _ ->
- try
- jsx:decode(Body, [return_maps])
- catch
- error:_ ->
- {error, {invalid_body, not_json, Body}}
- end
+ try
+ json:decode(Body)
+ catch
+ error:_ ->
+ {error, {invalid_body, not_json, Body}}
end.
validate_with_schema(Body, Definition, ValidatorState) ->
@@ -359,5 +380,84 @@ prepare_param(Rules, Name, Value, ValidatorState) ->
{error, Reason}
end.
+-spec to_binary(iodata() | atom() | number()) -> binary().
+to_binary(V) when is_binary(V) -> V;
+to_binary(V) when is_list(V) -> iolist_to_binary(V);
+to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
+to_binary(V) when is_integer(V) -> integer_to_binary(V);
+to_binary(V) when is_float(V) -> float_to_binary(V).
+
+-spec to_list(iodata() | atom() | number()) -> binary().
+to_list(V) when is_list(V) -> V;
+to_list(V) when is_binary(V) -> binary_to_list(V);
+to_list(V) when is_atom(V) -> atom_to_list(V);
+to_list(V) when is_integer(V) -> integer_to_list(V);
+to_list(V) when is_float(V) -> float_to_list(V).
+
+-spec to_float(iodata()) -> float().
+to_float(V) ->
+ binary_to_float(iolist_to_binary([V])).
+
+-spec to_int(integer() | binary() | list()) -> integer().
+to_int(Data) when is_integer(Data) ->
+ Data;
+to_int(Data) when is_binary(Data) ->
+ binary_to_integer(Data);
+to_int(Data) when is_list(Data) ->
+ list_to_integer(Data).
+
+-spec to_header(iodata() | atom() | number()) -> binary().
+to_header(Name) ->
+ to_binary(string:lowercase(to_binary(Name))).
+
binary_to_lower(V) when is_binary(V) ->
- list_to_binary(string:to_lower({{packageName}}_utils:to_list(V))).
+ string:lowercase(V).
+
+-spec to_qs(iodata() | atom() | number()) -> binary().
+to_qs(Name) ->
+ to_binary(Name).
+
+-spec to_binding(iodata() | atom() | number()) -> atom().
+to_binding(Name) ->
+ Prepared = to_binary(Name),
+ binary_to_existing_atom(Prepared, utf8).
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} -> Value;
+ false -> Default
+ end.
+
+get_openapi_path() ->
+ {ok, AppName} = application:get_application(?MODULE),
+ filename:join(priv_dir(AppName), "{{{openAPISpecName}}}.json").
+
+-include_lib("kernel/include/file.hrl").
+
+-spec priv_dir(Application :: atom()) -> file:name_all().
+priv_dir(AppName) ->
+ case code:priv_dir(AppName) of
+ Value when is_list(Value) ->
+ Value ++ "/";
+ _Error ->
+ select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
+ end.
+
+select_priv_dir(Paths) ->
+ case lists:dropwhile(fun test_priv_dir/1, Paths) of
+ [Path | _] -> Path;
+ _ -> exit(no_priv_dir)
+ end.
+
+test_priv_dir(Path) ->
+ case file:read_file_info(Path) of
+ {ok, #file_info{type = directory}} ->
+ false;
+ _ ->
+ true
+ end.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/app.src.mustache b/modules/openapi-generator/src/main/resources/erlang-server/app.src.mustache
index a90c4e7cae77..a7ab1dbc1492 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/app.src.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/app.src.mustache
@@ -1,19 +1,11 @@
-{application, {{packageName}}, [
- {description, {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
- {vsn, "{{apiVersion}}"},
- {registered, []},
- {applications, [
- kernel,
- stdlib,
- ssl,
- inets,
- jsx,
- jesse,
- cowboy
- ]},
- {env, [
- ]},
- {modules, []},
- {licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
- {links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}
-]}.
+{application,
+ {{packageName}},
+ [{description,
+ {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
+ {vsn, "{{apiVersion}}"},
+ {registered, []},
+ {applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
+ {env, []},
+ {modules, []},
+ {licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
+ {links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}]}.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/auth.mustache b/modules/openapi-generator/src/main/resources/erlang-server/auth.mustache
index 3159e352a9c5..060cb0269bd2 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/auth.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/auth.mustache
@@ -2,51 +2,44 @@
-export([authorize_api_key/5]).
--spec authorize_api_key(
- LogicHandler :: atom(),
- OperationID :: {{packageName}}_api:operation_id(),
- From :: header | qs_val,
- KeyParam :: iodata() | atom(),
- Req ::cowboy_req:req()
-)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
- {false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
-
-authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
+-spec authorize_api_key({{packageName}}_logic_handler:api_key_callback(),
+ {{packageName}}_api:operation_id(),
+ header | qs_val,
+ iodata() | atom(),
+ cowboy_req:req()) ->
+ {true, {{packageName}}_logic_handler:context(), cowboy_req:req()} |
+ {false, binary(), cowboy_req:req()}.
+authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
case ApiKey of
undefined ->
- AuthHeader = <<"">>,
+ AuthHeader = <<>>,
{false, AuthHeader, Req};
_ ->
- Result = {{packageName}}_logic_handler:authorize_api_key(
- LogicHandler,
- OperationID,
- ApiKey
- ),
- case Result of
- {{#authMethods}}
- {{#isApiKey}}
- {true, Context} ->
+ case Handler(OperationID, ApiKey) of
+ {true, Context} ->
{true, Context, Req};
- {{/isApiKey}}
- {{/authMethods}}
- false ->
- AuthHeader = <<"">>,
+ {false, AuthHeader} ->
{false, AuthHeader, Req}
end
end.
get_api_key(header, KeyParam, Req) ->
Headers = cowboy_req:headers(Req),
- {
- maps:get(
- {{packageName}}_utils:to_header(KeyParam),
- Headers,
- undefined
- ),
- Req
- };
-
+ {maps:get(KeyParam, Headers, undefined), Req};
get_api_key(qs_val, KeyParam, Req) ->
QS = cowboy_req:parse_qs(Req),
- { {{packageName}}_utils:get_opt(KeyParam, QS), Req}.
+ {get_opt(KeyParam, QS), Req}.
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} ->
+ Value;
+ false ->
+ Default
+ end.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server/default_logic_handler.mustache
deleted file mode 100644
index 1b39ed1d75a9..000000000000
--- a/modules/openapi-generator/src/main/resources/erlang-server/default_logic_handler.mustache
+++ /dev/null
@@ -1,32 +0,0 @@
--module({{packageName}}_default_logic_handler).
-
--behaviour({{packageName}}_logic_handler).
-
--export([handle_request/3]).
-{{#authMethods}}
- {{#isApiKey}}
--export([authorize_api_key/2]).
- {{/isApiKey}}
-{{/authMethods}}
-
-{{#authMethods}}
- {{#isApiKey}}
--spec authorize_api_key(OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) -> {true, #{}}.
-
-authorize_api_key(_, _) -> {true, #{}}.
- {{/isApiKey}}
-{{/authMethods}}
-
--spec handle_request(
- OperationID :: {{packageName}}_api:operation_id(),
- Req :: cowboy_req:req(),
- Context :: #{}
-) ->
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: jsx:json_term()}.
-
-handle_request(OperationID, Req, Context) ->
- error_logger:error_msg(
- "Got not implemented request to process: ~p~n",
- [{OperationID, Req, Context}]
- ),
- {501, #{}, #{}}.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server/handler.mustache
index 70756b2549ff..833c364c9377 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/handler.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/handler.mustache
@@ -1,252 +1,129 @@
%% basic handler
-module({{classname}}).
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
%% Cowboy REST callbacks
--export([allowed_methods/2]).
-export([init/2]).
--export([allow_missing_post/2]).
+-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
--export([known_content_type/2]).
--export([malformed_request/2]).
-export([valid_content_headers/2]).
--export([valid_entity_length/2]).
-
-%% Handlers
--export([handle_request_json/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
--record(state, {
- operation_id :: {{packageName}}_api:operation_id(),
- logic_handler :: atom(),
- validator_state :: jesse_state:state(),
- context=#{} :: #{}
-}).
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
--type state() :: state().
+-record(state,
+ {operation_id :: {{packageName}}_api:operation_id(),
+ accept_callback :: {{packageName}}_logic_handler:accept_callback(),
+ provide_callback :: {{packageName}}_logic_handler:provide_callback(),
+ api_key_handler :: {{packageName}}_logic_handler:api_key_callback(),
+ context = #{} :: {{packageName}}_logic_handler:context()}).
--spec init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) ->
- {cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
+-type state() :: #state{}.
-init(Req, {Operations, LogicHandler, ValidatorMod}) ->
+-spec init(cowboy_req:req(), {{packageName}}_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
-
- ValidatorState = ValidatorMod:get_validator_state(),
-
- error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
-
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- },
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
{cowboy_rest, Req, State}.
--spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
-
-{{#operations}}{{#operation}}
-allowed_methods(
- Req,
- State = #state{
- operation_id = '{{operationId}}'
- }
-) ->
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationId}}'} = State) ->
{[<<"{{httpMethod}}">>], Req, State};
-{{/operation}}{{/operations}}
-allowed_methods(Req, State) ->
+{{/operation}}{{/operations}}allowed_methods(Req, State) ->
{[], Req, State}.
--spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: true | {false, AuthHeader :: iodata()},
- Req :: cowboy_req:req(),
- State :: state()
- }.
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
{{#operations}}
{{#operation}}
-{{#authMethods}}
-is_authorized(
- Req0,
- State = #state{
- operation_id = '{{operationId}}' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- {{#isApiKey}}
- From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}},
- Result = {{packageName}}_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "{{keyParamName}}",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
- end;
- {{/isApiKey}}
- {{#isOAuth}}
- From = header,
- Result = {{packageName}}_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+{{#authMethods.size}}
+is_authorized(Req0,
+ #state{operation_id = '{{operationId}}' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case {{packageName}}_auth:authorize_api_key(Handler, OperationID, {{#isApiKey.isKeyInQuery}}qs_val, {{/isApiKey.isKeyInQuery}}{{^isApiKey.isKeyInQuery}}header, {{/isApiKey.isKeyInQuery}}{{#isApiKey}}"{{keyParamName}}", {{/isApiKey}}{{^isApiKey}}"authorization", {{/isApiKey}}Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
- {{/isOAuth}}
-{{/authMethods}}
+{{/authMethods.size}}
{{/operation}}
{{/operations}}
-{{^authMethods}}
is_authorized(Req, State) ->
{true, Req, State}.
-{{/authMethods}}
-{{#authMethods}}
-is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-{{/authMethods}}
-
--spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), AcceptResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
-content_types_accepted(Req, State) ->
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+{{#operations}}{{#operation}}content_types_accepted(Req, #state{operation_id = '{{operationId}}'} = State) ->
+ {{^consumes.size}}
+ {[], Req, State};
+ {{/consumes.size}}
+ {{#consumes.size}}
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
+ {{#consumes}}
+ {<<"{{mediaType}}">>, handle_type_accepted}{{^-last}}{{#consumes.size}},{{/consumes.size}}{{/-last}}
+ {{/consumes}}
+ ], Req, State};
+ {{/consumes.size}}
+{{/operation}}{{/operations}}content_types_accepted(Req, State) ->
+ {[], Req, State}.
--spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
-{{#operations}}{{#operation}}
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = '{{operationId}}'
- }
-) ->
- Headers = [{{#headerParams}}"{{baseName}}"{{^-last}},{{/-last}}{{/headerParams}}],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-{{/operation}}{{/operations}}
-valid_content_headers(Req, State) ->
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+{{#operations}}{{#operation}}valid_content_headers(Req, #state{operation_id = '{{operationId}}'} = State) ->
+ {true, Req, State};
+{{/operation}}{{/operations}}valid_content_headers(Req, State) ->
{false, Req, State}.
--spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), ProvideResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
-
-content_types_provided(Req, State) ->
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+{{#operations}}{{#operation}}content_types_provided(Req, #state{operation_id = '{{operationId}}'} = State) ->
+ {{^produces.size}}
+ {[], Req, State};
+ {{/produces.size}}
+ {{#produces.size}}
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-malformed_request(Req, State) ->
- {false, Req, State}.
-
--spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-allow_missing_post(Req, State) ->
- {false, Req, State}.
-
--spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
- processed_response().
+ {{#produces}}
+ {<<"{{mediaType}}">>, handle_type_provided}{{^-last}}{{#produces.size}},{{/produces.size}}{{/-last}}
+ {{/produces}}
+ ], Req, State};
+ {{/produces.size}}
+{{/operation}}{{/operations}}content_types_provided(Req, State) ->
+ {[], Req, State}.
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
- handle_request_json(Req, State).
-
--spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-known_content_type(Req, State) ->
- {true, Req, State}.
-
--spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-valid_entity_length(Req, State) ->
- %% @TODO check the length
- {true, Req, State}.
-
-%%%%
--type result_ok() :: {
- ok,
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
-}.
-
--type result_error() :: {error, Reason :: any()}.
-
--type processed_response() :: {stop, cowboy_req:req(), state()}.
-
--spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
- processed_response().
-
-process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
- case Response of
- {ok, {Code, Headers, Body}} ->
- Req = cowboy_req:reply(Code, Headers, Body, Req0),
- {stop, Req, State};
- {error, Message} ->
- error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
-
- Req = cowboy_req:reply(400, Req0),
- {stop, Req, State}
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
end.
--spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
-
-handle_request_json(
- Req0,
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- }
-) ->
- case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of
- {ok, Populated, Req1} ->
- {Code, Headers, Body} = {{packageName}}_logic_handler:handle_request(
- LogicHandler,
- OperationID,
- Req1,
- maps:merge(State#state.context, Populated)
- ),
- _ = {{packageName}}_api:validate_response(
- OperationID,
- Code,
- Body,
- ValidatorState
- ),
- PreparedBody = prepare_body(Code, Body),
- Response = {ok, {Code, Headers, PreparedBody}},
- process_response(Response, Req1, State);
- {error, Reason, Req1} ->
- process_response({error, Reason}, Req1, State)
- end.
-
-validate_headers(_, Req) -> {true, Req}.
-
-prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(_Code, Body) ->
- jsx:encode(Body).
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler({{operations.pathPrefix}}, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), {{packageName}}_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler({{operations.pathPrefix}}, OperationID, Req, State#state.context).
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/logic_handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server/logic_handler.mustache
index eb0688e682ce..13fd39434907 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/logic_handler.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/logic_handler.mustache
@@ -1,60 +1,56 @@
-module({{packageName}}_logic_handler).
--export([handle_request/4]).
-{{#authMethods}}
-{{#isApiKey}}
-{{#-first}}
--export([authorize_api_key/3]).
-{{/-first}}
-{{/isApiKey}}
-{{/authMethods}}
-{{^authMethods}}
--export([authorize_api_key/3]).
-{{/authMethods}}
+-include_lib("kernel/include/logger.hrl").
+
+-type api_key_callback() ::
+ fun(({{packageName}}_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
+-type accept_callback() ::
+ fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}).
+-type provide_callback() ::
+ fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}).
-type context() :: #{binary() => any()}.
--type handler_response() ::{
- Status :: cowboy:http_status(),
- Headers :: cowboy:http_headers(),
- Body :: jsx:json_term()}.
-
--export_type([handler_response/0]).
-
-{{#authMethods}}
- {{#isApiKey}}
--callback authorize_api_key(
- OperationID :: {{packageName}}_api:operation_id(),
- ApiKey :: binary()
-) ->
- Result :: boolean() | {boolean(), context()}.
- {{/isApiKey}}
-{{/authMethods}}
-
-
--callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) ->
- handler_response().
-
--spec handle_request(
- Handler :: atom(),
- OperationID :: {{packageName}}_api:operation_id(),
- Request :: cowboy_req:req(),
- Context :: context()
-) ->
- handler_response().
-
-handle_request(Handler, OperationID, Req, Context) ->
- Handler:handle_request(OperationID, Req, Context).
-
-{{#authMethods}}
- {{#isApiKey}}
--spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
- Result :: false | {true, context()}.
-authorize_api_key(Handler, OperationID, ApiKey) ->
- Handler:authorize_api_key(OperationID, ApiKey).
- {{/isApiKey}}
-{{/authMethods}}
-{{^authMethods}}
--spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
- Result :: false.
-authorize_api_key(_Handler, _OperationID, _ApiKey) ->
- false.
-{{/authMethods}}
+
+-export_type([context/0, api_key_callback/0, accept_callback/0, provide_callback/0]).
+
+-optional_callbacks([api_key_callback/2]).
+
+-callback api_key_callback({{packageName}}_api:operation_id(), binary()) ->
+ {true, context()} | {false, iodata()}.
+
+-callback accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+
+-callback provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+
+-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
+-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
+
+-spec api_key_callback({{packageName}}_api:operation_id(), binary()) -> {true, #{}}.
+api_key_callback(OperationID, ApiKey) ->
+ ?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
+ operation_id => OperationID,
+ api_key => ApiKey}),
+ {true, #{}}.
+
+-spec accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {501, #{}, #{}}.
+
+-spec provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+provide_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {<<>>, Req, Context}.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/rebar.config.mustache b/modules/openapi-generator/src/main/resources/erlang-server/rebar.config.mustache
index 743b108f384a..50cd482ca39b 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/rebar.config.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/rebar.config.mustache
@@ -1,6 +1,15 @@
+{minimum_otp_vsn, "27"}.
+
{deps, [
- {cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
- {rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
- {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
- {jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
+ {cowboy, "2.12.0"},
+ {ranch, "2.1.0"},
+ {jesse, "1.8.1"}
]}.
+
+{dialyzer,
+ [{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
+ {warnings, [missing_return, unknown]}
+]}.
+
+{xref_checks,
+ [undefined_function_calls, deprecated_function_calls, deprecated_functions]}.
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/router.mustache b/modules/openapi-generator/src/main/resources/erlang-server/router.mustache
index e2efc2206bc1..2bc70bdac73c 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/router.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/router.mustache
@@ -1,57 +1,36 @@
-module({{packageName}}_router).
--export([get_paths/1, get_validator_state/0]).
+-export([get_paths/1]).
--type operations() :: #{
- Method :: binary() => {{packageName}}_api:operation_id()
-}.
-
--type init_opts() :: {
- Operations :: operations(),
- LogicHandler :: atom(),
- ValidatorMod :: module()
-}.
+-type method() :: binary().
+-type operations() :: #{method() => {{packageName}}_api:operation_id()}.
+-type init_opts() :: {operations(), module()}.
-export_type([init_opts/0]).
--spec get_paths(LogicHandler :: atom()) -> [{'_',[{
- Path :: string(),
- Handler :: atom(),
- InitOpts :: init_opts()
-}]}].
-
+-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
get_paths(LogicHandler) ->
- ValidatorState = prepare_validator(),
PreparedPaths = maps:fold(
- fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
- [{Path, Handler, Operations} | Acc]
- end,
- [],
- group_paths()
- ),
- [
- {'_',
- [{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
- }
- ].
+ fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
+ [{Path, Handler, Operations} | Acc]
+ end, [], group_paths()
+ ),
+ [{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
group_paths() ->
maps:fold(
- fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
- case maps:find(Path, Acc) of
- {ok, PathInfo0 = #{operations := Operations0}} ->
- Operations = Operations0#{Method => OperationID},
- PathInfo = PathInfo0#{operations => Operations},
- Acc#{Path => PathInfo};
- error ->
- Operations = #{Method => OperationID},
- PathInfo = #{handler => Handler, operations => Operations},
- Acc#{Path => PathInfo}
- end
- end,
- #{},
- get_operations()
- ).
+ fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
+ case maps:find(Path, Acc) of
+ {ok, PathInfo0 = #{operations := Operations0}} ->
+ Operations = Operations0#{Method => OperationID},
+ PathInfo = PathInfo0#{operations => Operations},
+ Acc#{Path => PathInfo};
+ error ->
+ Operations = #{Method => OperationID},
+ PathInfo = #{handler => Handler, operations => Operations},
+ Acc#{Path => PathInfo}
+ end
+ end, #{}, get_operations()).
get_operations() ->
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
@@ -61,18 +40,3 @@ get_operations() ->
handler => '{{classname}}'
}{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}
}.
-
-get_validator_state() ->
- persistent_term:get({?MODULE, validator_state}).
-
-
-prepare_validator() ->
- R = jsx:decode(element(2, file:read_file(get_openapi_path()))),
- JesseState = jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]),
- persistent_term:put({?MODULE, validator_state}, JesseState),
- ?MODULE.
-
-
-get_openapi_path() ->
- {ok, AppName} = application:get_application(?MODULE),
- filename:join({{packageName}}_utils:priv_dir(AppName), "{{{openAPISpecName}}}.json").
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/server.mustache b/modules/openapi-generator/src/main/resources/erlang-server/server.mustache
index cca0af57a84c..43ed54e5d230 100644
--- a/modules/openapi-generator/src/main/resources/erlang-server/server.mustache
+++ b/modules/openapi-generator/src/main/resources/erlang-server/server.mustache
@@ -1,26 +1,21 @@
-module({{packageName}}_server).
-
--define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler).
+-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_logic_handler).
-export([start/2]).
-
--spec start( ID :: any(), #{
- ip => inet:ip_address(),
- port => inet:port_number(),
- logic_handler => module(),
- net_opts => []
-}) -> {ok, pid()} | {error, any()}.
-
-start(ID, #{
- ip := IP ,
- port := Port,
- net_opts := NetOpts
-} = Params) ->
- {Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
+-ignore_xref([start/2]).
+
+-spec start(term(), #{transport => tcp | ssl,
+ transport_opts => ranch:opts(),
+ protocol_opts => cowboy:opts(),
+ logic_handler => module()}) ->
+ {ok, pid()} | {error, any()}.
+start(ID, Params) ->
+ Transport = maps:get(transport, Params, tcp),
+ TransportOpts = maps:get(transport_opts, Params, #{}),
+ ProtocolOpts = maps:get(procotol_opts, Params, #{}),
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
- ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
- CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
+ CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
case Transport of
ssl ->
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
@@ -28,33 +23,17 @@ start(ID, #{
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
end.
-get_socket_transport(IP, Port, Options) ->
- Opts = [
- {ip, IP},
- {port, Port}
- ],
- case {{packageName}}_utils:get_opt(ssl, Options) of
- SslOpts = [_|_] ->
- {ssl, Opts ++ SslOpts};
- undefined ->
- {tcp, Opts}
- end.
-
get_cowboy_config(LogicHandler, ExtraOpts) ->
- get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
-
-get_cowboy_config(_LogicHandler, [], Opts) ->
- Opts;
+ DefaultOpts = get_default_opts(LogicHandler),
+ maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
-get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
- NewEnv = case proplists:get_value(dispatch, Env) of
- undefined -> [get_default_dispatch(LogicHandler) | Env];
- _ -> Env
- end,
- get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts));
-
-get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) ->
- get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)).
+get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
+ maps:put(env, Env, AccIn);
+get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
+ Env = maps:merge(OldEnv, NewEnv),
+ maps:put(env, Env, AccIn);
+get_cowboy_config(Key, Value, AccIn) ->
+ maps:put(Key, Value, AccIn).
get_default_dispatch(LogicHandler) ->
Paths = {{packageName}}_router:get_paths(LogicHandler),
@@ -62,6 +41,3 @@ get_default_dispatch(LogicHandler) ->
get_default_opts(LogicHandler) ->
#{env => get_default_dispatch(LogicHandler)}.
-
-store_key(Key, Value, Opts) ->
- maps:put(Key, Value, Opts).
diff --git a/modules/openapi-generator/src/main/resources/erlang-server/utils.mustache b/modules/openapi-generator/src/main/resources/erlang-server/utils.mustache
deleted file mode 100644
index b6701add7fc2..000000000000
--- a/modules/openapi-generator/src/main/resources/erlang-server/utils.mustache
+++ /dev/null
@@ -1,173 +0,0 @@
--module({{packageName}}_utils).
-
--export([to_binary/1]).
--export([to_list/1]).
--export([to_float/1]).
--export([to_int/1]).
--export([to_lower/1]).
--export([to_upper/1]).
--export([set_resp_headers/2]).
--export([to_header/1]).
--export([to_qs/1]).
--export([to_binding/1]).
--export([get_opt/2]).
--export([get_opt/3]).
--export([priv_dir/0]).
--export([priv_dir/1]).
--export([priv_path/1]).
-
-
--spec to_binary(iodata() | atom() | number()) -> binary().
-
-to_binary(V) when is_binary(V) -> V;
-to_binary(V) when is_list(V) -> iolist_to_binary(V);
-to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
-to_binary(V) when is_integer(V) -> integer_to_binary(V);
-to_binary(V) when is_float(V) -> float_to_binary(V).
-
--spec to_list(iodata() | atom() | number()) -> string().
-
-to_list(V) when is_list(V) -> V;
-to_list(V) -> binary_to_list(to_binary(V)).
-
--spec to_float(iodata()) -> number().
-
-to_float(V) ->
- Data = iolist_to_binary([V]),
- case binary:split(Data, <<$.>>) of
- [Data] ->
- binary_to_integer(Data);
- [<<>>, _] ->
- binary_to_float(<<$0, Data/binary>>);
- _ ->
- binary_to_float(Data)
- end.
-
-%%
-
--spec to_int(integer() | binary() | list()) -> integer().
-
-to_int(Data) when is_integer(Data) ->
- Data;
-to_int(Data) when is_binary(Data) ->
- binary_to_integer(Data);
-to_int(Data) when is_list(Data) ->
- list_to_integer(Data).
-
--spec set_resp_headers([{binary(), iodata()}], cowboy_req:req()) -> cowboy_req:req().
-
-set_resp_headers([], Req) ->
- Req;
-set_resp_headers([{K, V} | T], Req0) ->
- Req = cowboy_req:set_resp_header(K, V, Req0),
- set_resp_headers(T, Req).
-
--spec to_header(iodata() | atom() | number()) -> binary().
-
-to_header(Name) ->
- Prepared = to_binary(Name),
- to_lower(Prepared).
-
--spec to_qs(iodata() | atom() | number()) -> binary().
-
-to_qs(Name) ->
- to_binary(Name).
-
--spec to_binding(iodata() | atom() | number()) -> atom().
-
-to_binding(Name) ->
- Prepared = to_binary(Name),
- binary_to_atom(Prepared, utf8).
-
--spec get_opt(any(), []) -> any().
-
-get_opt(Key, Opts) ->
- get_opt(Key, Opts, undefined).
-
--spec get_opt(any(), [], any()) -> any().
-
-get_opt(Key, Opts, Default) ->
- case lists:keyfind(Key, 1, Opts) of
- {_, Value} -> Value;
- false -> Default
- end.
-
--spec priv_dir() -> file:filename().
-
-priv_dir() ->
- {ok, AppName} = application:get_application(),
- priv_dir(AppName).
-
--spec priv_dir(Application :: atom()) -> file:filename().
-
-priv_dir(AppName) ->
- case code:priv_dir(AppName) of
- Value when is_list(Value) ->
- Value ++ "/";
- _Error ->
- select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
- end.
-
--spec priv_path(Relative :: file:filename()) -> file:filename().
-
-priv_path(Relative) ->
- filename:join(priv_dir(), Relative).
-
--include_lib("kernel/include/file.hrl").
-
-select_priv_dir(Paths) ->
- case lists:dropwhile(fun test_priv_dir/1, Paths) of
- [Path | _] -> Path;
- _ -> exit(no_priv_dir)
- end.
-
-test_priv_dir(Path) ->
- case file:read_file_info(Path) of
- {ok, #file_info{type = directory}} ->
- false;
- _ ->
- true
- end.
-
-
-%%
-
--spec to_lower(binary()) -> binary().
-
-to_lower(S) ->
- to_case(lower, S, <<>>).
-
--spec to_upper(binary()) -> binary().
-
-to_upper(S) ->
- to_case(upper, S, <<>>).
-
-to_case(_Case, <<>>, Acc) ->
- Acc;
-
-to_case(_Case, <>, _Acc) when C > 127 ->
- error(badarg);
-
-to_case(Case = lower, <>, Acc) ->
- to_case(Case, Rest, <>);
-
-to_case(Case = upper, <>, Acc) ->
- to_case(Case, Rest, <>).
-
-to_lower_char(C) when is_integer(C), $A =< C, C =< $Z ->
- C + 32;
-to_lower_char(C) when is_integer(C), 16#C0 =< C, C =< 16#D6 ->
- C + 32;
-to_lower_char(C) when is_integer(C), 16#D8 =< C, C =< 16#DE ->
- C + 32;
-to_lower_char(C) ->
- C.
-
-to_upper_char(C) when is_integer(C), $a =< C, C =< $z ->
- C - 32;
-to_upper_char(C) when is_integer(C), 16#E0 =< C, C =< 16#F6 ->
- C - 32;
-to_upper_char(C) when is_integer(C), 16#F8 =< C, C =< 16#FE ->
- C - 32;
-to_upper_char(C) ->
- C.
diff --git a/samples/server/echo_api/erlang-server/.openapi-generator-ignore b/samples/server/echo_api/erlang-server/.openapi-generator-ignore
new file mode 100644
index 000000000000..7484ee590a38
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/server/echo_api/erlang-server/.openapi-generator/FILES b/samples/server/echo_api/erlang-server/.openapi-generator/FILES
new file mode 100644
index 000000000000..3aa7956b7dab
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/.openapi-generator/FILES
@@ -0,0 +1,15 @@
+README.md
+priv/openapi.json
+rebar.config
+src/openapi.app.src
+src/openapi_api.erl
+src/openapi_auth.erl
+src/openapi_auth_handler.erl
+src/openapi_body_handler.erl
+src/openapi_form_handler.erl
+src/openapi_header_handler.erl
+src/openapi_logic_handler.erl
+src/openapi_path_handler.erl
+src/openapi_query_handler.erl
+src/openapi_router.erl
+src/openapi_server.erl
diff --git a/samples/server/echo_api/erlang-server/.openapi-generator/VERSION b/samples/server/echo_api/erlang-server/.openapi-generator/VERSION
new file mode 100644
index 000000000000..17f2442ff3bc
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/.openapi-generator/VERSION
@@ -0,0 +1 @@
+7.9.0-SNAPSHOT
diff --git a/samples/server/echo_api/erlang-server/README.md b/samples/server/echo_api/erlang-server/README.md
new file mode 100644
index 000000000000..887f730062fe
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/README.md
@@ -0,0 +1,36 @@
+# OpenAPI server library for Erlang
+
+## Overview
+
+An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
+
+Dependencies: Erlang OTP/27 and rebar3. Also:
+- [Cowboy](https://hex.pm/packages/cowboy)
+- [Ranch](https://hex.pm/packages/ranch)
+- [Jesse](https://hex.pm/packages/jesse)
+
+## Prerequisites
+
+## Getting started
+Use erlang-server with rebar3
+
+ 1, Create an application by using rebar3
+ $ rebar3 new app http_server
+
+ 2, Generate erlang-server project using openapi-generator
+ https://github.com/OpenAPITools/openapi-generator#2---getting-started
+
+ 3, Copy erlang-server file to http_server project, and don't forget the 'priv' folder.
+
+ 4, Start in the http_server project:
+ 1, Introduce the following line in the http_server_app:start(_Type, _Args) function
+ openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
+ 2, Compile your http_server project
+ $ rebar3 compile
+ 3, Start erlang virtual machine
+ $ rebar3 shell
+ 4, Start project
+ application:ensure_all_started(http_server).
+
+To implement your own business logic, create a module called `http_server_logic` that implements the
+behaviour `openapi_logic_handler`. Refer to `openapi_logic_handler` documentation for details.
diff --git a/samples/server/echo_api/erlang-server/pom.xml b/samples/server/echo_api/erlang-server/pom.xml
new file mode 100644
index 000000000000..69d236d7cda2
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/pom.xml
@@ -0,0 +1,46 @@
+
+ 4.0.0
+ org.openapitools
+ ErlangServerEchoTests
+ pom
+ 1.0-SNAPSHOT
+ Erlang Echo Server
+
+
+
+ maven-dependency-plugin
+
+
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+
+ compile-test
+ integration-test
+
+ exec
+
+
+ rebar3
+
+ compile
+
+
+
+
+
+
+
+
diff --git a/samples/server/echo_api/erlang-server/priv/openapi.json b/samples/server/echo_api/erlang-server/priv/openapi.json
new file mode 100644
index 000000000000..be217ad12e5d
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/priv/openapi.json
@@ -0,0 +1,1282 @@
+{
+ "openapi" : "3.0.3",
+ "info" : {
+ "contact" : {
+ "email" : "team@openapitools.org"
+ },
+ "description" : "Echo Server API",
+ "license" : {
+ "name" : "Apache 2.0",
+ "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "title" : "Echo Server API",
+ "version" : "0.1.0"
+ },
+ "servers" : [ {
+ "url" : "http://localhost:3000/"
+ } ],
+ "paths" : {
+ "/path/string/{path_string}/integer/{path_integer}/{enum_nonref_string_path}/{enum_ref_string_path}" : {
+ "get" : {
+ "description" : "Test path parameter(s)",
+ "operationId" : "tests/path/string/{path_string}/integer/{path_integer}/{enum_nonref_string_path}/{enum_ref_string_path}",
+ "parameters" : [ {
+ "explode" : false,
+ "in" : "path",
+ "name" : "path_string",
+ "required" : true,
+ "schema" : {
+ "type" : "string"
+ },
+ "style" : "simple"
+ }, {
+ "explode" : false,
+ "in" : "path",
+ "name" : "path_integer",
+ "required" : true,
+ "schema" : {
+ "type" : "integer"
+ },
+ "style" : "simple"
+ }, {
+ "explode" : false,
+ "in" : "path",
+ "name" : "enum_nonref_string_path",
+ "required" : true,
+ "schema" : {
+ "enum" : [ "success", "failure", "unclassified" ],
+ "type" : "string"
+ },
+ "style" : "simple"
+ }, {
+ "explode" : false,
+ "in" : "path",
+ "name" : "enum_ref_string_path",
+ "required" : true,
+ "schema" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ },
+ "style" : "simple"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test path parameter(s)",
+ "tags" : [ "path" ]
+ }
+ },
+ "/form/integer/boolean/string" : {
+ "post" : {
+ "description" : "Test form parameter(s)",
+ "operationId" : "test/form/integer/boolean/string",
+ "requestBody" : {
+ "content" : {
+ "application/x-www-form-urlencoded" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/test_form_integer_boolean_string_request"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test form parameter(s)",
+ "tags" : [ "form" ]
+ }
+ },
+ "/form/oneof" : {
+ "post" : {
+ "description" : "Test form parameter(s) for oneOf schema",
+ "operationId" : "test/form/oneof",
+ "requestBody" : {
+ "content" : {
+ "application/x-www-form-urlencoded" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/test_form_oneof_request"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test form parameter(s) for oneOf schema",
+ "tags" : [ "form" ]
+ }
+ },
+ "/form/object/multipart" : {
+ "post" : {
+ "description" : "Test form parameter(s) for multipart schema",
+ "operationId" : "test/form/object/multipart",
+ "requestBody" : {
+ "content" : {
+ "multipart/form-data" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/test_form_object_multipart_request"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test form parameter(s) for multipart schema",
+ "tags" : [ "form" ]
+ }
+ },
+ "/header/integer/boolean/string/enums" : {
+ "get" : {
+ "description" : "Test header parameter(s)",
+ "operationId" : "test/header/integer/boolean/string/enums",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "header",
+ "name" : "integer_header",
+ "required" : false,
+ "schema" : {
+ "type" : "integer"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "header",
+ "name" : "boolean_header",
+ "required" : false,
+ "schema" : {
+ "type" : "boolean"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "header",
+ "name" : "string_header",
+ "required" : false,
+ "schema" : {
+ "type" : "string"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "header",
+ "name" : "enum_nonref_string_header",
+ "required" : false,
+ "schema" : {
+ "enum" : [ "success", "failure", "unclassified" ],
+ "type" : "string"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "header",
+ "name" : "enum_ref_string_header",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test header parameter(s)",
+ "tags" : [ "header" ]
+ }
+ },
+ "/query/enum_ref_string" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/enum_ref_string",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "enum_nonref_string_query",
+ "required" : false,
+ "schema" : {
+ "enum" : [ "success", "failure", "unclassified" ],
+ "type" : "string"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "query",
+ "name" : "enum_ref_string_query",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/datetime/date/string" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/datetime/date/string",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "datetime_query",
+ "required" : false,
+ "schema" : {
+ "format" : "date-time",
+ "type" : "string"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "query",
+ "name" : "date_query",
+ "required" : false,
+ "schema" : {
+ "format" : "date",
+ "type" : "string"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "query",
+ "name" : "string_query",
+ "required" : false,
+ "schema" : {
+ "type" : "string"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/integer/boolean/string" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/integer/boolean/string",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "integer_query",
+ "required" : false,
+ "schema" : {
+ "type" : "integer"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "query",
+ "name" : "boolean_query",
+ "required" : false,
+ "schema" : {
+ "type" : "boolean"
+ },
+ "style" : "form"
+ }, {
+ "explode" : true,
+ "in" : "query",
+ "name" : "string_query",
+ "required" : false,
+ "schema" : {
+ "type" : "string"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_form/explode_true/array_string" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_form/explode_true/array_string",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/test_query_style_form_explode_true_array_string_query_object_parameter"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_form/explode_false/array_integer" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_form/explode_false/array_integer",
+ "parameters" : [ {
+ "explode" : false,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "items" : {
+ "type" : "integer"
+ },
+ "type" : "array"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_form/explode_false/array_string" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_form/explode_false/array_string",
+ "parameters" : [ {
+ "explode" : false,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "items" : {
+ "type" : "string"
+ },
+ "type" : "array"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_form/explode_true/object" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_form/explode_true/object",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/Pet"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_form/explode_true/object/allOf" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_form/explode_true/object/allOf",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/DataQuery"
+ },
+ "style" : "form"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_deepObject/explode_true/object" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_deepObject/explode_true/object",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/Pet"
+ },
+ "style" : "deepObject"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/query/style_deepObject/explode_true/object/allOf" : {
+ "get" : {
+ "description" : "Test query parameter(s)",
+ "operationId" : "test/query/style_deepObject/explode_true/object/allOf",
+ "parameters" : [ {
+ "explode" : true,
+ "in" : "query",
+ "name" : "query_object",
+ "required" : false,
+ "schema" : {
+ "$ref" : "#/components/schemas/test_query_style_deepObject_explode_true_object_allOf_query_object_parameter"
+ },
+ "style" : "deepObject"
+ } ],
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test query parameter(s)",
+ "tags" : [ "query" ]
+ }
+ },
+ "/body/application/octetstream/binary" : {
+ "post" : {
+ "description" : "Test body parameter(s)",
+ "operationId" : "test/body/application/octetstream/binary",
+ "requestBody" : {
+ "content" : {
+ "application/octet-stream" : {
+ "schema" : {
+ "format" : "binary",
+ "type" : "string"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test body parameter(s)",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/Pet" : {
+ "post" : {
+ "description" : "Test body parameter(s)",
+ "operationId" : "test/echo/body/Pet",
+ "requestBody" : {
+ "$ref" : "#/components/requestBodies/Pet"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/Pet"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test body parameter(s)",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/allOf/Pet" : {
+ "post" : {
+ "description" : "Test body parameter(s)",
+ "operationId" : "test/echo/body/allOf/Pet",
+ "requestBody" : {
+ "$ref" : "#/components/requestBodies/AllOfPet"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/Pet"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test body parameter(s)",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/Pet/response_string" : {
+ "post" : {
+ "description" : "Test empty response body",
+ "operationId" : "test/echo/body/Pet/response_string",
+ "requestBody" : {
+ "$ref" : "#/components/requestBodies/Pet"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test empty response body",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/Tag/response_string" : {
+ "post" : {
+ "description" : "Test empty json (request body)",
+ "operationId" : "test/echo/body/Tag/response_string",
+ "requestBody" : {
+ "$ref" : "#/components/requestBodies/Tag"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test empty json (request body)",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/FreeFormObject/response_string" : {
+ "post" : {
+ "description" : "Test free form object",
+ "operationId" : "test/echo/body/FreeFormObject/response_string",
+ "requestBody" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "type" : "object"
+ }
+ }
+ },
+ "description" : "Free form object"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test free form object",
+ "tags" : [ "body" ]
+ }
+ },
+ "/echo/body/string_enum" : {
+ "post" : {
+ "description" : "Test string enum response body",
+ "operationId" : "test/echo/body/string_enum",
+ "requestBody" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ }
+ }
+ },
+ "description" : "String enum"
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test string enum response body",
+ "tags" : [ "body" ]
+ }
+ },
+ "/binary/gif" : {
+ "post" : {
+ "description" : "Test binary (gif) response body",
+ "operationId" : "test/binary/gif",
+ "responses" : {
+ "200" : {
+ "content" : {
+ "image/gif" : {
+ "schema" : {
+ "format" : "binary",
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test binary (gif) response body",
+ "tags" : [ "body" ]
+ }
+ },
+ "/body/application/octetstream/single_binary" : {
+ "post" : {
+ "description" : "Test single binary in multipart mime",
+ "operationId" : "test/body/multipart/formdata/single_binary",
+ "requestBody" : {
+ "content" : {
+ "multipart/form-data" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/test_body_multipart_formdata_single_binary_request"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test single binary in multipart mime",
+ "tags" : [ "body" ]
+ }
+ },
+ "/body/application/octetstream/array_of_binary" : {
+ "post" : {
+ "description" : "Test array of binary in multipart mime",
+ "operationId" : "test/body/multipart/formdata/array_of_binary",
+ "requestBody" : {
+ "content" : {
+ "multipart/form-data" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/test_body_multipart_formdata_array_of_binary_request"
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "summary" : "Test array of binary in multipart mime",
+ "tags" : [ "body" ]
+ }
+ },
+ "/auth/http/basic" : {
+ "post" : {
+ "description" : "To test HTTP basic authentication",
+ "operationId" : "test/auth/http/basic",
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "security" : [ {
+ "http_auth" : [ ]
+ } ],
+ "summary" : "To test HTTP basic authentication",
+ "tags" : [ "auth" ]
+ }
+ },
+ "/auth/http/bearer" : {
+ "post" : {
+ "description" : "To test HTTP bearer authentication",
+ "operationId" : "test/auth/http/bearer",
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "type" : "string"
+ }
+ }
+ },
+ "description" : "Successful operation"
+ }
+ },
+ "security" : [ {
+ "http_bearer_auth" : [ ]
+ } ],
+ "summary" : "To test HTTP bearer authentication",
+ "tags" : [ "auth" ]
+ }
+ }
+ },
+ "components" : {
+ "requestBodies" : {
+ "Pet" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/Pet"
+ }
+ }
+ },
+ "description" : "Pet object that needs to be added to the store"
+ },
+ "AllOfPet" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "allOf" : [ {
+ "$ref" : "#/components/schemas/Pet"
+ } ]
+ }
+ }
+ },
+ "description" : "Pet object that needs to be added to the store"
+ },
+ "Tag" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/Tag"
+ }
+ }
+ },
+ "description" : "Tag object"
+ }
+ },
+ "schemas" : {
+ "Category" : {
+ "example" : {
+ "name" : "Dogs",
+ "id" : 1
+ },
+ "properties" : {
+ "id" : {
+ "example" : 1,
+ "format" : "int64",
+ "type" : "integer"
+ },
+ "name" : {
+ "example" : "Dogs",
+ "type" : "string"
+ }
+ },
+ "type" : "object",
+ "xml" : {
+ "name" : "category"
+ }
+ },
+ "Tag" : {
+ "example" : {
+ "name" : "name",
+ "id" : 0
+ },
+ "properties" : {
+ "id" : {
+ "format" : "int64",
+ "type" : "integer"
+ },
+ "name" : {
+ "type" : "string"
+ }
+ },
+ "type" : "object",
+ "xml" : {
+ "name" : "tag"
+ }
+ },
+ "Pet" : {
+ "example" : {
+ "photoUrls" : [ "photoUrls", "photoUrls" ],
+ "name" : "doggie",
+ "id" : 10,
+ "category" : {
+ "name" : "Dogs",
+ "id" : 1
+ },
+ "tags" : [ {
+ "name" : "name",
+ "id" : 0
+ }, {
+ "name" : "name",
+ "id" : 0
+ } ],
+ "status" : "available"
+ },
+ "properties" : {
+ "id" : {
+ "example" : 10,
+ "format" : "int64",
+ "type" : "integer"
+ },
+ "name" : {
+ "example" : "doggie",
+ "type" : "string"
+ },
+ "category" : {
+ "$ref" : "#/components/schemas/Category"
+ },
+ "photoUrls" : {
+ "items" : {
+ "type" : "string",
+ "xml" : {
+ "name" : "photoUrl"
+ }
+ },
+ "type" : "array",
+ "xml" : {
+ "wrapped" : true
+ }
+ },
+ "tags" : {
+ "items" : {
+ "$ref" : "#/components/schemas/Tag"
+ },
+ "type" : "array",
+ "xml" : {
+ "wrapped" : true
+ }
+ },
+ "status" : {
+ "description" : "pet status in the store",
+ "enum" : [ "available", "pending", "sold" ],
+ "type" : "string"
+ }
+ },
+ "required" : [ "name", "photoUrls" ],
+ "type" : "object",
+ "xml" : {
+ "name" : "pet"
+ }
+ },
+ "StringEnumRef" : {
+ "enum" : [ "success", "failure", "unclassified" ],
+ "type" : "string"
+ },
+ "DefaultValue" : {
+ "description" : "to test the default value of properties",
+ "properties" : {
+ "array_string_enum_ref_default" : {
+ "default" : [ "success", "failure" ],
+ "items" : {
+ "$ref" : "#/components/schemas/StringEnumRef"
+ },
+ "type" : "array"
+ },
+ "array_string_enum_default" : {
+ "default" : [ "success", "failure" ],
+ "items" : {
+ "enum" : [ "success", "failure", "unclassified" ],
+ "type" : "string"
+ },
+ "type" : "array"
+ },
+ "array_string_default" : {
+ "default" : [ "failure", "skipped" ],
+ "items" : {
+ "type" : "string"
+ },
+ "type" : "array"
+ },
+ "array_integer_default" : {
+ "default" : [ 1, 3 ],
+ "items" : {
+ "type" : "integer"
+ },
+ "type" : "array"
+ },
+ "array_string" : {
+ "items" : {
+ "type" : "string"
+ },
+ "type" : "array"
+ },
+ "array_string_nullable" : {
+ "items" : {
+ "type" : "string"
+ },
+ "nullable" : true,
+ "type" : "array"
+ },
+ "array_string_extension_nullable" : {
+ "items" : {
+ "type" : "string"
+ },
+ "type" : "array",
+ "x-nullable" : true
+ },
+ "string_nullable" : {
+ "nullable" : true,
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ },
+ "Bird" : {
+ "properties" : {
+ "size" : {
+ "type" : "string"
+ },
+ "color" : {
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ },
+ "Query" : {
+ "properties" : {
+ "id" : {
+ "description" : "Query",
+ "format" : "int64",
+ "type" : "integer"
+ },
+ "outcomes" : {
+ "default" : [ "SUCCESS", "FAILURE" ],
+ "items" : {
+ "enum" : [ "SUCCESS", "FAILURE", "SKIPPED" ],
+ "type" : "string"
+ },
+ "type" : "array"
+ }
+ },
+ "type" : "object",
+ "x-parent" : true
+ },
+ "DataQuery" : {
+ "allOf" : [ {
+ "properties" : {
+ "suffix" : {
+ "description" : "test suffix",
+ "type" : "string"
+ },
+ "text" : {
+ "description" : "Some text containing white spaces",
+ "example" : "Some text",
+ "type" : "string"
+ },
+ "date" : {
+ "description" : "A date",
+ "format" : "date-time",
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ }, {
+ "$ref" : "#/components/schemas/Query"
+ } ]
+ },
+ "NumberPropertiesOnly" : {
+ "properties" : {
+ "number" : {
+ "type" : "number"
+ },
+ "float" : {
+ "format" : "float",
+ "type" : "number"
+ },
+ "double" : {
+ "format" : "double",
+ "maximum" : 50.2,
+ "minimum" : 0.8,
+ "type" : "number"
+ }
+ },
+ "type" : "object"
+ },
+ "test_form_integer_boolean_string_request" : {
+ "properties" : {
+ "integer_form" : {
+ "type" : "integer"
+ },
+ "boolean_form" : {
+ "type" : "boolean"
+ },
+ "string_form" : {
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ },
+ "test_form_oneof_request_oneOf" : {
+ "properties" : {
+ "form1" : {
+ "type" : "string"
+ },
+ "form2" : {
+ "type" : "integer"
+ }
+ },
+ "type" : "object"
+ },
+ "test_form_oneof_request_oneOf_1" : {
+ "properties" : {
+ "form3" : {
+ "type" : "string"
+ },
+ "form4" : {
+ "type" : "boolean"
+ }
+ },
+ "type" : "object"
+ },
+ "test_form_oneof_request" : {
+ "oneOf" : [ {
+ "$ref" : "#/components/schemas/test_form_oneof_request_oneOf"
+ }, {
+ "$ref" : "#/components/schemas/test_form_oneof_request_oneOf_1"
+ }, {
+ "$ref" : "#/components/schemas/Tag"
+ } ],
+ "type" : "object"
+ },
+ "test_form_object_multipart_request_marker" : {
+ "properties" : {
+ "name" : {
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ },
+ "test_form_object_multipart_request" : {
+ "properties" : {
+ "marker" : {
+ "$ref" : "#/components/schemas/test_form_object_multipart_request_marker"
+ }
+ },
+ "required" : [ "marker" ],
+ "type" : "object"
+ },
+ "test_query_style_form_explode_true_array_string_query_object_parameter" : {
+ "properties" : {
+ "values" : {
+ "items" : {
+ "type" : "string"
+ },
+ "type" : "array"
+ }
+ },
+ "type" : "object"
+ },
+ "test_query_style_deepObject_explode_true_object_allOf_query_object_parameter" : {
+ "allOf" : [ {
+ "$ref" : "#/components/schemas/Bird"
+ }, {
+ "$ref" : "#/components/schemas/Category"
+ } ]
+ },
+ "test_body_multipart_formdata_single_binary_request" : {
+ "properties" : {
+ "my-file" : {
+ "format" : "binary",
+ "type" : "string"
+ }
+ },
+ "type" : "object"
+ },
+ "test_body_multipart_formdata_array_of_binary_request" : {
+ "properties" : {
+ "files" : {
+ "items" : {
+ "format" : "binary",
+ "type" : "string"
+ },
+ "type" : "array"
+ }
+ },
+ "required" : [ "files" ],
+ "type" : "object"
+ }
+ },
+ "securitySchemes" : {
+ "http_auth" : {
+ "scheme" : "basic",
+ "type" : "http"
+ },
+ "http_bearer_auth" : {
+ "scheme" : "bearer",
+ "type" : "http"
+ }
+ }
+ }
+}
diff --git a/samples/server/echo_api/erlang-server/rebar.config b/samples/server/echo_api/erlang-server/rebar.config
new file mode 100644
index 000000000000..50cd482ca39b
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/rebar.config
@@ -0,0 +1,15 @@
+{minimum_otp_vsn, "27"}.
+
+{deps, [
+ {cowboy, "2.12.0"},
+ {ranch, "2.1.0"},
+ {jesse, "1.8.1"}
+]}.
+
+{dialyzer,
+ [{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
+ {warnings, [missing_return, unknown]}
+]}.
+
+{xref_checks,
+ [undefined_function_calls, deprecated_function_calls, deprecated_functions]}.
diff --git a/samples/server/echo_api/erlang-server/src/openapi.app.src b/samples/server/echo_api/erlang-server/src/openapi.app.src
new file mode 100644
index 000000000000..95507ce40e9c
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi.app.src
@@ -0,0 +1,11 @@
+{application,
+ openapi,
+ [{description,
+ "Echo Server API"},
+ {vsn, "1.0.0"},
+ {registered, []},
+ {applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
+ {env, []},
+ {modules, []},
+ {licenses, ["Apache 2.0"]},
+ {links, []}]}.
diff --git a/samples/server/echo_api/erlang-server/src/openapi_api.erl b/samples/server/echo_api/erlang-server/src/openapi_api.erl
new file mode 100644
index 000000000000..b796f2df4a36
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_api.erl
@@ -0,0 +1,942 @@
+-module(openapi_api).
+-moduledoc """
+This module offers an API for JSON schema validation, using `jesse` under the hood.
+
+If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
+and request and response can be validated using `populate_request/3`
+and `validate_response/4` respectively.
+
+For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
+```
+-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ValidatorState = openapi_api:prepare_validator(),
+ case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
+ {ok, Populated, Req1} ->
+ {Code, Headers, Body} = openapi_logic_handler:handle_request(
+ LogicHandler,
+ OperationID,
+ Req1,
+ maps:merge(State#state.context, Populated)
+ ),
+ _ = openapi_api:validate_response(
+ OperationID,
+ Code,
+ Body,
+ ValidatorState
+ ),
+ PreparedBody = prepare_body(Code, Body),
+ Response = {ok, {Code, Headers, PreparedBody}},
+ process_response(Response, Req1, State);
+ {error, Reason, Req1} ->
+ process_response({error, Reason}, Req1, State)
+ end.
+```
+""".
+
+-export([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
+-export([populate_request/3, validate_response/4]).
+
+-ignore_xref([populate_request/3, validate_response/4]).
+-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
+
+-type operation_id() :: atom().
+-type request_param() :: atom().
+
+-export_type([operation_id/0]).
+
+-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
+
+-type rule() ::
+ {type, binary} |
+ {type, integer} |
+ {type, float} |
+ {type, boolean} |
+ {type, date} |
+ {type, datetime} |
+ {enum, [atom()]} |
+ {max, Max :: number()} |
+ {exclusive_max, Max :: number()} |
+ {min, Min :: number()} |
+ {exclusive_min, Min :: number()} |
+ {max_length, MaxLength :: integer()} |
+ {min_length, MaxLength :: integer()} |
+ {pattern, Pattern :: string()} |
+ schema |
+ required |
+ not_required.
+
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator() -> jesse_state:state().
+prepare_validator() ->
+ prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
+
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator(binary()) -> jesse_state:state().
+prepare_validator(SchemaVer) ->
+ prepare_validator(get_openapi_path(), SchemaVer).
+
+-doc """
+Loads the JSON schema and the desired validation draft into a `t:jesse_state:state()`.
+""".
+-spec prepare_validator(file:name_all(), binary()) -> jesse_state:state().
+prepare_validator(OpenApiPath, SchemaVer) ->
+ {ok, FileContents} = file:read_file(OpenApiPath),
+ R = json:decode(FileContents),
+ jesse_state:new(R, [{default_schema_ver, SchemaVer}]).
+
+-doc """
+Automatically loads the entire body from the cowboy req
+and validates the JSON body against the schema.
+""".
+-spec populate_request(
+ OperationID :: operation_id(),
+ Req :: cowboy_req:req(),
+ ValidatorState :: jesse_state:state()) ->
+ {ok, Model :: #{}, Req :: cowboy_req:req()} |
+ {error, Reason :: any(), Req :: cowboy_req:req()}.
+populate_request(OperationID, Req, ValidatorState) ->
+ Params = request_params(OperationID),
+ populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
+
+-doc """
+Validates that the provided `Code` and `Body` comply with the `ValidatorState` schema
+for the `OperationID` operation.
+""".
+-spec validate_response(
+ OperationID :: operation_id(),
+ Code :: 200..599,
+ Body :: jesse:json_term(),
+ ValidatorState :: jesse_state:state()) ->
+ ok | {ok, term()} | [ok | {ok, term()}] | no_return().
+validate_response('TestAuthHttpBasic', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestAuthHttpBearer', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestBinaryGif', 200, Body, ValidatorState) ->
+ validate_response_body('file', 'file', Body, ValidatorState);
+validate_response('TestBodyApplicationOctetstreamBinary', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestBodyMultipartFormdataArrayOfBinary', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestBodyMultipartFormdataSingleBinary', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestEchoBodyAllOfPet', 200, Body, ValidatorState) ->
+ validate_response_body('Pet', 'Pet', Body, ValidatorState);
+validate_response('TestEchoBodyFreeFormObjectResponseString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestEchoBodyPet', 200, Body, ValidatorState) ->
+ validate_response_body('Pet', 'Pet', Body, ValidatorState);
+validate_response('TestEchoBodyPetResponseString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestEchoBodyStringEnum', 200, Body, ValidatorState) ->
+ validate_response_body('StringEnumRef', 'StringEnumRef', Body, ValidatorState);
+validate_response('TestEchoBodyTagResponseString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestFormIntegerBooleanString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestFormObjectMultipart', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestFormOneof', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestHeaderIntegerBooleanStringEnums', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestEnumRefString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryDatetimeDateString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryIntegerBooleanString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleDeepObjectExplodeTrueObject', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleDeepObjectExplodeTrueObjectAllOf', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleFormExplodeFalseArrayInteger', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleFormExplodeFalseArrayString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleFormExplodeTrueArrayString', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleFormExplodeTrueObject', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('TestQueryStyleFormExplodeTrueObjectAllOf', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
+ ok.
+
+%%%
+-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
+request_params('TestAuthHttpBasic') ->
+ [
+ ];
+request_params('TestAuthHttpBearer') ->
+ [
+ ];
+request_params('TestBinaryGif') ->
+ [
+ ];
+request_params('TestBodyApplicationOctetstreamBinary') ->
+ [
+ 'file'
+ ];
+request_params('TestBodyMultipartFormdataArrayOfBinary') ->
+ [
+ 'files'
+ ];
+request_params('TestBodyMultipartFormdataSingleBinary') ->
+ [
+ 'my-file'
+ ];
+request_params('TestEchoBodyAllOfPet') ->
+ [
+ 'Pet'
+ ];
+request_params('TestEchoBodyFreeFormObjectResponseString') ->
+ [
+ 'object'
+ ];
+request_params('TestEchoBodyPet') ->
+ [
+ 'Pet'
+ ];
+request_params('TestEchoBodyPetResponseString') ->
+ [
+ 'Pet'
+ ];
+request_params('TestEchoBodyStringEnum') ->
+ [
+ 'binary'
+ ];
+request_params('TestEchoBodyTagResponseString') ->
+ [
+ 'Tag'
+ ];
+request_params('TestFormIntegerBooleanString') ->
+ [
+ 'integer_form',
+ 'boolean_form',
+ 'string_form'
+ ];
+request_params('TestFormObjectMultipart') ->
+ [
+ 'marker'
+ ];
+request_params('TestFormOneof') ->
+ [
+ 'form1',
+ 'form2',
+ 'form3',
+ 'form4',
+ 'id',
+ 'name'
+ ];
+request_params('TestHeaderIntegerBooleanStringEnums') ->
+ [
+ 'integer_header',
+ 'boolean_header',
+ 'string_header',
+ 'enum_nonref_string_header',
+ 'enum_ref_string_header'
+ ];
+request_params('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}') ->
+ [
+ 'path_string',
+ 'path_integer',
+ 'enum_nonref_string_path',
+ 'enum_ref_string_path'
+ ];
+request_params('TestEnumRefString') ->
+ [
+ 'enum_nonref_string_query',
+ 'enum_ref_string_query'
+ ];
+request_params('TestQueryDatetimeDateString') ->
+ [
+ 'datetime_query',
+ 'date_query',
+ 'string_query'
+ ];
+request_params('TestQueryIntegerBooleanString') ->
+ [
+ 'integer_query',
+ 'boolean_query',
+ 'string_query'
+ ];
+request_params('TestQueryStyleDeepObjectExplodeTrueObject') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleDeepObjectExplodeTrueObjectAllOf') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleFormExplodeFalseArrayInteger') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleFormExplodeFalseArrayString') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleFormExplodeTrueArrayString') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleFormExplodeTrueObject') ->
+ [
+ 'query_object'
+ ];
+request_params('TestQueryStyleFormExplodeTrueObjectAllOf') ->
+ [
+ 'query_object'
+ ];
+request_params(_) ->
+ error(unknown_operation).
+
+-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
+ #{source => qs_val | binding | header | body, rules => [rule()]}.
+request_param_info('TestBodyApplicationOctetstreamBinary', 'file') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestBodyMultipartFormdataArrayOfBinary', 'files') ->
+ #{
+ source => body,
+ rules => [
+ required
+ ]
+ };
+request_param_info('TestBodyMultipartFormdataSingleBinary', 'my-file') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyAllOfPet', 'Pet') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyFreeFormObjectResponseString', 'object') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyPet', 'Pet') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyPetResponseString', 'Pet') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyStringEnum', 'binary') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestEchoBodyTagResponseString', 'Tag') ->
+ #{
+ source => body,
+ rules => [
+ schema,
+ not_required
+ ]
+ };
+request_param_info('TestFormIntegerBooleanString', 'integer_form') ->
+ #{
+ source => body,
+ rules => [
+ {type, integer},
+ not_required
+ ]
+ };
+request_param_info('TestFormIntegerBooleanString', 'boolean_form') ->
+ #{
+ source => body,
+ rules => [
+ {type, boolean},
+ not_required
+ ]
+ };
+request_param_info('TestFormIntegerBooleanString', 'string_form') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestFormObjectMultipart', 'marker') ->
+ #{
+ source => body,
+ rules => [
+ required
+ ]
+ };
+request_param_info('TestFormOneof', 'form1') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestFormOneof', 'form2') ->
+ #{
+ source => body,
+ rules => [
+ {type, integer},
+ not_required
+ ]
+ };
+request_param_info('TestFormOneof', 'form3') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestFormOneof', 'form4') ->
+ #{
+ source => body,
+ rules => [
+ {type, boolean},
+ not_required
+ ]
+ };
+request_param_info('TestFormOneof', 'id') ->
+ #{
+ source => body,
+ rules => [
+ {type, integer},
+ not_required
+ ]
+ };
+request_param_info('TestFormOneof', 'name') ->
+ #{
+ source => body,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestHeaderIntegerBooleanStringEnums', 'integer_header') ->
+ #{
+ source => header,
+ rules => [
+ {type, integer},
+ not_required
+ ]
+ };
+request_param_info('TestHeaderIntegerBooleanStringEnums', 'boolean_header') ->
+ #{
+ source => header,
+ rules => [
+ {type, boolean},
+ not_required
+ ]
+ };
+request_param_info('TestHeaderIntegerBooleanStringEnums', 'string_header') ->
+ #{
+ source => header,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestHeaderIntegerBooleanStringEnums', 'enum_nonref_string_header') ->
+ #{
+ source => header,
+ rules => [
+ {type, binary},
+ {enum, ['success', 'failure', 'unclassified'] },
+ not_required
+ ]
+ };
+request_param_info('TestHeaderIntegerBooleanStringEnums', 'enum_ref_string_header') ->
+ #{
+ source => header,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'path_string') ->
+ #{
+ source => binding,
+ rules => [
+ {type, binary},
+ required
+ ]
+ };
+request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'path_integer') ->
+ #{
+ source => binding,
+ rules => [
+ {type, integer},
+ required
+ ]
+ };
+request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'enum_nonref_string_path') ->
+ #{
+ source => binding,
+ rules => [
+ {type, binary},
+ {enum, ['success', 'failure', 'unclassified'] },
+ required
+ ]
+ };
+request_param_info('TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}', 'enum_ref_string_path') ->
+ #{
+ source => binding,
+ rules => [
+ required
+ ]
+ };
+request_param_info('TestEnumRefString', 'enum_nonref_string_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, binary},
+ {enum, ['success', 'failure', 'unclassified'] },
+ not_required
+ ]
+ };
+request_param_info('TestEnumRefString', 'enum_ref_string_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryDatetimeDateString', 'datetime_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, datetime},
+ not_required
+ ]
+ };
+request_param_info('TestQueryDatetimeDateString', 'date_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, date},
+ not_required
+ ]
+ };
+request_param_info('TestQueryDatetimeDateString', 'string_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestQueryIntegerBooleanString', 'integer_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, integer},
+ not_required
+ ]
+ };
+request_param_info('TestQueryIntegerBooleanString', 'boolean_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, boolean},
+ not_required
+ ]
+ };
+request_param_info('TestQueryIntegerBooleanString', 'string_query') ->
+ #{
+ source => qs_val,
+ rules => [
+ {type, binary},
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleDeepObjectExplodeTrueObject', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleDeepObjectExplodeTrueObjectAllOf', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleFormExplodeFalseArrayInteger', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleFormExplodeFalseArrayString', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleFormExplodeTrueArrayString', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleFormExplodeTrueObject', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info('TestQueryStyleFormExplodeTrueObjectAllOf', 'query_object') ->
+ #{
+ source => qs_val,
+ rules => [
+ not_required
+ ]
+ };
+request_param_info(OperationID, Name) ->
+ error({unknown_param, OperationID, Name}).
+
+populate_request_params(_, [], Req, _, Model) ->
+ {ok, Model, Req};
+populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
+ case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
+ {ok, K, V, Req} ->
+ populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model));
+ Error ->
+ Error
+ end.
+
+populate_request_param(OperationID, Name, Req0, ValidatorState) ->
+ #{rules := Rules, source := Source} = request_param_info(OperationID, Name),
+ case get_value(Source, Name, Req0) of
+ {error, Reason, Req} ->
+ {error, Reason, Req};
+ {Value, Req} ->
+ case prepare_param(Rules, Name, Value, ValidatorState) of
+ {ok, Result} -> {ok, Name, Result, Req};
+ {error, Reason} ->
+ {error, Reason, Req}
+ end
+ end.
+
+-include_lib("kernel/include/logger.hrl").
+
+validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
+ [
+ validate(schema, ReturnBaseType, Item, ValidatorState)
+ || Item <- Body];
+
+validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
+ validate(schema, ReturnBaseType, Body, ValidatorState).
+
+validate(Rule = required, Name, Value, _ValidatorState) ->
+ case Value of
+ undefined -> validation_error(Rule, Name);
+ _ -> ok
+ end;
+validate(not_required, _Name, _Value, _ValidatorState) ->
+ ok;
+validate(_, _Name, undefined, _ValidatorState) ->
+ ok;
+validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
+ try
+ {ok, to_int(Value)}
+ catch
+ error:badarg ->
+ validation_error(Rule, Name)
+ end;
+validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
+ try
+ {ok, to_float(Value)}
+ catch
+ error:badarg ->
+ validation_error(Rule, Name)
+ end;
+validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
+ case is_binary(Value) of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
+ {ok, Value};
+validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
+ V = binary_to_lower(Value),
+ try
+ case binary_to_existing_atom(V, utf8) of
+ B when is_boolean(B) -> {ok, B};
+ _ -> validation_error(Rule, Name)
+ end
+ catch
+ error:badarg ->
+ validation_error(Rule, Name)
+ end;
+validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
+ case is_binary(Value) of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
+ case is_binary(Value) of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
+ try
+ FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
+ case lists:member(FormattedValue, Values) of
+ true -> {ok, FormattedValue};
+ false -> validation_error(Rule, Name)
+ end
+ catch
+ error:badarg ->
+ validation_error(Rule, Name)
+ end;
+validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
+ case Value =< Max of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
+ case Value > ExclusiveMax of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
+ case Value >= Min of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
+ case Value =< ExclusiveMin of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
+ case size(Value) =< MaxLength of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
+ case size(Value) >= MinLength of
+ true -> ok;
+ false -> validation_error(Rule, Name)
+ end;
+validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
+ {ok, MP} = re:compile(Pattern),
+ case re:run(Value, MP) of
+ {match, _} -> ok;
+ _ -> validation_error(Rule, Name)
+ end;
+validate(Rule = schema, Name, Value, ValidatorState) ->
+ Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
+ try
+ _ = validate_with_schema(Value, Definition, ValidatorState),
+ ok
+ catch
+ throw:[{schema_invalid, _, Error} | _] ->
+ Info = #{
+ type => schema_invalid,
+ error => Error
+ },
+ validation_error(Rule, Name, Info);
+ throw:[{data_invalid, Schema, Error, _, Path} | _] ->
+ Info = #{
+ type => data_invalid,
+ error => Error,
+ schema => Schema,
+ path => Path
+ },
+ validation_error(Rule, Name, Info)
+ end;
+validate(Rule, Name, _Value, _ValidatorState) ->
+ ?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
+ error({unknown_validation_rule, Rule}).
+
+-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
+validation_error(ViolatedRule, Name) ->
+ validation_error(ViolatedRule, Name, #{}).
+
+-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
+validation_error(ViolatedRule, Name, Info) ->
+ throw({wrong_param, Name, ViolatedRule, Info}).
+
+-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) ->
+ {Value :: any(), Req :: cowboy_req:req()} |
+ {error, Reason :: any(), Req :: cowboy_req:req()}.
+get_value(body, _Name, Req0) ->
+ {ok, Body, Req} = cowboy_req:read_body(Req0),
+ case prepare_body(Body) of
+ {error, Reason} ->
+ {error, Reason, Req};
+ Value ->
+ {Value, Req}
+ end;
+get_value(qs_val, Name, Req) ->
+ QS = cowboy_req:parse_qs(Req),
+ Value = get_opt(to_qs(Name), QS),
+ {Value, Req};
+get_value(header, Name, Req) ->
+ Headers = cowboy_req:headers(Req),
+ Value = maps:get(to_header(Name), Headers, undefined),
+ {Value, Req};
+get_value(binding, Name, Req) ->
+ Value = cowboy_req:binding(to_binding(Name), Req),
+ {Value, Req}.
+
+prepare_body(<<>>) ->
+ <<>>;
+prepare_body(Body) ->
+ try
+ json:decode(Body)
+ catch
+ error:_ ->
+ {error, {invalid_body, not_json, Body}}
+ end.
+
+validate_with_schema(Body, Definition, ValidatorState) ->
+ jesse_schema_validator:validate_with_state(
+ [{<<"$ref">>, Definition}],
+ Body,
+ ValidatorState
+ ).
+
+prepare_param(Rules, Name, Value, ValidatorState) ->
+ try
+ Result = lists:foldl(
+ fun(Rule, Acc) ->
+ case validate(Rule, Name, Acc, ValidatorState) of
+ ok -> Acc;
+ {ok, Prepared} -> Prepared
+ end
+ end,
+ Value,
+ Rules
+ ),
+ {ok, Result}
+ catch
+ throw:Reason ->
+ {error, Reason}
+ end.
+
+-spec to_binary(iodata() | atom() | number()) -> binary().
+to_binary(V) when is_binary(V) -> V;
+to_binary(V) when is_list(V) -> iolist_to_binary(V);
+to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
+to_binary(V) when is_integer(V) -> integer_to_binary(V);
+to_binary(V) when is_float(V) -> float_to_binary(V).
+
+-spec to_list(iodata() | atom() | number()) -> binary().
+to_list(V) when is_list(V) -> V;
+to_list(V) when is_binary(V) -> binary_to_list(V);
+to_list(V) when is_atom(V) -> atom_to_list(V);
+to_list(V) when is_integer(V) -> integer_to_list(V);
+to_list(V) when is_float(V) -> float_to_list(V).
+
+-spec to_float(iodata()) -> float().
+to_float(V) ->
+ binary_to_float(iolist_to_binary([V])).
+
+-spec to_int(integer() | binary() | list()) -> integer().
+to_int(Data) when is_integer(Data) ->
+ Data;
+to_int(Data) when is_binary(Data) ->
+ binary_to_integer(Data);
+to_int(Data) when is_list(Data) ->
+ list_to_integer(Data).
+
+-spec to_header(iodata() | atom() | number()) -> binary().
+to_header(Name) ->
+ to_binary(string:lowercase(to_binary(Name))).
+
+binary_to_lower(V) when is_binary(V) ->
+ string:lowercase(V).
+
+-spec to_qs(iodata() | atom() | number()) -> binary().
+to_qs(Name) ->
+ to_binary(Name).
+
+-spec to_binding(iodata() | atom() | number()) -> atom().
+to_binding(Name) ->
+ Prepared = to_binary(Name),
+ binary_to_existing_atom(Prepared, utf8).
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} -> Value;
+ false -> Default
+ end.
+
+get_openapi_path() ->
+ {ok, AppName} = application:get_application(?MODULE),
+ filename:join(priv_dir(AppName), "openapi.json").
+
+-include_lib("kernel/include/file.hrl").
+
+-spec priv_dir(Application :: atom()) -> file:name_all().
+priv_dir(AppName) ->
+ case code:priv_dir(AppName) of
+ Value when is_list(Value) ->
+ Value ++ "/";
+ _Error ->
+ select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
+ end.
+
+select_priv_dir(Paths) ->
+ case lists:dropwhile(fun test_priv_dir/1, Paths) of
+ [Path | _] -> Path;
+ _ -> exit(no_priv_dir)
+ end.
+
+test_priv_dir(Path) ->
+ case file:read_file_info(Path) of
+ {ok, #file_info{type = directory}} ->
+ false;
+ _ ->
+ true
+ end.
diff --git a/samples/server/echo_api/erlang-server/src/openapi_auth.erl b/samples/server/echo_api/erlang-server/src/openapi_auth.erl
new file mode 100644
index 000000000000..0e7beb1132c4
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_auth.erl
@@ -0,0 +1,45 @@
+-module(openapi_auth).
+
+-export([authorize_api_key/5]).
+
+-spec authorize_api_key(openapi_logic_handler:api_key_callback(),
+ openapi_api:operation_id(),
+ header | qs_val,
+ iodata() | atom(),
+ cowboy_req:req()) ->
+ {true, openapi_logic_handler:context(), cowboy_req:req()} |
+ {false, binary(), cowboy_req:req()}.
+authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
+ {ApiKey, Req} = get_api_key(From, KeyParam, Req0),
+ case ApiKey of
+ undefined ->
+ AuthHeader = <<>>,
+ {false, AuthHeader, Req};
+ _ ->
+ case Handler(OperationID, ApiKey) of
+ {true, Context} ->
+ {true, Context, Req};
+ {false, AuthHeader} ->
+ {false, AuthHeader, Req}
+ end
+ end.
+
+get_api_key(header, KeyParam, Req) ->
+ Headers = cowboy_req:headers(Req),
+ {maps:get(KeyParam, Headers, undefined), Req};
+get_api_key(qs_val, KeyParam, Req) ->
+ QS = cowboy_req:parse_qs(Req),
+ {get_opt(KeyParam, QS), Req}.
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} ->
+ Value;
+ false ->
+ Default
+ end.
diff --git a/samples/server/echo_api/erlang-server/src/openapi_auth_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_auth_handler.erl
new file mode 100644
index 000000000000..9b8e9b18352f
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_auth_handler.erl
@@ -0,0 +1,126 @@
+%% basic handler
+-module(openapi_auth_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestAuthHttpBasic'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestAuthHttpBearer'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req0,
+ #state{operation_id = 'TestAuthHttpBasic' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
+ end;
+is_authorized(Req0,
+ #state{operation_id = 'TestAuthHttpBearer' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
+ end;
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestAuthHttpBasic'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestAuthHttpBearer'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestAuthHttpBasic'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestAuthHttpBearer'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestAuthHttpBasic'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestAuthHttpBearer'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(auth, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(auth, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_body_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_body_handler.erl
new file mode 100644
index 000000000000..de4fd624e10f
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_body_handler.erl
@@ -0,0 +1,206 @@
+%% basic handler
+-module(openapi_body_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestBinaryGif'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestBodyApplicationOctetstreamBinary'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestBodyMultipartFormdataArrayOfBinary'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestBodyMultipartFormdataSingleBinary'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyAllOfPet'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyFreeFormObjectResponseString'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyPet'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyPetResponseString'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyStringEnum'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestEchoBodyTagResponseString'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestBinaryGif'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestBodyApplicationOctetstreamBinary'} = State) ->
+ {[
+ {<<"application/octet-stream">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestBodyMultipartFormdataArrayOfBinary'} = State) ->
+ {[
+ {<<"multipart/form-data">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestBodyMultipartFormdataSingleBinary'} = State) ->
+ {[
+ {<<"multipart/form-data">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyAllOfPet'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyFreeFormObjectResponseString'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyPet'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyPetResponseString'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyStringEnum'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestEchoBodyTagResponseString'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestBinaryGif'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestBodyApplicationOctetstreamBinary'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestBodyMultipartFormdataArrayOfBinary'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestBodyMultipartFormdataSingleBinary'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyAllOfPet'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyFreeFormObjectResponseString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyPet'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyPetResponseString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyStringEnum'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestEchoBodyTagResponseString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestBinaryGif'} = State) ->
+ {[
+ {<<"image/gif">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestBodyApplicationOctetstreamBinary'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestBodyMultipartFormdataArrayOfBinary'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestBodyMultipartFormdataSingleBinary'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyAllOfPet'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyFreeFormObjectResponseString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyPet'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyPetResponseString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyStringEnum'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestEchoBodyTagResponseString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(body, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(body, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_form_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_form_handler.erl
new file mode 100644
index 000000000000..2d6fff400073
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_form_handler.erl
@@ -0,0 +1,124 @@
+%% basic handler
+-module(openapi_form_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestFormIntegerBooleanString'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestFormObjectMultipart'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestFormOneof'} = State) ->
+ {[<<"POST">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestFormIntegerBooleanString'} = State) ->
+ {[
+ {<<"application/x-www-form-urlencoded">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestFormObjectMultipart'} = State) ->
+ {[
+ {<<"multipart/form-data">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestFormOneof'} = State) ->
+ {[
+ {<<"application/x-www-form-urlencoded">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestFormIntegerBooleanString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestFormObjectMultipart'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestFormOneof'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestFormIntegerBooleanString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestFormObjectMultipart'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestFormOneof'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(form, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(form, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_header_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_header_handler.erl
new file mode 100644
index 000000000000..8077a9ca8826
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_header_handler.erl
@@ -0,0 +1,98 @@
+%% basic handler
+-module(openapi_header_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestHeaderIntegerBooleanStringEnums'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestHeaderIntegerBooleanStringEnums'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestHeaderIntegerBooleanStringEnums'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestHeaderIntegerBooleanStringEnums'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(header, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(header, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_logic_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_logic_handler.erl
new file mode 100644
index 000000000000..28d45ab65113
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_logic_handler.erl
@@ -0,0 +1,56 @@
+-module(openapi_logic_handler).
+
+-include_lib("kernel/include/logger.hrl").
+
+-type api_key_callback() ::
+ fun((openapi_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
+-type accept_callback() ::
+ fun((atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}).
+-type provide_callback() ::
+ fun((atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}).
+-type context() :: #{binary() => any()}.
+
+-export_type([context/0, api_key_callback/0, accept_callback/0, provide_callback/0]).
+
+-optional_callbacks([api_key_callback/2]).
+
+-callback api_key_callback(openapi_api:operation_id(), binary()) ->
+ {true, context()} | {false, iodata()}.
+
+-callback accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+
+-callback provide_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+
+-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
+-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
+
+-spec api_key_callback(openapi_api:operation_id(), binary()) -> {true, #{}}.
+api_key_callback(OperationID, ApiKey) ->
+ ?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
+ operation_id => OperationID,
+ api_key => ApiKey}),
+ {true, #{}}.
+
+-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {501, #{}, #{}}.
+
+-spec provide_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+provide_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {<<>>, Req, Context}.
diff --git a/samples/server/echo_api/erlang-server/src/openapi_path_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_path_handler.erl
new file mode 100644
index 000000000000..0d08034f3932
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_path_handler.erl
@@ -0,0 +1,98 @@
+%% basic handler
+-module(openapi_path_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(path, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(path, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_query_handler.erl b/samples/server/echo_api/erlang-server/src/openapi_query_handler.erl
new file mode 100644
index 000000000000..5abfddcf7826
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_query_handler.erl
@@ -0,0 +1,188 @@
+%% basic handler
+-module(openapi_query_handler).
+
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
+%% Cowboy REST callbacks
+-export([init/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2]).
+-export([content_types_provided/2]).
+-export([delete_resource/2]).
+-export([is_authorized/2]).
+-export([valid_content_headers/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
+
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
+
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
+
+-type state() :: #state{}.
+
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
+ Method = cowboy_req:method(Req),
+ OperationID = maps:get(Method, Operations, undefined),
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
+ {cowboy_rest, Req, State}.
+
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'TestEnumRefString'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryDatetimeDateString'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryIntegerBooleanString'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObject'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObjectAllOf'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayInteger'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayString'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueArrayString'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObject'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObjectAllOf'} = State) ->
+ {[<<"GET">>], Req, State};
+allowed_methods(Req, State) ->
+ {[], Req, State}.
+
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req, State) ->
+ {true, Req, State}.
+
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'TestEnumRefString'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryDatetimeDateString'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryIntegerBooleanString'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObject'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObjectAllOf'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayInteger'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayString'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueArrayString'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObject'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObjectAllOf'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'TestEnumRefString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryDatetimeDateString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryIntegerBooleanString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObject'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObjectAllOf'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayInteger'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueArrayString'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObject'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObjectAllOf'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, State) ->
+ {false, Req, State}.
+
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'TestEnumRefString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryDatetimeDateString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryIntegerBooleanString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObject'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleDeepObjectExplodeTrueObjectAllOf'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayInteger'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleFormExplodeFalseArrayString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueArrayString'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObject'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'TestQueryStyleFormExplodeTrueObjectAllOf'} = State) ->
+ {[
+ {<<"text/plain">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+delete_resource(Req, State) ->
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
+ end.
+
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(query, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(query, OperationID, Req, State#state.context).
diff --git a/samples/server/echo_api/erlang-server/src/openapi_router.erl b/samples/server/echo_api/erlang-server/src/openapi_router.erl
new file mode 100644
index 000000000000..84cee5a6256d
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_router.erl
@@ -0,0 +1,172 @@
+-module(openapi_router).
+
+-export([get_paths/1]).
+
+-type method() :: binary().
+-type operations() :: #{method() => openapi_api:operation_id()}.
+-type init_opts() :: {operations(), module()}.
+
+-export_type([init_opts/0]).
+
+-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
+get_paths(LogicHandler) ->
+ PreparedPaths = maps:fold(
+ fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
+ [{Path, Handler, Operations} | Acc]
+ end, [], group_paths()
+ ),
+ [{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
+
+group_paths() ->
+ maps:fold(
+ fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
+ case maps:find(Path, Acc) of
+ {ok, PathInfo0 = #{operations := Operations0}} ->
+ Operations = Operations0#{Method => OperationID},
+ PathInfo = PathInfo0#{operations => Operations},
+ Acc#{Path => PathInfo};
+ error ->
+ Operations = #{Method => OperationID},
+ PathInfo = #{handler => Handler, operations => Operations},
+ Acc#{Path => PathInfo}
+ end
+ end, #{}, get_operations()).
+
+get_operations() ->
+ #{
+ 'TestAuthHttpBasic' => #{
+ path => "/auth/http/basic",
+ method => <<"POST">>,
+ handler => 'openapi_auth_handler'
+ },
+ 'TestAuthHttpBearer' => #{
+ path => "/auth/http/bearer",
+ method => <<"POST">>,
+ handler => 'openapi_auth_handler'
+ },
+ 'TestBinaryGif' => #{
+ path => "/binary/gif",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestBodyApplicationOctetstreamBinary' => #{
+ path => "/body/application/octetstream/binary",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestBodyMultipartFormdataArrayOfBinary' => #{
+ path => "/body/application/octetstream/array_of_binary",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestBodyMultipartFormdataSingleBinary' => #{
+ path => "/body/application/octetstream/single_binary",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyAllOfPet' => #{
+ path => "/echo/body/allOf/Pet",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyFreeFormObjectResponseString' => #{
+ path => "/echo/body/FreeFormObject/response_string",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyPet' => #{
+ path => "/echo/body/Pet",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyPetResponseString' => #{
+ path => "/echo/body/Pet/response_string",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyStringEnum' => #{
+ path => "/echo/body/string_enum",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestEchoBodyTagResponseString' => #{
+ path => "/echo/body/Tag/response_string",
+ method => <<"POST">>,
+ handler => 'openapi_body_handler'
+ },
+ 'TestFormIntegerBooleanString' => #{
+ path => "/form/integer/boolean/string",
+ method => <<"POST">>,
+ handler => 'openapi_form_handler'
+ },
+ 'TestFormObjectMultipart' => #{
+ path => "/form/object/multipart",
+ method => <<"POST">>,
+ handler => 'openapi_form_handler'
+ },
+ 'TestFormOneof' => #{
+ path => "/form/oneof",
+ method => <<"POST">>,
+ handler => 'openapi_form_handler'
+ },
+ 'TestHeaderIntegerBooleanStringEnums' => #{
+ path => "/header/integer/boolean/string/enums",
+ method => <<"GET">>,
+ handler => 'openapi_header_handler'
+ },
+ 'TestsPathString{pathString}Integer{pathInteger}{enumNonrefStringPath}{enumRefStringPath}' => #{
+ path => "/path/string/:path_string/integer/:path_integer/:enum_nonref_string_path/:enum_ref_string_path",
+ method => <<"GET">>,
+ handler => 'openapi_path_handler'
+ },
+ 'TestEnumRefString' => #{
+ path => "/query/enum_ref_string",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryDatetimeDateString' => #{
+ path => "/query/datetime/date/string",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryIntegerBooleanString' => #{
+ path => "/query/integer/boolean/string",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleDeepObjectExplodeTrueObject' => #{
+ path => "/query/style_deepObject/explode_true/object",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleDeepObjectExplodeTrueObjectAllOf' => #{
+ path => "/query/style_deepObject/explode_true/object/allOf",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleFormExplodeFalseArrayInteger' => #{
+ path => "/query/style_form/explode_false/array_integer",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleFormExplodeFalseArrayString' => #{
+ path => "/query/style_form/explode_false/array_string",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleFormExplodeTrueArrayString' => #{
+ path => "/query/style_form/explode_true/array_string",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleFormExplodeTrueObject' => #{
+ path => "/query/style_form/explode_true/object",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ },
+ 'TestQueryStyleFormExplodeTrueObjectAllOf' => #{
+ path => "/query/style_form/explode_true/object/allOf",
+ method => <<"GET">>,
+ handler => 'openapi_query_handler'
+ }
+ }.
diff --git a/samples/server/echo_api/erlang-server/src/openapi_server.erl b/samples/server/echo_api/erlang-server/src/openapi_server.erl
new file mode 100644
index 000000000000..0cd992fe69d2
--- /dev/null
+++ b/samples/server/echo_api/erlang-server/src/openapi_server.erl
@@ -0,0 +1,43 @@
+-module(openapi_server).
+
+-define(DEFAULT_LOGIC_HANDLER, openapi_logic_handler).
+
+-export([start/2]).
+-ignore_xref([start/2]).
+
+-spec start(term(), #{transport => tcp | ssl,
+ transport_opts => ranch:opts(),
+ protocol_opts => cowboy:opts(),
+ logic_handler => module()}) ->
+ {ok, pid()} | {error, any()}.
+start(ID, Params) ->
+ Transport = maps:get(transport, Params, tcp),
+ TransportOpts = maps:get(transport_opts, Params, #{}),
+ ProtocolOpts = maps:get(procotol_opts, Params, #{}),
+ LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
+ CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
+ case Transport of
+ ssl ->
+ cowboy:start_tls(ID, TransportOpts, CowboyOpts);
+ tcp ->
+ cowboy:start_clear(ID, TransportOpts, CowboyOpts)
+ end.
+
+get_cowboy_config(LogicHandler, ExtraOpts) ->
+ DefaultOpts = get_default_opts(LogicHandler),
+ maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
+
+get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
+ maps:put(env, Env, AccIn);
+get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
+ Env = maps:merge(OldEnv, NewEnv),
+ maps:put(env, Env, AccIn);
+get_cowboy_config(Key, Value, AccIn) ->
+ maps:put(Key, Value, AccIn).
+
+get_default_dispatch(LogicHandler) ->
+ Paths = openapi_router:get_paths(LogicHandler),
+ #{dispatch => cowboy_router:compile(Paths)}.
+
+get_default_opts(LogicHandler) ->
+ #{env => get_default_dispatch(LogicHandler)}.
diff --git a/samples/server/petstore/erlang-server/.openapi-generator/FILES b/samples/server/petstore/erlang-server/.openapi-generator/FILES
index 9c50202b9359..ac7922db6682 100644
--- a/samples/server/petstore/erlang-server/.openapi-generator/FILES
+++ b/samples/server/petstore/erlang-server/.openapi-generator/FILES
@@ -4,11 +4,9 @@ rebar.config
src/openapi.app.src
src/openapi_api.erl
src/openapi_auth.erl
-src/openapi_default_logic_handler.erl
src/openapi_logic_handler.erl
src/openapi_pet_handler.erl
src/openapi_router.erl
src/openapi_server.erl
src/openapi_store_handler.erl
src/openapi_user_handler.erl
-src/openapi_utils.erl
diff --git a/samples/server/petstore/erlang-server/README.md b/samples/server/petstore/erlang-server/README.md
index 5d36ca07576b..887f730062fe 100644
--- a/samples/server/petstore/erlang-server/README.md
+++ b/samples/server/petstore/erlang-server/README.md
@@ -4,54 +4,33 @@
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
-Dependency: [Cowboy](https://github.com/ninenines/cowboy)
+Dependencies: Erlang OTP/27 and rebar3. Also:
+- [Cowboy](https://hex.pm/packages/cowboy)
+- [Ranch](https://hex.pm/packages/ranch)
+- [Jesse](https://hex.pm/packages/jesse)
## Prerequisites
-TODO
-
## Getting started
-Use erlang-server with erlang.mk
-
- 1, Create an application by using erlang.mk
- $ mkdir http_server
- $ cd http_server
- $ wget https://erlang.mk/erlang.mk
- $ make -f erlang.mk bootstrap bootstrap-rel
- $ make run
-
- 2, Modify the Makefile in the http_server directory to the following to introduce the dependency library:
- PROJECT = http_server
- PROJECT_DESCRIPTION = New project
- PROJECT_VERSION = 0.1.0
-
- DEPS = cowboy jesse jsx
- dep_cowboy_commit = 2.5.0
- dep_jesse_commit = 1.5.2
- dep_jsx_commit = 2.9.0
- DEP_PLUGINS = cowboy jesse jsx
-
- PACKAGES += rfc3339
- pkg_rfc3339_name = rfc3339
- pkg_rfc3339_description = an erlang/elixir rfc3339 lib
- pkg_rfc3339_homepage = https://github.com/talentdeficit/rfc3339
- pkg_rfc3339_fetch = git
- pkg_rfc3339_repo = https://github.com/talentdeficit/rfc3339
- pkg_rfc3339_commit = master
-
- include erlang.mk
-
- 3, Generate erlang-server project using openapi-generator
+Use erlang-server with rebar3
+
+ 1, Create an application by using rebar3
+ $ rebar3 new app http_server
+
+ 2, Generate erlang-server project using openapi-generator
https://github.com/OpenAPITools/openapi-generator#2---getting-started
- 4, Copy erlang-server file to http_server project,Don't forget the 'priv' folder.
+ 3, Copy erlang-server file to http_server project, and don't forget the 'priv' folder.
- 5, Start in the http_server project:
+ 4, Start in the http_server project:
1, Introduce the following line in the http_server_app:start(_Type, _Args) function
- openapi_server:start(http_server, #{ip=>{127,0,0,1}, port=>8080, net_opts=>[]})
- 2, Compilation http_server project
- $ make
+ openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
+ 2, Compile your http_server project
+ $ rebar3 compile
3, Start erlang virtual machine
- $erl -pa ./deps/cowboy/ebin -pa ./deps/cowlib/ebin -pa ./deps/ranch/ebin -pa ./deps/jsx/ebin -pa ./deps/jesse/ebin -pa ./deps/rfc3339/ebin -pa ./ebin
+ $ rebar3 shell
4, Start project
application:ensure_all_started(http_server).
+
+To implement your own business logic, create a module called `http_server_logic` that implements the
+behaviour `openapi_logic_handler`. Refer to `openapi_logic_handler` documentation for details.
diff --git a/samples/server/petstore/erlang-server/rebar.config b/samples/server/petstore/erlang-server/rebar.config
index 743b108f384a..50cd482ca39b 100644
--- a/samples/server/petstore/erlang-server/rebar.config
+++ b/samples/server/petstore/erlang-server/rebar.config
@@ -1,6 +1,15 @@
+{minimum_otp_vsn, "27"}.
+
{deps, [
- {cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
- {rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
- {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
- {jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
+ {cowboy, "2.12.0"},
+ {ranch, "2.1.0"},
+ {jesse, "1.8.1"}
]}.
+
+{dialyzer,
+ [{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
+ {warnings, [missing_return, unknown]}
+]}.
+
+{xref_checks,
+ [undefined_function_calls, deprecated_function_calls, deprecated_functions]}.
diff --git a/samples/server/petstore/erlang-server/src/openapi.app.src b/samples/server/petstore/erlang-server/src/openapi.app.src
index 99859823b68e..0172627719b7 100644
--- a/samples/server/petstore/erlang-server/src/openapi.app.src
+++ b/samples/server/petstore/erlang-server/src/openapi.app.src
@@ -1,19 +1,11 @@
-{application, openapi, [
- {description, "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters."},
- {vsn, "1.0.0"},
- {registered, []},
- {applications, [
- kernel,
- stdlib,
- ssl,
- inets,
- jsx,
- jesse,
- cowboy
- ]},
- {env, [
- ]},
- {modules, []},
- {licenses, ["Apache-2.0"]},
- {links, []}
-]}.
+{application,
+ openapi,
+ [{description,
+ "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters."},
+ {vsn, "1.0.0"},
+ {registered, []},
+ {applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
+ {env, []},
+ {modules, []},
+ {licenses, ["Apache-2.0"]},
+ {links, []}]}.
diff --git a/samples/server/petstore/erlang-server/src/openapi_api.erl b/samples/server/petstore/erlang-server/src/openapi_api.erl
index 8b9cb84eaa49..16ad18e97823 100644
--- a/samples/server/petstore/erlang-server/src/openapi_api.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_api.erl
@@ -1,404 +1,491 @@
-module(openapi_api).
+-moduledoc """
+This module offers an API for JSON schema validation, using `jesse` under the hood.
+
+If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
+and request and response can be validated using `populate_request/3`
+and `validate_response/4` respectively.
+
+For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
+```
+-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ValidatorState = openapi_api:prepare_validator(),
+ case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
+ {ok, Populated, Req1} ->
+ {Code, Headers, Body} = openapi_logic_handler:handle_request(
+ LogicHandler,
+ OperationID,
+ Req1,
+ maps:merge(State#state.context, Populated)
+ ),
+ _ = openapi_api:validate_response(
+ OperationID,
+ Code,
+ Body,
+ ValidatorState
+ ),
+ PreparedBody = prepare_body(Code, Body),
+ Response = {ok, {Code, Headers, PreparedBody}},
+ process_response(Response, Req1, State);
+ {error, Reason, Req1} ->
+ process_response({error, Reason}, Req1, State)
+ end.
+```
+""".
+
+-export([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
+-export([populate_request/3, validate_response/4]).
--export([request_params/1]).
--export([request_param_info/2]).
--export([populate_request/3]).
--export([validate_response/4]).
-%% exported to silence openapi complains
--export([get_value/3, validate_response_body/4]).
+-ignore_xref([populate_request/3, validate_response/4]).
+-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-type operation_id() :: atom().
-type request_param() :: atom().
-export_type([operation_id/0]).
--spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
+-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
+
+-type rule() ::
+ {type, binary} |
+ {type, integer} |
+ {type, float} |
+ {type, boolean} |
+ {type, date} |
+ {type, datetime} |
+ {enum, [atom()]} |
+ {max, Max :: number()} |
+ {exclusive_max, Max :: number()} |
+ {min, Min :: number()} |
+ {exclusive_min, Min :: number()} |
+ {max_length, MaxLength :: integer()} |
+ {min_length, MaxLength :: integer()} |
+ {pattern, Pattern :: string()} |
+ schema |
+ required |
+ not_required.
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator() -> jesse_state:state().
+prepare_validator() ->
+ prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
+
+-doc #{equiv => prepare_validator/2}.
+-spec prepare_validator(binary()) -> jesse_state:state().
+prepare_validator(SchemaVer) ->
+ prepare_validator(get_openapi_path(), SchemaVer).
+
+-doc """
+Loads the JSON schema and the desired validation draft into a `t:jesse_state:state()`.
+""".
+-spec prepare_validator(file:name_all(), binary()) -> jesse_state:state().
+prepare_validator(OpenApiPath, SchemaVer) ->
+ {ok, FileContents} = file:read_file(OpenApiPath),
+ R = json:decode(FileContents),
+ jesse_state:new(R, [{default_schema_ver, SchemaVer}]).
+
+-doc """
+Automatically loads the entire body from the cowboy req
+and validates the JSON body against the schema.
+""".
+-spec populate_request(
+ OperationID :: operation_id(),
+ Req :: cowboy_req:req(),
+ ValidatorState :: jesse_state:state()) ->
+ {ok, Model :: #{}, Req :: cowboy_req:req()} |
+ {error, Reason :: any(), Req :: cowboy_req:req()}.
+populate_request(OperationID, Req, ValidatorState) ->
+ Params = request_params(OperationID),
+ populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
+-doc """
+Validates that the provided `Code` and `Body` comply with the `ValidatorState` schema
+for the `OperationID` operation.
+""".
+-spec validate_response(
+ OperationID :: operation_id(),
+ Code :: 200..599,
+ Body :: jesse:json_term(),
+ ValidatorState :: jesse_state:state()) ->
+ ok | {ok, term()} | [ok | {ok, term()}] | no_return().
+validate_response('AddPet', 200, Body, ValidatorState) ->
+ validate_response_body('Pet', 'Pet', Body, ValidatorState);
+validate_response('AddPet', 405, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('DeletePet', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('FindPetsByStatus', 200, Body, ValidatorState) ->
+ validate_response_body('list', 'Pet', Body, ValidatorState);
+validate_response('FindPetsByStatus', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('FindPetsByTags', 200, Body, ValidatorState) ->
+ validate_response_body('list', 'Pet', Body, ValidatorState);
+validate_response('FindPetsByTags', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetPetById', 200, Body, ValidatorState) ->
+ validate_response_body('Pet', 'Pet', Body, ValidatorState);
+validate_response('GetPetById', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetPetById', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdatePet', 200, Body, ValidatorState) ->
+ validate_response_body('Pet', 'Pet', Body, ValidatorState);
+validate_response('UpdatePet', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdatePet', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdatePet', 405, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdatePetWithForm', 405, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UploadFile', 200, Body, ValidatorState) ->
+ validate_response_body('ApiResponse', 'ApiResponse', Body, ValidatorState);
+validate_response('DeleteOrder', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('DeleteOrder', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetInventory', 200, Body, ValidatorState) ->
+ validate_response_body('map', 'integer', Body, ValidatorState);
+validate_response('GetOrderById', 200, Body, ValidatorState) ->
+ validate_response_body('Order', 'Order', Body, ValidatorState);
+validate_response('GetOrderById', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetOrderById', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('PlaceOrder', 200, Body, ValidatorState) ->
+ validate_response_body('Order', 'Order', Body, ValidatorState);
+validate_response('PlaceOrder', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('CreateUser', 0, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('CreateUsersWithArrayInput', 0, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('CreateUsersWithListInput', 0, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('DeleteUser', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('DeleteUser', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetUserByName', 200, Body, ValidatorState) ->
+ validate_response_body('User', 'User', Body, ValidatorState);
+validate_response('GetUserByName', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('GetUserByName', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('LoginUser', 200, Body, ValidatorState) ->
+ validate_response_body('binary', 'string', Body, ValidatorState);
+validate_response('LoginUser', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('LogoutUser', 0, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdateUser', 400, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response('UpdateUser', 404, Body, ValidatorState) ->
+ validate_response_body('', '', Body, ValidatorState);
+validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
+ ok.
+
+%%%
+-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
request_params('AddPet') ->
[
'Pet'
];
-
request_params('DeletePet') ->
[
'petId',
'api_key'
];
-
request_params('FindPetsByStatus') ->
[
'status'
];
-
request_params('FindPetsByTags') ->
[
'tags'
];
-
request_params('GetPetById') ->
[
'petId'
];
-
request_params('UpdatePet') ->
[
'Pet'
];
-
request_params('UpdatePetWithForm') ->
[
'petId',
'name',
'status'
];
-
request_params('UploadFile') ->
[
'petId',
'additionalMetadata',
'file'
];
-
-
request_params('DeleteOrder') ->
[
'orderId'
];
-
request_params('GetInventory') ->
[
];
-
request_params('GetOrderById') ->
[
'orderId'
];
-
request_params('PlaceOrder') ->
[
'Order'
];
-
-
request_params('CreateUser') ->
[
'User'
];
-
request_params('CreateUsersWithArrayInput') ->
[
'list'
];
-
request_params('CreateUsersWithListInput') ->
[
'list'
];
-
request_params('DeleteUser') ->
[
'username'
];
-
request_params('GetUserByName') ->
[
'username'
];
-
request_params('LoginUser') ->
[
'username',
'password'
];
-
request_params('LogoutUser') ->
[
];
-
request_params('UpdateUser') ->
[
'username',
'User'
];
-
request_params(_) ->
error(unknown_operation).
--type rule() ::
- {type, 'binary'} |
- {type, 'integer'} |
- {type, 'float'} |
- {type, 'binary'} |
- {type, 'boolean'} |
- {type, 'date'} |
- {type, 'datetime'} |
- {enum, [atom()]} |
- {max, Max :: number()} |
- {exclusive_max, Max :: number()} |
- {min, Min :: number()} |
- {exclusive_min, Min :: number()} |
- {max_length, MaxLength :: integer()} |
- {min_length, MaxLength :: integer()} |
- {pattern, Pattern :: string()} |
- schema |
- required |
- not_required.
-
--spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{
- source => qs_val | binding | header | body,
- rules => [rule()]
-}.
-
-
-
+-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
+ #{source => qs_val | binding | header | body, rules => [rule()]}.
request_param_info('AddPet', 'Pet') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info('DeletePet', 'petId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'integer'},
+ {type, integer},
required
]
};
-
request_param_info('DeletePet', 'api_key') ->
#{
- source => header,
+ source => header,
rules => [
- {type, 'binary'},
+ {type, binary},
not_required
]
};
-
request_param_info('FindPetsByStatus', 'status') ->
#{
- source => qs_val ,
+ source => qs_val,
rules => [
{enum, ['available', 'pending', 'sold'] },
required
]
};
-
request_param_info('FindPetsByTags', 'tags') ->
#{
- source => qs_val ,
+ source => qs_val,
rules => [
required
]
};
-
request_param_info('GetPetById', 'petId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'integer'},
+ {type, integer},
required
]
};
-
request_param_info('UpdatePet', 'Pet') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info('UpdatePetWithForm', 'petId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'integer'},
+ {type, integer},
required
]
};
-
request_param_info('UpdatePetWithForm', 'name') ->
#{
- source => body,
+ source => body,
rules => [
- {type, 'binary'},
+ {type, binary},
not_required
]
};
-
request_param_info('UpdatePetWithForm', 'status') ->
#{
- source => body,
+ source => body,
rules => [
- {type, 'binary'},
+ {type, binary},
not_required
]
};
-
request_param_info('UploadFile', 'petId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'integer'},
+ {type, integer},
required
]
};
-
request_param_info('UploadFile', 'additionalMetadata') ->
#{
- source => body,
+ source => body,
rules => [
- {type, 'binary'},
+ {type, binary},
not_required
]
};
-
request_param_info('UploadFile', 'file') ->
#{
- source => body,
+ source => body,
rules => [
- {type, 'binary'},
+ {type, binary},
not_required
]
};
-
-
request_param_info('DeleteOrder', 'orderId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'binary'},
+ {type, binary},
required
]
};
-
request_param_info('GetOrderById', 'orderId') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'integer'},
- {max, 5 },
- {min, 1 },
+ {type, integer},
+ {max, 5},
+ {min, 1},
required
]
};
-
request_param_info('PlaceOrder', 'Order') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
-
request_param_info('CreateUser', 'User') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info('CreateUsersWithArrayInput', 'list') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info('CreateUsersWithListInput', 'list') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info('DeleteUser', 'username') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'binary'},
+ {type, binary},
required
]
};
-
request_param_info('GetUserByName', 'username') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'binary'},
+ {type, binary},
required
]
};
-
request_param_info('LoginUser', 'username') ->
#{
- source => qs_val ,
+ source => qs_val,
rules => [
- {type, 'binary'},
- {pattern, "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" },
+ {type, binary},
+ {pattern, "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"},
required
]
};
-
request_param_info('LoginUser', 'password') ->
#{
- source => qs_val ,
+ source => qs_val,
rules => [
- {type, 'binary'},
+ {type, binary},
required
]
};
-
request_param_info('UpdateUser', 'username') ->
#{
- source => binding ,
+ source => binding,
rules => [
- {type, 'binary'},
+ {type, binary},
required
]
};
-
request_param_info('UpdateUser', 'User') ->
#{
- source => body,
+ source => body,
rules => [
schema,
required
]
};
-
request_param_info(OperationID, Name) ->
error({unknown_param, OperationID, Name}).
--spec populate_request(
- OperationID :: operation_id(),
- Req :: cowboy_req:req(),
- ValidatorState :: jesse_state:state()
-) ->
- {ok, Model :: #{}, Req :: cowboy_req:req()} |
- {error, Reason :: any(), Req :: cowboy_req:req()}.
-
-populate_request(OperationID, Req, ValidatorState) ->
- Params = request_params(OperationID),
- populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
-
populate_request_params(_, [], Req, _, Model) ->
{ok, Model, Req};
-
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
{ok, K, V, Req} ->
@@ -420,115 +507,9 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
end
end.
--spec validate_response(
- OperationID :: operation_id(),
- Code :: 200..599,
- Body :: jesse:json_term(),
- ValidatorState :: jesse_state:state()
-) -> ok | no_return().
-
-
-validate_response('AddPet', 200, Body, ValidatorState) ->
- validate_response_body('Pet', 'Pet', Body, ValidatorState);
-validate_response('AddPet', 405, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('DeletePet', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('FindPetsByStatus', 200, Body, ValidatorState) ->
- validate_response_body('list', 'Pet', Body, ValidatorState);
-validate_response('FindPetsByStatus', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('FindPetsByTags', 200, Body, ValidatorState) ->
- validate_response_body('list', 'Pet', Body, ValidatorState);
-validate_response('FindPetsByTags', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
+-include_lib("kernel/include/logger.hrl").
-validate_response('GetPetById', 200, Body, ValidatorState) ->
- validate_response_body('Pet', 'Pet', Body, ValidatorState);
-validate_response('GetPetById', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('GetPetById', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('UpdatePet', 200, Body, ValidatorState) ->
- validate_response_body('Pet', 'Pet', Body, ValidatorState);
-validate_response('UpdatePet', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('UpdatePet', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('UpdatePet', 405, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('UpdatePetWithForm', 405, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('UploadFile', 200, Body, ValidatorState) ->
- validate_response_body('ApiResponse', 'ApiResponse', Body, ValidatorState);
-
-
-validate_response('DeleteOrder', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('DeleteOrder', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('GetInventory', 200, Body, ValidatorState) ->
- validate_response_body('map', 'integer', Body, ValidatorState);
-
-validate_response('GetOrderById', 200, Body, ValidatorState) ->
- validate_response_body('Order', 'Order', Body, ValidatorState);
-validate_response('GetOrderById', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('GetOrderById', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('PlaceOrder', 200, Body, ValidatorState) ->
- validate_response_body('Order', 'Order', Body, ValidatorState);
-validate_response('PlaceOrder', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-
-validate_response('CreateUser', 0, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('CreateUsersWithArrayInput', 0, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('CreateUsersWithListInput', 0, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('DeleteUser', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('DeleteUser', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('GetUserByName', 200, Body, ValidatorState) ->
- validate_response_body('User', 'User', Body, ValidatorState);
-validate_response('GetUserByName', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('GetUserByName', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('LoginUser', 200, Body, ValidatorState) ->
- validate_response_body('binary', 'string', Body, ValidatorState);
-validate_response('LoginUser', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('LogoutUser', 0, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-validate_response('UpdateUser', 400, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-validate_response('UpdateUser', 404, Body, ValidatorState) ->
- validate_response_body('', '', Body, ValidatorState);
-
-
-validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
- ok.
-
-validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
+validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, ReturnBaseType, Item, ValidatorState)
|| Item <- Body];
@@ -536,45 +517,37 @@ validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
validate(schema, ReturnBaseType, Body, ValidatorState).
-%%%
validate(Rule = required, Name, Value, _ValidatorState) ->
case Value of
undefined -> validation_error(Rule, Name);
_ -> ok
end;
-
validate(not_required, _Name, _Value, _ValidatorState) ->
ok;
-
validate(_, _Name, undefined, _ValidatorState) ->
ok;
-
-validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
try
- {ok, openapi_utils:to_int(Value)}
+ {ok, to_int(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
try
- {ok, openapi_utils:to_float(Value)}
+ {ok, to_float(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
-validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
+validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
{ok, Value};
-
-validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
V = binary_to_lower(Value),
try
case binary_to_existing_atom(V, utf8) of
@@ -585,19 +558,16 @@ validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
-validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) ->
+validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
try
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
@@ -609,52 +579,44 @@ validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
-
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
case Value =< Max of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
case Value > ExclusiveMax of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
case Value >= Min of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
case Value =< ExclusiveMin of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
case size(Value) =< MaxLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
case size(Value) >= MinLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
-
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
{ok, MP} = re:compile(Pattern),
case re:run(Value, MP) of
{match, _} -> ok;
_ -> validation_error(Rule, Name)
end;
-
validate(Rule = schema, Name, Value, ValidatorState) ->
- Definition = list_to_binary("#/components/schemas/" ++ openapi_utils:to_list(Name)),
+ Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
@@ -674,18 +636,15 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
},
validation_error(Rule, Name, Info)
end;
-
validate(Rule, Name, _Value, _ValidatorState) ->
- error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]),
+ ?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
error({unknown_validation_rule, Rule}).
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
-
validation_error(ViolatedRule, Name) ->
validation_error(ViolatedRule, Name, #{}).
--spec validation_error(Rule :: any(), Name :: any(), Info :: #{}) -> no_return().
-
+-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
validation_error(ViolatedRule, Name, Info) ->
throw({wrong_param, Name, ViolatedRule, Info}).
@@ -700,31 +659,26 @@ get_value(body, _Name, Req0) ->
Value ->
{Value, Req}
end;
-
get_value(qs_val, Name, Req) ->
QS = cowboy_req:parse_qs(Req),
- Value = openapi_utils:get_opt(openapi_utils:to_qs(Name), QS),
+ Value = get_opt(to_qs(Name), QS),
{Value, Req};
-
get_value(header, Name, Req) ->
Headers = cowboy_req:headers(Req),
- Value = maps:get(openapi_utils:to_header(Name), Headers, undefined),
+ Value = maps:get(to_header(Name), Headers, undefined),
{Value, Req};
-
get_value(binding, Name, Req) ->
- Value = cowboy_req:binding(openapi_utils:to_binding(Name), Req),
+ Value = cowboy_req:binding(to_binding(Name), Req),
{Value, Req}.
+prepare_body(<<>>) ->
+ <<>>;
prepare_body(Body) ->
- case Body of
- <<"">> -> <<"">>;
- _ ->
- try
- jsx:decode(Body, [return_maps])
- catch
- error:_ ->
- {error, {invalid_body, not_json, Body}}
- end
+ try
+ json:decode(Body)
+ catch
+ error:_ ->
+ {error, {invalid_body, not_json, Body}}
end.
validate_with_schema(Body, Definition, ValidatorState) ->
@@ -752,5 +706,84 @@ prepare_param(Rules, Name, Value, ValidatorState) ->
{error, Reason}
end.
+-spec to_binary(iodata() | atom() | number()) -> binary().
+to_binary(V) when is_binary(V) -> V;
+to_binary(V) when is_list(V) -> iolist_to_binary(V);
+to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
+to_binary(V) when is_integer(V) -> integer_to_binary(V);
+to_binary(V) when is_float(V) -> float_to_binary(V).
+
+-spec to_list(iodata() | atom() | number()) -> binary().
+to_list(V) when is_list(V) -> V;
+to_list(V) when is_binary(V) -> binary_to_list(V);
+to_list(V) when is_atom(V) -> atom_to_list(V);
+to_list(V) when is_integer(V) -> integer_to_list(V);
+to_list(V) when is_float(V) -> float_to_list(V).
+
+-spec to_float(iodata()) -> float().
+to_float(V) ->
+ binary_to_float(iolist_to_binary([V])).
+
+-spec to_int(integer() | binary() | list()) -> integer().
+to_int(Data) when is_integer(Data) ->
+ Data;
+to_int(Data) when is_binary(Data) ->
+ binary_to_integer(Data);
+to_int(Data) when is_list(Data) ->
+ list_to_integer(Data).
+
+-spec to_header(iodata() | atom() | number()) -> binary().
+to_header(Name) ->
+ to_binary(string:lowercase(to_binary(Name))).
+
binary_to_lower(V) when is_binary(V) ->
- list_to_binary(string:to_lower(openapi_utils:to_list(V))).
+ string:lowercase(V).
+
+-spec to_qs(iodata() | atom() | number()) -> binary().
+to_qs(Name) ->
+ to_binary(Name).
+
+-spec to_binding(iodata() | atom() | number()) -> atom().
+to_binding(Name) ->
+ Prepared = to_binary(Name),
+ binary_to_existing_atom(Prepared, utf8).
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} -> Value;
+ false -> Default
+ end.
+
+get_openapi_path() ->
+ {ok, AppName} = application:get_application(?MODULE),
+ filename:join(priv_dir(AppName), "openapi.json").
+
+-include_lib("kernel/include/file.hrl").
+
+-spec priv_dir(Application :: atom()) -> file:name_all().
+priv_dir(AppName) ->
+ case code:priv_dir(AppName) of
+ Value when is_list(Value) ->
+ Value ++ "/";
+ _Error ->
+ select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
+ end.
+
+select_priv_dir(Paths) ->
+ case lists:dropwhile(fun test_priv_dir/1, Paths) of
+ [Path | _] -> Path;
+ _ -> exit(no_priv_dir)
+ end.
+
+test_priv_dir(Path) ->
+ case file:read_file_info(Path) of
+ {ok, #file_info{type = directory}} ->
+ false;
+ _ ->
+ true
+ end.
diff --git a/samples/server/petstore/erlang-server/src/openapi_auth.erl b/samples/server/petstore/erlang-server/src/openapi_auth.erl
index b84a4f98dc89..0e7beb1132c4 100644
--- a/samples/server/petstore/erlang-server/src/openapi_auth.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_auth.erl
@@ -2,47 +2,44 @@
-export([authorize_api_key/5]).
--spec authorize_api_key(
- LogicHandler :: atom(),
- OperationID :: openapi_api:operation_id(),
- From :: header | qs_val,
- KeyParam :: iodata() | atom(),
- Req ::cowboy_req:req()
-)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
- {false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
-
-authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
+-spec authorize_api_key(openapi_logic_handler:api_key_callback(),
+ openapi_api:operation_id(),
+ header | qs_val,
+ iodata() | atom(),
+ cowboy_req:req()) ->
+ {true, openapi_logic_handler:context(), cowboy_req:req()} |
+ {false, binary(), cowboy_req:req()}.
+authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
case ApiKey of
undefined ->
- AuthHeader = <<"">>,
+ AuthHeader = <<>>,
{false, AuthHeader, Req};
_ ->
- Result = openapi_logic_handler:authorize_api_key(
- LogicHandler,
- OperationID,
- ApiKey
- ),
- case Result of
- {true, Context} ->
+ case Handler(OperationID, ApiKey) of
+ {true, Context} ->
{true, Context, Req};
- false ->
- AuthHeader = <<"">>,
+ {false, AuthHeader} ->
{false, AuthHeader, Req}
end
end.
get_api_key(header, KeyParam, Req) ->
Headers = cowboy_req:headers(Req),
- {
- maps:get(
- openapi_utils:to_header(KeyParam),
- Headers,
- undefined
- ),
- Req
- };
-
+ {maps:get(KeyParam, Headers, undefined), Req};
get_api_key(qs_val, KeyParam, Req) ->
QS = cowboy_req:parse_qs(Req),
- { openapi_utils:get_opt(KeyParam, QS), Req}.
+ {get_opt(KeyParam, QS), Req}.
+
+-spec get_opt(any(), []) -> any().
+get_opt(Key, Opts) ->
+ get_opt(Key, Opts, undefined).
+
+-spec get_opt(any(), [], any()) -> any().
+get_opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} ->
+ Value;
+ false ->
+ Default
+ end.
diff --git a/samples/server/petstore/erlang-server/src/openapi_default_logic_handler.erl b/samples/server/petstore/erlang-server/src/openapi_default_logic_handler.erl
deleted file mode 100644
index da6e79a74ebd..000000000000
--- a/samples/server/petstore/erlang-server/src/openapi_default_logic_handler.erl
+++ /dev/null
@@ -1,24 +0,0 @@
--module(openapi_default_logic_handler).
-
--behaviour(openapi_logic_handler).
-
--export([handle_request/3]).
--export([authorize_api_key/2]).
-
--spec authorize_api_key(OperationID :: openapi_api:operation_id(), ApiKey :: binary()) -> {true, #{}}.
-
-authorize_api_key(_, _) -> {true, #{}}.
-
--spec handle_request(
- OperationID :: openapi_api:operation_id(),
- Req :: cowboy_req:req(),
- Context :: #{}
-) ->
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: jsx:json_term()}.
-
-handle_request(OperationID, Req, Context) ->
- error_logger:error_msg(
- "Got not implemented request to process: ~p~n",
- [{OperationID, Req, Context}]
- ),
- {501, #{}, #{}}.
diff --git a/samples/server/petstore/erlang-server/src/openapi_logic_handler.erl b/samples/server/petstore/erlang-server/src/openapi_logic_handler.erl
index 817c1a49a830..28d45ab65113 100644
--- a/samples/server/petstore/erlang-server/src/openapi_logic_handler.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_logic_handler.erl
@@ -1,36 +1,56 @@
-module(openapi_logic_handler).
--export([handle_request/4]).
+-include_lib("kernel/include/logger.hrl").
+
+-type api_key_callback() ::
+ fun((openapi_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
+-type accept_callback() ::
+ fun((atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}).
+-type provide_callback() ::
+ fun((atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}).
-type context() :: #{binary() => any()}.
--type handler_response() ::{
- Status :: cowboy:http_status(),
- Headers :: cowboy:http_headers(),
- Body :: jsx:json_term()}.
-
--export_type([handler_response/0]).
-
--callback authorize_api_key(
- OperationID :: openapi_api:operation_id(),
- ApiKey :: binary()
-) ->
- Result :: boolean() | {boolean(), context()}.
-
-
--callback handle_request(OperationID :: openapi_api:operation_id(), cowboy_req:req(), Context :: context()) ->
- handler_response().
-
--spec handle_request(
- Handler :: atom(),
- OperationID :: openapi_api:operation_id(),
- Request :: cowboy_req:req(),
- Context :: context()
-) ->
- handler_response().
-
-handle_request(Handler, OperationID, Req, Context) ->
- Handler:handle_request(OperationID, Req, Context).
-
--spec authorize_api_key(Handler :: atom(), OperationID :: openapi_api:operation_id(), ApiKey :: binary()) ->
- Result :: false | {true, context()}.
-authorize_api_key(Handler, OperationID, ApiKey) ->
- Handler:authorize_api_key(OperationID, ApiKey).
+
+-export_type([context/0, api_key_callback/0, accept_callback/0, provide_callback/0]).
+
+-optional_callbacks([api_key_callback/2]).
+
+-callback api_key_callback(openapi_api:operation_id(), binary()) ->
+ {true, context()} | {false, iodata()}.
+
+-callback accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+
+-callback provide_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+
+-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
+-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
+
+-spec api_key_callback(openapi_api:operation_id(), binary()) -> {true, #{}}.
+api_key_callback(OperationID, ApiKey) ->
+ ?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
+ operation_id => OperationID,
+ api_key => ApiKey}),
+ {true, #{}}.
+
+-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
+accept_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {501, #{}, #{}}.
+
+-spec provide_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), context()}.
+provide_callback(Class, OperationID, Req, Context) ->
+ ?LOG_ERROR(#{what => "Got not implemented request to process",
+ class => Class,
+ operation_id => OperationID,
+ request => Req,
+ context => Context}),
+ {<<>>, Req, Context}.
diff --git a/samples/server/petstore/erlang-server/src/openapi_pet_handler.erl b/samples/server/petstore/erlang-server/src/openapi_pet_handler.erl
index dc72a63c5d26..70edd6fb0010 100644
--- a/samples/server/petstore/erlang-server/src/openapi_pet_handler.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_pet_handler.erl
@@ -1,485 +1,251 @@
%% basic handler
-module(openapi_pet_handler).
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
%% Cowboy REST callbacks
--export([allowed_methods/2]).
-export([init/2]).
--export([allow_missing_post/2]).
+-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
--export([known_content_type/2]).
--export([malformed_request/2]).
-export([valid_content_headers/2]).
--export([valid_entity_length/2]).
-
-%% Handlers
--export([handle_request_json/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
--record(state, {
- operation_id :: openapi_api:operation_id(),
- logic_handler :: atom(),
- validator_state :: jesse_state:state(),
- context=#{} :: #{}
-}).
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
--type state() :: state().
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
--spec init(Req :: cowboy_req:req(), Opts :: openapi_router:init_opts()) ->
- {cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
+-type state() :: #state{}.
-init(Req, {Operations, LogicHandler, ValidatorMod}) ->
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
-
- ValidatorState = ValidatorMod:get_validator_state(),
-
- error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
-
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- },
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
{cowboy_rest, Req, State}.
--spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
-
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'AddPet'
- }
-) ->
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'AddPet'} = State) ->
{[<<"POST">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'DeletePet'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'DeletePet'} = State) ->
{[<<"DELETE">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'FindPetsByStatus'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'FindPetsByTags'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'GetPetById'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'GetPetById'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'UpdatePet'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'UpdatePet'} = State) ->
{[<<"PUT">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'UpdatePetWithForm'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
{[<<"POST">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'UploadFile'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'UploadFile'} = State) ->
{[<<"POST">>], Req, State};
-
allowed_methods(Req, State) ->
{[], Req, State}.
--spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: true | {false, AuthHeader :: iodata()},
- Req :: cowboy_req:req(),
- State :: state()
- }.
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'AddPet' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req0,
+ #state{operation_id = 'AddPet' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'DeletePet' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'DeletePet' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'FindPetsByStatus' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'FindPetsByStatus' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'FindPetsByTags' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'FindPetsByTags' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'GetPetById' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'GetPetById' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'UpdatePet' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'UpdatePet' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'UpdatePetWithForm' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'UpdatePetWithForm' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'UploadFile' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "Authorization",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'UploadFile' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-
--spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), AcceptResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
+ {true, Req, State}.
-content_types_accepted(Req, State) ->
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'AddPet'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'AddPet'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'DeletePet'
- }
-) ->
- Headers = ["api_key"],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'FindPetsByStatus'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'FindPetsByTags'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'GetPetById'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'UpdatePet'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'UpdatePetWithForm'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'UploadFile'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
+ {<<"application/json">>, handle_type_accepted},
+ {<<"application/xml">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'DeletePet'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'GetPetById'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'UpdatePet'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted},
+ {<<"application/xml">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
+ {[
+ {<<"application/x-www-form-urlencoded">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'UploadFile'} = State) ->
+ {[
+ {<<"multipart/form-data">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'AddPet'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'DeletePet'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'GetPetById'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'UpdatePet'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'UploadFile'} = State) ->
+ {true, Req, State};
valid_content_headers(Req, State) ->
{false, Req, State}.
--spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), ProvideResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
-
-content_types_provided(Req, State) ->
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'AddPet'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-malformed_request(Req, State) ->
- {false, Req, State}.
-
--spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-allow_missing_post(Req, State) ->
- {false, Req, State}.
-
--spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
- processed_response().
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'DeletePet'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'GetPetById'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'UpdatePet'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'UploadFile'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
- handle_request_json(Req, State).
-
--spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-known_content_type(Req, State) ->
- {true, Req, State}.
-
--spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-valid_entity_length(Req, State) ->
- %% @TODO check the length
- {true, Req, State}.
-
-%%%%
--type result_ok() :: {
- ok,
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
-}.
-
--type result_error() :: {error, Reason :: any()}.
-
--type processed_response() :: {stop, cowboy_req:req(), state()}.
-
--spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
- processed_response().
-
-process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
- case Response of
- {ok, {Code, Headers, Body}} ->
- Req = cowboy_req:reply(Code, Headers, Body, Req0),
- {stop, Req, State};
- {error, Message} ->
- error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
-
- Req = cowboy_req:reply(400, Req0),
- {stop, Req, State}
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
end.
--spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
-
-handle_request_json(
- Req0,
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- }
-) ->
- case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
- {ok, Populated, Req1} ->
- {Code, Headers, Body} = openapi_logic_handler:handle_request(
- LogicHandler,
- OperationID,
- Req1,
- maps:merge(State#state.context, Populated)
- ),
- _ = openapi_api:validate_response(
- OperationID,
- Code,
- Body,
- ValidatorState
- ),
- PreparedBody = prepare_body(Code, Body),
- Response = {ok, {Code, Headers, PreparedBody}},
- process_response(Response, Req1, State);
- {error, Reason, Req1} ->
- process_response({error, Reason}, Req1, State)
- end.
-
-validate_headers(_, Req) -> {true, Req}.
-
-prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(_Code, Body) ->
- jsx:encode(Body).
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(pet, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(pet, OperationID, Req, State#state.context).
diff --git a/samples/server/petstore/erlang-server/src/openapi_router.erl b/samples/server/petstore/erlang-server/src/openapi_router.erl
index c32f2e5ba1d6..618d2024794b 100644
--- a/samples/server/petstore/erlang-server/src/openapi_router.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_router.erl
@@ -1,57 +1,36 @@
-module(openapi_router).
--export([get_paths/1, get_validator_state/0]).
+-export([get_paths/1]).
--type operations() :: #{
- Method :: binary() => openapi_api:operation_id()
-}.
-
--type init_opts() :: {
- Operations :: operations(),
- LogicHandler :: atom(),
- ValidatorMod :: module()
-}.
+-type method() :: binary().
+-type operations() :: #{method() => openapi_api:operation_id()}.
+-type init_opts() :: {operations(), module()}.
-export_type([init_opts/0]).
--spec get_paths(LogicHandler :: atom()) -> [{'_',[{
- Path :: string(),
- Handler :: atom(),
- InitOpts :: init_opts()
-}]}].
-
+-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
get_paths(LogicHandler) ->
- ValidatorState = prepare_validator(),
PreparedPaths = maps:fold(
- fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
- [{Path, Handler, Operations} | Acc]
- end,
- [],
- group_paths()
- ),
- [
- {'_',
- [{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
- }
- ].
+ fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
+ [{Path, Handler, Operations} | Acc]
+ end, [], group_paths()
+ ),
+ [{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
group_paths() ->
maps:fold(
- fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
- case maps:find(Path, Acc) of
- {ok, PathInfo0 = #{operations := Operations0}} ->
- Operations = Operations0#{Method => OperationID},
- PathInfo = PathInfo0#{operations => Operations},
- Acc#{Path => PathInfo};
- error ->
- Operations = #{Method => OperationID},
- PathInfo = #{handler => Handler, operations => Operations},
- Acc#{Path => PathInfo}
- end
- end,
- #{},
- get_operations()
- ).
+ fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
+ case maps:find(Path, Acc) of
+ {ok, PathInfo0 = #{operations := Operations0}} ->
+ Operations = Operations0#{Method => OperationID},
+ PathInfo = PathInfo0#{operations => Operations},
+ Acc#{Path => PathInfo};
+ error ->
+ Operations = #{Method => OperationID},
+ PathInfo = #{handler => Handler, operations => Operations},
+ Acc#{Path => PathInfo}
+ end
+ end, #{}, get_operations()).
get_operations() ->
#{
@@ -156,18 +135,3 @@ get_operations() ->
handler => 'openapi_user_handler'
}
}.
-
-get_validator_state() ->
- persistent_term:get({?MODULE, validator_state}).
-
-
-prepare_validator() ->
- R = jsx:decode(element(2, file:read_file(get_openapi_path()))),
- JesseState = jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]),
- persistent_term:put({?MODULE, validator_state}, JesseState),
- ?MODULE.
-
-
-get_openapi_path() ->
- {ok, AppName} = application:get_application(?MODULE),
- filename:join(openapi_utils:priv_dir(AppName), "openapi.json").
diff --git a/samples/server/petstore/erlang-server/src/openapi_server.erl b/samples/server/petstore/erlang-server/src/openapi_server.erl
index 02500173821f..0cd992fe69d2 100644
--- a/samples/server/petstore/erlang-server/src/openapi_server.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_server.erl
@@ -1,26 +1,21 @@
-module(openapi_server).
-
--define(DEFAULT_LOGIC_HANDLER, openapi_default_logic_handler).
+-define(DEFAULT_LOGIC_HANDLER, openapi_logic_handler).
-export([start/2]).
-
--spec start( ID :: any(), #{
- ip => inet:ip_address(),
- port => inet:port_number(),
- logic_handler => module(),
- net_opts => []
-}) -> {ok, pid()} | {error, any()}.
-
-start(ID, #{
- ip := IP ,
- port := Port,
- net_opts := NetOpts
-} = Params) ->
- {Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
+-ignore_xref([start/2]).
+
+-spec start(term(), #{transport => tcp | ssl,
+ transport_opts => ranch:opts(),
+ protocol_opts => cowboy:opts(),
+ logic_handler => module()}) ->
+ {ok, pid()} | {error, any()}.
+start(ID, Params) ->
+ Transport = maps:get(transport, Params, tcp),
+ TransportOpts = maps:get(transport_opts, Params, #{}),
+ ProtocolOpts = maps:get(procotol_opts, Params, #{}),
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
- ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
- CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
+ CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
case Transport of
ssl ->
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
@@ -28,33 +23,17 @@ start(ID, #{
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
end.
-get_socket_transport(IP, Port, Options) ->
- Opts = [
- {ip, IP},
- {port, Port}
- ],
- case openapi_utils:get_opt(ssl, Options) of
- SslOpts = [_|_] ->
- {ssl, Opts ++ SslOpts};
- undefined ->
- {tcp, Opts}
- end.
-
get_cowboy_config(LogicHandler, ExtraOpts) ->
- get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
-
-get_cowboy_config(_LogicHandler, [], Opts) ->
- Opts;
+ DefaultOpts = get_default_opts(LogicHandler),
+ maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
-get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
- NewEnv = case proplists:get_value(dispatch, Env) of
- undefined -> [get_default_dispatch(LogicHandler) | Env];
- _ -> Env
- end,
- get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts));
-
-get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) ->
- get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)).
+get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
+ maps:put(env, Env, AccIn);
+get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
+ Env = maps:merge(OldEnv, NewEnv),
+ maps:put(env, Env, AccIn);
+get_cowboy_config(Key, Value, AccIn) ->
+ maps:put(Key, Value, AccIn).
get_default_dispatch(LogicHandler) ->
Paths = openapi_router:get_paths(LogicHandler),
@@ -62,6 +41,3 @@ get_default_dispatch(LogicHandler) ->
get_default_opts(LogicHandler) ->
#{env => get_default_dispatch(LogicHandler)}.
-
-store_key(Key, Value, Opts) ->
- maps:put(Key, Value, Opts).
diff --git a/samples/server/petstore/erlang-server/src/openapi_store_handler.erl b/samples/server/petstore/erlang-server/src/openapi_store_handler.erl
index 09245deb509b..89952c4cc04e 100644
--- a/samples/server/petstore/erlang-server/src/openapi_store_handler.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_store_handler.erl
@@ -1,280 +1,139 @@
%% basic handler
-module(openapi_store_handler).
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
%% Cowboy REST callbacks
--export([allowed_methods/2]).
-export([init/2]).
--export([allow_missing_post/2]).
+-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
--export([known_content_type/2]).
--export([malformed_request/2]).
-export([valid_content_headers/2]).
--export([valid_entity_length/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
-%% Handlers
--export([handle_request_json/2]).
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
--record(state, {
- operation_id :: openapi_api:operation_id(),
- logic_handler :: atom(),
- validator_state :: jesse_state:state(),
- context=#{} :: #{}
-}).
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
--type state() :: state().
+-type state() :: #state{}.
--spec init(Req :: cowboy_req:req(), Opts :: openapi_router:init_opts()) ->
- {cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
-
-init(Req, {Operations, LogicHandler, ValidatorMod}) ->
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
-
- ValidatorState = ValidatorMod:get_validator_state(),
-
- error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
-
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- },
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
{cowboy_rest, Req, State}.
--spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
-
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'DeleteOrder'
- }
-) ->
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'DeleteOrder'} = State) ->
{[<<"DELETE">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'GetInventory'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'GetInventory'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'GetOrderById'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'GetOrderById'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'PlaceOrder'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'PlaceOrder'} = State) ->
{[<<"POST">>], Req, State};
-
allowed_methods(Req, State) ->
{[], Req, State}.
--spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: true | {false, AuthHeader :: iodata()},
- Req :: cowboy_req:req(),
- State :: state()
- }.
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'GetInventory' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req0,
+ #state{operation_id = 'GetInventory' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-
--spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), AcceptResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
+ {true, Req, State}.
-content_types_accepted(Req, State) ->
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'DeleteOrder'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'GetInventory'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'GetOrderById'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'PlaceOrder'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'DeleteOrder'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'GetInventory'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'GetOrderById'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'PlaceOrder'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'DeleteOrder'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'GetInventory'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'GetOrderById'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'PlaceOrder'} = State) ->
+ {true, Req, State};
valid_content_headers(Req, State) ->
{false, Req, State}.
--spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), ProvideResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
-
-content_types_provided(Req, State) ->
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'DeleteOrder'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'GetInventory'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-malformed_request(Req, State) ->
- {false, Req, State}.
-
--spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-allow_missing_post(Req, State) ->
- {false, Req, State}.
-
--spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
- processed_response().
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'GetOrderById'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'PlaceOrder'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
- handle_request_json(Req, State).
-
--spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-known_content_type(Req, State) ->
- {true, Req, State}.
-
--spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-valid_entity_length(Req, State) ->
- %% @TODO check the length
- {true, Req, State}.
-
-%%%%
--type result_ok() :: {
- ok,
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
-}.
-
--type result_error() :: {error, Reason :: any()}.
-
--type processed_response() :: {stop, cowboy_req:req(), state()}.
-
--spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
- processed_response().
-
-process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
- case Response of
- {ok, {Code, Headers, Body}} ->
- Req = cowboy_req:reply(Code, Headers, Body, Req0),
- {stop, Req, State};
- {error, Message} ->
- error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
-
- Req = cowboy_req:reply(400, Req0),
- {stop, Req, State}
- end.
-
--spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
-
-handle_request_json(
- Req0,
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- }
-) ->
- case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
- {ok, Populated, Req1} ->
- {Code, Headers, Body} = openapi_logic_handler:handle_request(
- LogicHandler,
- OperationID,
- Req1,
- maps:merge(State#state.context, Populated)
- ),
- _ = openapi_api:validate_response(
- OperationID,
- Code,
- Body,
- ValidatorState
- ),
- PreparedBody = prepare_body(Code, Body),
- Response = {ok, {Code, Headers, PreparedBody}},
- process_response(Response, Req1, State);
- {error, Reason, Req1} ->
- process_response({error, Reason}, Req1, State)
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
end.
-validate_headers(_, Req) -> {true, Req}.
-
-prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(_Code, Body) ->
- jsx:encode(Body).
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(store, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(store, OperationID, Req, State#state.context).
diff --git a/samples/server/petstore/erlang-server/src/openapi_user_handler.erl b/samples/server/petstore/erlang-server/src/openapi_user_handler.erl
index aad43103ac66..3fedf1c9fd56 100644
--- a/samples/server/petstore/erlang-server/src/openapi_user_handler.erl
+++ b/samples/server/petstore/erlang-server/src/openapi_user_handler.erl
@@ -1,447 +1,220 @@
%% basic handler
-module(openapi_user_handler).
+-behaviour(cowboy_rest).
+
+-include_lib("kernel/include/logger.hrl").
+
%% Cowboy REST callbacks
--export([allowed_methods/2]).
-export([init/2]).
--export([allow_missing_post/2]).
+-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
--export([known_content_type/2]).
--export([malformed_request/2]).
-export([valid_content_headers/2]).
--export([valid_entity_length/2]).
+-export([handle_type_accepted/2, handle_type_provided/2]).
-%% Handlers
--export([handle_request_json/2]).
+-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
--record(state, {
- operation_id :: openapi_api:operation_id(),
- logic_handler :: atom(),
- validator_state :: jesse_state:state(),
- context=#{} :: #{}
-}).
+-record(state,
+ {operation_id :: openapi_api:operation_id(),
+ accept_callback :: openapi_logic_handler:accept_callback(),
+ provide_callback :: openapi_logic_handler:provide_callback(),
+ api_key_handler :: openapi_logic_handler:api_key_callback(),
+ context = #{} :: openapi_logic_handler:context()}).
--type state() :: state().
+-type state() :: #state{}.
--spec init(Req :: cowboy_req:req(), Opts :: openapi_router:init_opts()) ->
- {cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
-
-init(Req, {Operations, LogicHandler, ValidatorMod}) ->
+-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
+ {cowboy_rest, cowboy_req:req(), state()}.
+init(Req, {Operations, Module}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
-
- ValidatorState = ValidatorMod:get_validator_state(),
-
- error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
-
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- },
+ ?LOG_INFO(#{what => "Attempt to process operation",
+ method => Method,
+ operation_id => OperationID}),
+ State = #state{operation_id = OperationID,
+ accept_callback = fun Module:accept_callback/4,
+ provide_callback = fun Module:provide_callback/4,
+ api_key_handler = fun Module:authorize_api_key/2},
{cowboy_rest, Req, State}.
--spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
-
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'CreateUser'
- }
-) ->
+-spec allowed_methods(cowboy_req:req(), state()) ->
+ {[binary()], cowboy_req:req(), state()}.
+allowed_methods(Req, #state{operation_id = 'CreateUser'} = State) ->
{[<<"POST">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'CreateUsersWithArrayInput'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
{[<<"POST">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'CreateUsersWithListInput'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
{[<<"POST">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'DeleteUser'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'DeleteUser'} = State) ->
{[<<"DELETE">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'GetUserByName'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'GetUserByName'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'LoginUser'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'LoginUser'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'LogoutUser'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'LogoutUser'} = State) ->
{[<<"GET">>], Req, State};
-
-allowed_methods(
- Req,
- State = #state{
- operation_id = 'UpdateUser'
- }
-) ->
+allowed_methods(Req, #state{operation_id = 'UpdateUser'} = State) ->
{[<<"PUT">>], Req, State};
-
allowed_methods(Req, State) ->
{[], Req, State}.
--spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: true | {false, AuthHeader :: iodata()},
- Req :: cowboy_req:req(),
- State :: state()
- }.
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'CreateUser' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+-spec is_authorized(cowboy_req:req(), state()) ->
+ {true | {false, iodata()}, cowboy_req:req(), state()}.
+is_authorized(Req0,
+ #state{operation_id = 'CreateUser' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'CreateUsersWithArrayInput' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'CreateUsersWithArrayInput' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'CreateUsersWithListInput' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'CreateUsersWithListInput' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'DeleteUser' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'DeleteUser' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'LogoutUser' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'LogoutUser' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
-is_authorized(
- Req0,
- State = #state{
- operation_id = 'UpdateUser' = OperationID,
- logic_handler = LogicHandler
- }
-) ->
- From = header,
- Result = openapi_auth:authorize_api_key(
- LogicHandler,
- OperationID,
- From,
- "api_key",
- Req0
- ),
- case Result of
- {true, Context, Req} -> {true, Req, State#state{context = Context}};
- {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
+is_authorized(Req0,
+ #state{operation_id = 'UpdateUser' = OperationID,
+ api_key_handler = Handler} = State) ->
+ case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
+ {true, Context, Req} ->
+ {true, Req, State#state{context = Context}};
+ {false, AuthHeader, Req} ->
+ {{false, AuthHeader}, Req, State}
end;
is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-is_authorized(Req, State) ->
- {{false, <<"">>}, Req, State}.
-
--spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), AcceptResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
+ {true, Req, State}.
-content_types_accepted(Req, State) ->
+-spec content_types_accepted(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_accepted(Req, #state{operation_id = 'CreateUser'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'CreateUser'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'CreateUsersWithArrayInput'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'CreateUsersWithListInput'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'DeleteUser'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'GetUserByName'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'LoginUser'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'LogoutUser'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
-
-valid_content_headers(
- Req0,
- State = #state{
- operation_id = 'UpdateUser'
- }
-) ->
- Headers = [],
- {Result, Req} = validate_headers(Headers, Req0),
- {Result, Req, State};
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, #state{operation_id = 'DeleteUser'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'GetUserByName'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'LoginUser'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'LogoutUser'} = State) ->
+ {[], Req, State};
+content_types_accepted(Req, #state{operation_id = 'UpdateUser'} = State) ->
+ {[
+ {<<"application/json">>, handle_type_accepted}
+ ], Req, State};
+content_types_accepted(Req, State) ->
+ {[], Req, State}.
+-spec valid_content_headers(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
+valid_content_headers(Req, #state{operation_id = 'CreateUser'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'DeleteUser'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'GetUserByName'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'LoginUser'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'LogoutUser'} = State) ->
+ {true, Req, State};
+valid_content_headers(Req, #state{operation_id = 'UpdateUser'} = State) ->
+ {true, Req, State};
valid_content_headers(Req, State) ->
{false, Req, State}.
--spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
- {
- Value :: [{binary(), ProvideResource :: atom()}],
- Req :: cowboy_req:req(),
- State :: state()
- }.
-
-content_types_provided(Req, State) ->
+-spec content_types_provided(cowboy_req:req(), state()) ->
+ {[{binary(), atom()}], cowboy_req:req(), state()}.
+content_types_provided(Req, #state{operation_id = 'CreateUser'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'DeleteUser'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'GetUserByName'} = State) ->
{[
- {<<"application/json">>, handle_request_json}
- ], Req, State}.
-
--spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-malformed_request(Req, State) ->
- {false, Req, State}.
-
--spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: false, Req :: cowboy_req:req(), State :: state()}.
-
-allow_missing_post(Req, State) ->
- {false, Req, State}.
-
--spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
- processed_response().
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'LoginUser'} = State) ->
+ {[
+ {<<"application/xml">>, handle_type_provided},
+ {<<"application/json">>, handle_type_provided}
+ ], Req, State};
+content_types_provided(Req, #state{operation_id = 'LogoutUser'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, #state{operation_id = 'UpdateUser'} = State) ->
+ {[], Req, State};
+content_types_provided(Req, State) ->
+ {[], Req, State}.
+-spec delete_resource(cowboy_req:req(), state()) ->
+ {boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
- handle_request_json(Req, State).
-
--spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-known_content_type(Req, State) ->
- {true, Req, State}.
-
--spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
- {Value :: true, Req :: cowboy_req:req(), State :: state()}.
-
-valid_entity_length(Req, State) ->
- %% @TODO check the length
- {true, Req, State}.
-
-%%%%
--type result_ok() :: {
- ok,
- {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
-}.
-
--type result_error() :: {error, Reason :: any()}.
-
--type processed_response() :: {stop, cowboy_req:req(), state()}.
-
--spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
- processed_response().
-
-process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
- case Response of
- {ok, {Code, Headers, Body}} ->
- Req = cowboy_req:reply(Code, Headers, Body, Req0),
- {stop, Req, State};
- {error, Message} ->
- error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
-
- Req = cowboy_req:reply(400, Req0),
- {stop, Req, State}
+ case handle_type_accepted(Req, State) of
+ true ->
+ {true, Req, State};
+ _ ->
+ {false, Req, State}
end.
--spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
-
-handle_request_json(
- Req0,
- State = #state{
- operation_id = OperationID,
- logic_handler = LogicHandler,
- validator_state = ValidatorState
- }
-) ->
- case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
- {ok, Populated, Req1} ->
- {Code, Headers, Body} = openapi_logic_handler:handle_request(
- LogicHandler,
- OperationID,
- Req1,
- maps:merge(State#state.context, Populated)
- ),
- _ = openapi_api:validate_response(
- OperationID,
- Code,
- Body,
- ValidatorState
- ),
- PreparedBody = prepare_body(Code, Body),
- Response = {ok, {Code, Headers, PreparedBody}},
- process_response(Response, Req1, State);
- {error, Reason, Req1} ->
- process_response({error, Reason}, Req1, State)
- end.
-
-validate_headers(_, Req) -> {true, Req}.
-
-prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
- <<>>;
-prepare_body(_Code, Body) ->
- jsx:encode(Body).
+-spec handle_type_accepted(cowboy_req:req(), state()) ->
+ boolean() | {created, iodata()} | {see_other, iodata()}.
+handle_type_accepted(Req, #state{operation_id = OperationID,
+ accept_callback = Handler} = State) ->
+ Handler(user, OperationID, Req, State#state.context).
+
+-spec handle_type_provided(cowboy_req:req(), state()) ->
+ {cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
+handle_type_provided(Req, #state{operation_id = OperationID,
+ provide_callback = Handler} = State) ->
+ Handler(user, OperationID, Req, State#state.context).
diff --git a/samples/server/petstore/erlang-server/src/openapi_utils.erl b/samples/server/petstore/erlang-server/src/openapi_utils.erl
deleted file mode 100644
index 58eee3a48e0f..000000000000
--- a/samples/server/petstore/erlang-server/src/openapi_utils.erl
+++ /dev/null
@@ -1,173 +0,0 @@
--module(openapi_utils).
-
--export([to_binary/1]).
--export([to_list/1]).
--export([to_float/1]).
--export([to_int/1]).
--export([to_lower/1]).
--export([to_upper/1]).
--export([set_resp_headers/2]).
--export([to_header/1]).
--export([to_qs/1]).
--export([to_binding/1]).
--export([get_opt/2]).
--export([get_opt/3]).
--export([priv_dir/0]).
--export([priv_dir/1]).
--export([priv_path/1]).
-
-
--spec to_binary(iodata() | atom() | number()) -> binary().
-
-to_binary(V) when is_binary(V) -> V;
-to_binary(V) when is_list(V) -> iolist_to_binary(V);
-to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
-to_binary(V) when is_integer(V) -> integer_to_binary(V);
-to_binary(V) when is_float(V) -> float_to_binary(V).
-
--spec to_list(iodata() | atom() | number()) -> string().
-
-to_list(V) when is_list(V) -> V;
-to_list(V) -> binary_to_list(to_binary(V)).
-
--spec to_float(iodata()) -> number().
-
-to_float(V) ->
- Data = iolist_to_binary([V]),
- case binary:split(Data, <<$.>>) of
- [Data] ->
- binary_to_integer(Data);
- [<<>>, _] ->
- binary_to_float(<<$0, Data/binary>>);
- _ ->
- binary_to_float(Data)
- end.
-
-%%
-
--spec to_int(integer() | binary() | list()) -> integer().
-
-to_int(Data) when is_integer(Data) ->
- Data;
-to_int(Data) when is_binary(Data) ->
- binary_to_integer(Data);
-to_int(Data) when is_list(Data) ->
- list_to_integer(Data).
-
--spec set_resp_headers([{binary(), iodata()}], cowboy_req:req()) -> cowboy_req:req().
-
-set_resp_headers([], Req) ->
- Req;
-set_resp_headers([{K, V} | T], Req0) ->
- Req = cowboy_req:set_resp_header(K, V, Req0),
- set_resp_headers(T, Req).
-
--spec to_header(iodata() | atom() | number()) -> binary().
-
-to_header(Name) ->
- Prepared = to_binary(Name),
- to_lower(Prepared).
-
--spec to_qs(iodata() | atom() | number()) -> binary().
-
-to_qs(Name) ->
- to_binary(Name).
-
--spec to_binding(iodata() | atom() | number()) -> atom().
-
-to_binding(Name) ->
- Prepared = to_binary(Name),
- binary_to_atom(Prepared, utf8).
-
--spec get_opt(any(), []) -> any().
-
-get_opt(Key, Opts) ->
- get_opt(Key, Opts, undefined).
-
--spec get_opt(any(), [], any()) -> any().
-
-get_opt(Key, Opts, Default) ->
- case lists:keyfind(Key, 1, Opts) of
- {_, Value} -> Value;
- false -> Default
- end.
-
--spec priv_dir() -> file:filename().
-
-priv_dir() ->
- {ok, AppName} = application:get_application(),
- priv_dir(AppName).
-
--spec priv_dir(Application :: atom()) -> file:filename().
-
-priv_dir(AppName) ->
- case code:priv_dir(AppName) of
- Value when is_list(Value) ->
- Value ++ "/";
- _Error ->
- select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
- end.
-
--spec priv_path(Relative :: file:filename()) -> file:filename().
-
-priv_path(Relative) ->
- filename:join(priv_dir(), Relative).
-
--include_lib("kernel/include/file.hrl").
-
-select_priv_dir(Paths) ->
- case lists:dropwhile(fun test_priv_dir/1, Paths) of
- [Path | _] -> Path;
- _ -> exit(no_priv_dir)
- end.
-
-test_priv_dir(Path) ->
- case file:read_file_info(Path) of
- {ok, #file_info{type = directory}} ->
- false;
- _ ->
- true
- end.
-
-
-%%
-
--spec to_lower(binary()) -> binary().
-
-to_lower(S) ->
- to_case(lower, S, <<>>).
-
--spec to_upper(binary()) -> binary().
-
-to_upper(S) ->
- to_case(upper, S, <<>>).
-
-to_case(_Case, <<>>, Acc) ->
- Acc;
-
-to_case(_Case, <>, _Acc) when C > 127 ->
- error(badarg);
-
-to_case(Case = lower, <>, Acc) ->
- to_case(Case, Rest, <>);
-
-to_case(Case = upper, <>, Acc) ->
- to_case(Case, Rest, <>).
-
-to_lower_char(C) when is_integer(C), $A =< C, C =< $Z ->
- C + 32;
-to_lower_char(C) when is_integer(C), 16#C0 =< C, C =< 16#D6 ->
- C + 32;
-to_lower_char(C) when is_integer(C), 16#D8 =< C, C =< 16#DE ->
- C + 32;
-to_lower_char(C) ->
- C.
-
-to_upper_char(C) when is_integer(C), $a =< C, C =< $z ->
- C - 32;
-to_upper_char(C) when is_integer(C), 16#E0 =< C, C =< 16#F6 ->
- C - 32;
-to_upper_char(C) when is_integer(C), 16#F8 =< C, C =< 16#FE ->
- C - 32;
-to_upper_char(C) ->
- C.