forked from loafle/openapi-generator-original
Erlang server overhaul (#19465)
* Upgrade erlang-server code generation and fix is_authorized crashes * Introduce structured logging * Improve general formatting * Update generated files * Enable erlang server on CI * Add echo-server testing to CI * Require OTP27 explicitly in the generated rebar.config file * Rework handler and API With this work, json validation becomes optional, fully implemented in the `_api` module as it was before, but without being forcibly called by the `_handler`. It is instead left as optional for the user to take advantage of the exposed callbacks. Jesse also chooses draft-06 as a default, but these can be chosen manually by the user too, as long as jesse implements them. `_handler` also becomes lighter, it now handles all mime types transparently by forwarding to a user-given module that must implement `accept_callback/4` and `provide_callback/4` as described in the `_logic_handler` callbacks. These will simply be the return values of cowboy_rest's `content_types_accepted` and `content_types_provided` respectively, and should simply comply with their defined APIs. They only get two parameters extending the behaviour, so that the user-given callback can pattern-match on them: the path prefix of the logic handler, and the operationID of the call. * Fix return types for provide_callbacks * Upgrade jesse to incur no dependencies The less dependencies the built code requires the better. * Fix dialyzer errors in the generated code * Apply stronger dialyzer checks
This commit is contained in:
parent
a98f45b4ac
commit
596d446f54
18
.github/workflows/samples-erlang.yaml
vendored
18
.github/workflows/samples-erlang.yaml
vendored
@ -3,33 +3,33 @@ name: Samples Erlang
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
# comment out due to errors
|
- samples/server/echo_api/erlang-server/**
|
||||||
# ===> Compiling src/openapi_pet_handler.erl failed
|
- samples/server/petstore/erlang-server/**
|
||||||
# src/openapi_pet_handler.erl:278: function is_authorized/2 already defined
|
|
||||||
#- samples/server/petstore/erlang-server/**
|
|
||||||
- samples/client/petstore/erlang-client/**
|
- samples/client/petstore/erlang-client/**
|
||||||
- samples/client/petstore/erlang-proper/**
|
- samples/client/petstore/erlang-proper/**
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
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-client/**
|
||||||
- samples/client/petstore/erlang-proper/**
|
- samples/client/petstore/erlang-proper/**
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Erlang projects
|
name: Build Erlang projects
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
sample:
|
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-client/
|
||||||
- samples/client/petstore/erlang-proper/
|
- samples/client/petstore/erlang-proper/
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: erlef/setup-beam@v1
|
- uses: erlef/setup-beam@v1
|
||||||
with:
|
with:
|
||||||
otp-version: '22.2'
|
otp-version: '27'
|
||||||
rebar3-version: '3.14.3'
|
rebar3-version: '3.23.0'
|
||||||
- run: rebar3 compile
|
- run: rebar3 compile
|
||||||
working-directory: ${{ matrix.sample }}
|
working-directory: ${{ matrix.sample }}
|
||||||
|
4
bin/configs/erlang-server-echo.yaml
Normal file
4
bin/configs/erlang-server-echo.yaml
Normal file
@ -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
|
@ -59,6 +59,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
<li>fun</li>
|
<li>fun</li>
|
||||||
<li>if</li>
|
<li>if</li>
|
||||||
<li>let</li>
|
<li>let</li>
|
||||||
|
<li>maybe</li>
|
||||||
<li>not</li>
|
<li>not</li>
|
||||||
<li>of</li>
|
<li>of</li>
|
||||||
<li>or</li>
|
<li>or</li>
|
||||||
|
@ -99,9 +99,9 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
|
|||||||
*/
|
*/
|
||||||
setReservedWordsLowerCase(
|
setReservedWordsLowerCase(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
|
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor",
|
||||||
"catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive",
|
"case", "catch", "cond", "div", "end", "fun", "if", "let", "maybe", "not",
|
||||||
"rem", "try", "when", "xor"
|
"of", "or", "orelse", "receive", "rem", "try", "when", "xor"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -172,10 +172,8 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
|
|||||||
supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl")));
|
supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl")));
|
||||||
supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl")));
|
supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl")));
|
||||||
supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "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("auth.mustache", "", toSourceFilePath("auth", "erl")));
|
||||||
supportingFiles.add(new SupportingFile("openapi.mustache", "", toPrivFilePath(this.openApiSpecName, "json")));
|
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("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl")));
|
||||||
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
|
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
|
||||||
.doNotOverwrite());
|
.doNotOverwrite());
|
||||||
@ -223,7 +221,7 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
|
|||||||
@Override
|
@Override
|
||||||
public String toApiName(String name) {
|
public String toApiName(String name) {
|
||||||
if (name.length() == 0) {
|
if (name.length() == 0) {
|
||||||
return this.packageName + "_default_handler";
|
return this.packageName + "_handler";
|
||||||
}
|
}
|
||||||
return this.packageName + "_" + underscore(name) + "_handler";
|
return this.packageName + "_" + underscore(name) + "_handler";
|
||||||
}
|
}
|
||||||
|
@ -4,54 +4,33 @@
|
|||||||
|
|
||||||
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
|
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
|
## Prerequisites
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
Use erlang-server with erlang.mk
|
Use erlang-server with rebar3
|
||||||
|
|
||||||
1, Create an application by using erlang.mk
|
1, Create an application by using rebar3
|
||||||
$ mkdir http_server
|
$ rebar3 new app 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:
|
2, Generate erlang-server project using openapi-generator
|
||||||
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
|
|
||||||
https://github.com/OpenAPITools/openapi-generator#2---getting-started
|
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
|
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=>[]})
|
openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
|
||||||
2, Compilation http_server project
|
2, Compile your http_server project
|
||||||
$ make
|
$ rebar3 compile
|
||||||
3, Start erlang virtual machine
|
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
|
4, Start project
|
||||||
application:ensure_all_started(http_server).
|
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.
|
||||||
|
@ -1,37 +1,60 @@
|
|||||||
-module({{packageName}}_api).
|
-module({{packageName}}_api).
|
||||||
|
-moduledoc """
|
||||||
|
This module offers an API for JSON schema validation, using `jesse` under the hood.
|
||||||
|
|
||||||
-export([request_params/1]).
|
If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
|
||||||
-export([request_param_info/2]).
|
and request and response can be validated using `populate_request/3`
|
||||||
-export([populate_request/3]).
|
and `validate_response/4` respectively.
|
||||||
-export([validate_response/4]).
|
|
||||||
%% exported to silence openapi complains
|
For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
|
||||||
-export([get_value/3, validate_response_body/4]).
|
```
|
||||||
|
-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 operation_id() :: atom().
|
||||||
-type request_param() :: atom().
|
-type request_param() :: atom().
|
||||||
|
|
||||||
-export_type([operation_id/0]).
|
-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]}).
|
||||||
{{#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).
|
|
||||||
|
|
||||||
-type rule() ::
|
-type rule() ::
|
||||||
{type, 'binary'} |
|
{type, binary} |
|
||||||
{type, 'integer'} |
|
{type, integer} |
|
||||||
{type, 'float'} |
|
{type, float} |
|
||||||
{type, 'binary'} |
|
{type, boolean} |
|
||||||
{type, 'boolean'} |
|
{type, date} |
|
||||||
{type, 'date'} |
|
{type, datetime} |
|
||||||
{type, 'datetime'} |
|
|
||||||
{enum, [atom()]} |
|
{enum, [atom()]} |
|
||||||
{max, Max :: number()} |
|
{max, Max :: number()} |
|
||||||
{exclusive_max, Max :: number()} |
|
{exclusive_max, Max :: number()} |
|
||||||
@ -44,59 +67,99 @@ request_params(_) ->
|
|||||||
required |
|
required |
|
||||||
not_required.
|
not_required.
|
||||||
|
|
||||||
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{
|
-doc #{equiv => prepare_validator/2}.
|
||||||
source => qs_val | binding | header | body,
|
-spec prepare_validator() -> jesse_state:state().
|
||||||
rules => [rule()]
|
prepare_validator() ->
|
||||||
}.
|
prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
|
||||||
|
|
||||||
{{#apiInfo}}{{#apis}}
|
-doc #{equiv => prepare_validator/2}.
|
||||||
{{#operations}}{{#operation}}{{#allParams}}
|
-spec prepare_validator(binary()) -> jesse_state:state().
|
||||||
request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
|
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.
|
||||||
|
|
||||||
|
%%%
|
||||||
|
-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}}
|
rules => [{{#isString}}
|
||||||
{type, 'binary'},{{/isString}}{{#isInteger}}
|
{type, binary},{{/isString}}{{#isInteger}}
|
||||||
{type, 'integer'},{{/isInteger}}{{#isLong}}
|
{type, integer},{{/isInteger}}{{#isLong}}
|
||||||
{type, 'integer'},{{/isLong}}{{#isFloat}}
|
{type, integer},{{/isLong}}{{#isFloat}}
|
||||||
{type, 'float'},{{/isFloat}}{{#isDouble}}
|
{type, float},{{/isFloat}}{{#isDouble}}
|
||||||
{type, 'float'},{{/isDouble}}{{#isByteArray}}
|
{type, float},{{/isDouble}}{{#isByteArray}}
|
||||||
{type, 'binary'},{{/isByteArray}}{{#isBinary}}
|
{type, binary},{{/isByteArray}}{{#isBinary}}
|
||||||
{type, 'binary'},{{/isBinary}}{{#isBoolean}}
|
{type, binary},{{/isBinary}}{{#isBoolean}}
|
||||||
{type, 'boolean'},{{/isBoolean}}{{#isDate}}
|
{type, boolean},{{/isBoolean}}{{#isDate}}
|
||||||
{type, 'date'},{{/isDate}}{{#isDateTime}}
|
{type, date},{{/isDate}}{{#isDateTime}}
|
||||||
{type, 'datetime'},{{/isDateTime}}{{#isEnum}}
|
{type, datetime},{{/isDateTime}}{{#isEnum}}
|
||||||
{enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}}
|
{enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}}
|
||||||
{max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}}
|
{max, {{maximum}}},{{/maximum}}{{#exclusiveMaximum}}
|
||||||
{exclusive_max, {{exclusiveMaximum}} },{{/exclusiveMaximum}}{{#minimum}}
|
{exclusive_max, {{exclusiveMaximum}}},{{/exclusiveMaximum}}{{#minimum}}
|
||||||
{min, {{minimum}} },{{/minimum}}{{#exclusiveMinimum}}
|
{min, {{minimum}}},{{/minimum}}{{#exclusiveMinimum}}
|
||||||
{exclusive_min, {{exclusiveMinimum}} },{{/exclusiveMinimum}}{{#maxLength}}
|
{exclusive_min, {{exclusiveMinimum}}},{{/exclusiveMinimum}}{{#maxLength}}
|
||||||
{max_length, {{maxLength}} },{{/maxLength}}{{#minLength}}
|
{max_length, {{maxLength}}},{{/maxLength}}{{#minLength}}
|
||||||
{min_length, {{minLength}} },{{/minLength}}{{#pattern}}
|
{min_length, {{minLength}}},{{/minLength}}{{#pattern}}
|
||||||
{pattern, "{{{pattern}}}" },{{/pattern}}{{#isBodyParam}}
|
{pattern, "{{{pattern}}}"},{{/pattern}}{{#isBodyParam}}
|
||||||
schema,{{/isBodyParam}}{{#required}}
|
schema,{{/isBodyParam}}{{#required}}
|
||||||
required{{/required}}{{^required}}
|
required{{/required}}{{^required}}
|
||||||
not_required{{/required}}
|
not_required{{/required}}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_param_info(OperationID, Name) ->
|
||||||
request_param_info(OperationID, Name) ->
|
|
||||||
error({unknown_param, 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) ->
|
populate_request_params(_, [], Req, _, Model) ->
|
||||||
{ok, Model, Req};
|
{ok, Model, Req};
|
||||||
|
|
||||||
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
|
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
|
||||||
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
|
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
|
||||||
{ok, K, V, Req} ->
|
{ok, K, V, Req} ->
|
||||||
@ -118,24 +181,9 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
|
|||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec validate_response(
|
-include_lib("kernel/include/logger.hrl").
|
||||||
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}}
|
|
||||||
|
|
||||||
validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
|
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
|
||||||
ok.
|
|
||||||
|
|
||||||
validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
|
|
||||||
[
|
[
|
||||||
validate(schema, ReturnBaseType, Item, ValidatorState)
|
validate(schema, ReturnBaseType, Item, ValidatorState)
|
||||||
|| Item <- Body];
|
|| Item <- Body];
|
||||||
@ -143,45 +191,37 @@ validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
|
|||||||
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
|
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
|
||||||
validate(schema, ReturnBaseType, Body, ValidatorState).
|
validate(schema, ReturnBaseType, Body, ValidatorState).
|
||||||
|
|
||||||
%%%
|
|
||||||
validate(Rule = required, Name, Value, _ValidatorState) ->
|
validate(Rule = required, Name, Value, _ValidatorState) ->
|
||||||
case Value of
|
case Value of
|
||||||
undefined -> validation_error(Rule, Name);
|
undefined -> validation_error(Rule, Name);
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(not_required, _Name, _Value, _ValidatorState) ->
|
validate(not_required, _Name, _Value, _ValidatorState) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
||||||
validate(_, _Name, undefined, _ValidatorState) ->
|
validate(_, _Name, undefined, _ValidatorState) ->
|
||||||
ok;
|
ok;
|
||||||
|
validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) ->
|
|
||||||
try
|
try
|
||||||
{ok, {{packageName}}_utils:to_int(Value)}
|
{ok, to_int(Value)}
|
||||||
catch
|
catch
|
||||||
error:badarg ->
|
error:badarg ->
|
||||||
validation_error(Rule, Name)
|
validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) ->
|
|
||||||
try
|
try
|
||||||
{ok, {{packageName}}_utils:to_float(Value)}
|
{ok, to_float(Value)}
|
||||||
catch
|
catch
|
||||||
error:badarg ->
|
error:badarg ->
|
||||||
validation_error(Rule, Name)
|
validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) ->
|
|
||||||
case is_binary(Value) of
|
case is_binary(Value) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
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};
|
{ok, Value};
|
||||||
|
validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
|
|
||||||
V = binary_to_lower(Value),
|
V = binary_to_lower(Value),
|
||||||
try
|
try
|
||||||
case binary_to_existing_atom(V, utf8) of
|
case binary_to_existing_atom(V, utf8) of
|
||||||
@ -192,19 +232,16 @@ validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
|
|||||||
error:badarg ->
|
error:badarg ->
|
||||||
validation_error(Rule, Name)
|
validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) ->
|
|
||||||
case is_binary(Value) of
|
case is_binary(Value) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
|
||||||
validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) ->
|
|
||||||
case is_binary(Value) of
|
case is_binary(Value) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
||||||
try
|
try
|
||||||
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
|
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
|
||||||
@ -216,52 +253,44 @@ validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
|
|||||||
error:badarg ->
|
error:badarg ->
|
||||||
validation_error(Rule, Name)
|
validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
|
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
|
||||||
case Value =< Max of
|
case Value =< Max of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
|
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
|
||||||
case Value > ExclusiveMax of
|
case Value > ExclusiveMax of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
|
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
|
||||||
case Value >= Min of
|
case Value >= Min of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
|
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
|
||||||
case Value =< ExclusiveMin of
|
case Value =< ExclusiveMin of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
|
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
|
||||||
case size(Value) =< MaxLength of
|
case size(Value) =< MaxLength of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
|
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
|
||||||
case size(Value) >= MinLength of
|
case size(Value) >= MinLength of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> validation_error(Rule, Name)
|
false -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
|
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
|
||||||
{ok, MP} = re:compile(Pattern),
|
{ok, MP} = re:compile(Pattern),
|
||||||
case re:run(Value, MP) of
|
case re:run(Value, MP) of
|
||||||
{match, _} -> ok;
|
{match, _} -> ok;
|
||||||
_ -> validation_error(Rule, Name)
|
_ -> validation_error(Rule, Name)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule = schema, Name, Value, ValidatorState) ->
|
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
|
try
|
||||||
_ = validate_with_schema(Value, Definition, ValidatorState),
|
_ = validate_with_schema(Value, Definition, ValidatorState),
|
||||||
ok
|
ok
|
||||||
@ -281,18 +310,15 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
|
|||||||
},
|
},
|
||||||
validation_error(Rule, Name, Info)
|
validation_error(Rule, Name, Info)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
validate(Rule, Name, _Value, _ValidatorState) ->
|
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}).
|
error({unknown_validation_rule, Rule}).
|
||||||
|
|
||||||
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
|
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
|
||||||
|
|
||||||
validation_error(ViolatedRule, Name) ->
|
validation_error(ViolatedRule, Name) ->
|
||||||
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) ->
|
validation_error(ViolatedRule, Name, Info) ->
|
||||||
throw({wrong_param, Name, ViolatedRule, Info}).
|
throw({wrong_param, Name, ViolatedRule, Info}).
|
||||||
|
|
||||||
@ -307,31 +333,26 @@ get_value(body, _Name, Req0) ->
|
|||||||
Value ->
|
Value ->
|
||||||
{Value, Req}
|
{Value, Req}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
get_value(qs_val, Name, Req) ->
|
get_value(qs_val, Name, Req) ->
|
||||||
QS = cowboy_req:parse_qs(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};
|
{Value, Req};
|
||||||
|
|
||||||
get_value(header, Name, Req) ->
|
get_value(header, Name, Req) ->
|
||||||
Headers = cowboy_req:headers(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};
|
{Value, Req};
|
||||||
|
|
||||||
get_value(binding, Name, 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}.
|
{Value, Req}.
|
||||||
|
|
||||||
|
prepare_body(<<>>) ->
|
||||||
|
<<>>;
|
||||||
prepare_body(Body) ->
|
prepare_body(Body) ->
|
||||||
case Body of
|
try
|
||||||
<<"">> -> <<"">>;
|
json:decode(Body)
|
||||||
_ ->
|
catch
|
||||||
try
|
error:_ ->
|
||||||
jsx:decode(Body, [return_maps])
|
{error, {invalid_body, not_json, Body}}
|
||||||
catch
|
|
||||||
error:_ ->
|
|
||||||
{error, {invalid_body, not_json, Body}}
|
|
||||||
end
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
validate_with_schema(Body, Definition, ValidatorState) ->
|
validate_with_schema(Body, Definition, ValidatorState) ->
|
||||||
@ -359,5 +380,84 @@ prepare_param(Rules, Name, Value, ValidatorState) ->
|
|||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
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) ->
|
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.
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
{application, {{packageName}}, [
|
{application,
|
||||||
{description, {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
|
{{packageName}},
|
||||||
{vsn, "{{apiVersion}}"},
|
[{description,
|
||||||
{registered, []},
|
{{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
|
||||||
{applications, [
|
{vsn, "{{apiVersion}}"},
|
||||||
kernel,
|
{registered, []},
|
||||||
stdlib,
|
{applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
|
||||||
ssl,
|
{env, []},
|
||||||
inets,
|
{modules, []},
|
||||||
jsx,
|
{licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
|
||||||
jesse,
|
{links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}]}.
|
||||||
cowboy
|
|
||||||
]},
|
|
||||||
{env, [
|
|
||||||
]},
|
|
||||||
{modules, []},
|
|
||||||
{licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
|
|
||||||
{links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}
|
|
||||||
]}.
|
|
||||||
|
@ -2,51 +2,44 @@
|
|||||||
|
|
||||||
-export([authorize_api_key/5]).
|
-export([authorize_api_key/5]).
|
||||||
|
|
||||||
-spec authorize_api_key(
|
-spec authorize_api_key({{packageName}}_logic_handler:api_key_callback(),
|
||||||
LogicHandler :: atom(),
|
{{packageName}}_api:operation_id(),
|
||||||
OperationID :: {{packageName}}_api:operation_id(),
|
header | qs_val,
|
||||||
From :: header | qs_val,
|
iodata() | atom(),
|
||||||
KeyParam :: iodata() | atom(),
|
cowboy_req:req()) ->
|
||||||
Req ::cowboy_req:req()
|
{true, {{packageName}}_logic_handler:context(), cowboy_req:req()} |
|
||||||
)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
|
{false, binary(), cowboy_req:req()}.
|
||||||
{false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
|
authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
|
||||||
|
|
||||||
authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
|
|
||||||
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
|
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
|
||||||
case ApiKey of
|
case ApiKey of
|
||||||
undefined ->
|
undefined ->
|
||||||
AuthHeader = <<"">>,
|
AuthHeader = <<>>,
|
||||||
{false, AuthHeader, Req};
|
{false, AuthHeader, Req};
|
||||||
_ ->
|
_ ->
|
||||||
Result = {{packageName}}_logic_handler:authorize_api_key(
|
case Handler(OperationID, ApiKey) of
|
||||||
LogicHandler,
|
{true, Context} ->
|
||||||
OperationID,
|
|
||||||
ApiKey
|
|
||||||
),
|
|
||||||
case Result of
|
|
||||||
{{#authMethods}}
|
|
||||||
{{#isApiKey}}
|
|
||||||
{true, Context} ->
|
|
||||||
{true, Context, Req};
|
{true, Context, Req};
|
||||||
{{/isApiKey}}
|
{false, AuthHeader} ->
|
||||||
{{/authMethods}}
|
|
||||||
false ->
|
|
||||||
AuthHeader = <<"">>,
|
|
||||||
{false, AuthHeader, Req}
|
{false, AuthHeader, Req}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_api_key(header, KeyParam, Req) ->
|
get_api_key(header, KeyParam, Req) ->
|
||||||
Headers = cowboy_req:headers(Req),
|
Headers = cowboy_req:headers(Req),
|
||||||
{
|
{maps:get(KeyParam, Headers, undefined), Req};
|
||||||
maps:get(
|
|
||||||
{{packageName}}_utils:to_header(KeyParam),
|
|
||||||
Headers,
|
|
||||||
undefined
|
|
||||||
),
|
|
||||||
Req
|
|
||||||
};
|
|
||||||
|
|
||||||
get_api_key(qs_val, KeyParam, Req) ->
|
get_api_key(qs_val, KeyParam, Req) ->
|
||||||
QS = cowboy_req:parse_qs(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.
|
||||||
|
@ -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, #{}, #{}}.
|
|
@ -1,252 +1,129 @@
|
|||||||
%% basic handler
|
%% basic handler
|
||||||
-module({{classname}}).
|
-module({{classname}}).
|
||||||
|
|
||||||
|
-behaviour(cowboy_rest).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/logger.hrl").
|
||||||
|
|
||||||
%% Cowboy REST callbacks
|
%% Cowboy REST callbacks
|
||||||
-export([allowed_methods/2]).
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
-export([allow_missing_post/2]).
|
-export([allowed_methods/2]).
|
||||||
-export([content_types_accepted/2]).
|
-export([content_types_accepted/2]).
|
||||||
-export([content_types_provided/2]).
|
-export([content_types_provided/2]).
|
||||||
-export([delete_resource/2]).
|
-export([delete_resource/2]).
|
||||||
-export([is_authorized/2]).
|
-export([is_authorized/2]).
|
||||||
-export([known_content_type/2]).
|
|
||||||
-export([malformed_request/2]).
|
|
||||||
-export([valid_content_headers/2]).
|
-export([valid_content_headers/2]).
|
||||||
-export([valid_entity_length/2]).
|
-export([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
|
|
||||||
%% Handlers
|
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
-export([handle_request_json/2]).
|
|
||||||
|
|
||||||
-record(state, {
|
-record(state,
|
||||||
operation_id :: {{packageName}}_api:operation_id(),
|
{operation_id :: {{packageName}}_api:operation_id(),
|
||||||
logic_handler :: atom(),
|
accept_callback :: {{packageName}}_logic_handler:accept_callback(),
|
||||||
validator_state :: jesse_state:state(),
|
provide_callback :: {{packageName}}_logic_handler:provide_callback(),
|
||||||
context=#{} :: #{}
|
api_key_handler :: {{packageName}}_logic_handler:api_key_callback(),
|
||||||
}).
|
context = #{} :: {{packageName}}_logic_handler:context()}).
|
||||||
|
|
||||||
-type state() :: state().
|
-type state() :: #state{}.
|
||||||
|
|
||||||
-spec init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) ->
|
-spec init(cowboy_req:req(), {{packageName}}_router:init_opts()) ->
|
||||||
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
|
{cowboy_rest, cowboy_req:req(), state()}.
|
||||||
|
init(Req, {Operations, Module}) ->
|
||||||
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
|
|
||||||
Method = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
OperationID = maps:get(Method, Operations, undefined),
|
OperationID = maps:get(Method, Operations, undefined),
|
||||||
|
?LOG_INFO(#{what => "Attempt to process operation",
|
||||||
ValidatorState = ValidatorMod:get_validator_state(),
|
method => Method,
|
||||||
|
operation_id => OperationID}),
|
||||||
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
|
State = #state{operation_id = OperationID,
|
||||||
|
accept_callback = fun Module:accept_callback/4,
|
||||||
State = #state{
|
provide_callback = fun Module:provide_callback/4,
|
||||||
operation_id = OperationID,
|
api_key_handler = fun Module:authorize_api_key/2},
|
||||||
logic_handler = LogicHandler,
|
|
||||||
validator_state = ValidatorState
|
|
||||||
},
|
|
||||||
{cowboy_rest, Req, State}.
|
{cowboy_rest, Req, State}.
|
||||||
|
|
||||||
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
|
-spec allowed_methods(cowboy_req:req(), state()) ->
|
||||||
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
|
{[binary()], cowboy_req:req(), state()}.
|
||||||
|
{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationId}}'} = State) ->
|
||||||
{{#operations}}{{#operation}}
|
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = '{{operationId}}'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"{{httpMethod}}">>], Req, State};
|
{[<<"{{httpMethod}}">>], Req, State};
|
||||||
{{/operation}}{{/operations}}
|
{{/operation}}{{/operations}}allowed_methods(Req, State) ->
|
||||||
allowed_methods(Req, State) ->
|
|
||||||
{[], Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
|
-spec is_authorized(cowboy_req:req(), state()) ->
|
||||||
{
|
{true | {false, iodata()}, cowboy_req:req(), state()}.
|
||||||
Value :: true | {false, AuthHeader :: iodata()},
|
|
||||||
Req :: cowboy_req:req(),
|
|
||||||
State :: state()
|
|
||||||
}.
|
|
||||||
{{#operations}}
|
{{#operations}}
|
||||||
{{#operation}}
|
{{#operation}}
|
||||||
{{#authMethods}}
|
{{#authMethods.size}}
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = '{{operationId}}' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = '{{operationId}}' = OperationID,
|
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
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
{{#isApiKey}}
|
{{false, AuthHeader}, Req, State}
|
||||||
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;
|
end;
|
||||||
{{/isApiKey}}
|
{{/authMethods.size}}
|
||||||
{{#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}
|
|
||||||
end;
|
|
||||||
{{/isOAuth}}
|
|
||||||
{{/authMethods}}
|
|
||||||
{{/operation}}
|
{{/operation}}
|
||||||
{{/operations}}
|
{{/operations}}
|
||||||
{{^authMethods}}
|
|
||||||
is_authorized(Req, State) ->
|
is_authorized(Req, State) ->
|
||||||
{true, 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()) ->
|
-spec content_types_accepted(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), AcceptResource :: atom()}],
|
{{#operations}}{{#operation}}content_types_accepted(Req, #state{operation_id = '{{operationId}}'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
{{^consumes.size}}
|
||||||
State :: state()
|
{[], Req, State};
|
||||||
}.
|
{{/consumes.size}}
|
||||||
|
{{#consumes.size}}
|
||||||
content_types_accepted(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{{#consumes}}
|
||||||
], Req, State}.
|
{<<"{{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()) ->
|
-spec valid_content_headers(cowboy_req:req(), state()) ->
|
||||||
{Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
|
{boolean(), cowboy_req:req(), state()}.
|
||||||
{{#operations}}{{#operation}}
|
{{#operations}}{{#operation}}valid_content_headers(Req, #state{operation_id = '{{operationId}}'} = State) ->
|
||||||
valid_content_headers(
|
{true, Req, State};
|
||||||
Req0,
|
{{/operation}}{{/operations}}valid_content_headers(Req, State) ->
|
||||||
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) ->
|
|
||||||
{false, Req, State}.
|
{false, Req, State}.
|
||||||
|
|
||||||
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_provided(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), ProvideResource :: atom()}],
|
{{#operations}}{{#operation}}content_types_provided(Req, #state{operation_id = '{{operationId}}'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
{{^produces.size}}
|
||||||
State :: state()
|
{[], Req, State};
|
||||||
}.
|
{{/produces.size}}
|
||||||
|
{{#produces.size}}
|
||||||
content_types_provided(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{{#produces}}
|
||||||
], Req, State}.
|
{<<"{{mediaType}}">>, handle_type_provided}{{^-last}}{{#produces.size}},{{/produces.size}}{{/-last}}
|
||||||
|
{{/produces}}
|
||||||
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
|
], Req, State};
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
{{/produces.size}}
|
||||||
|
{{/operation}}{{/operations}}content_types_provided(Req, State) ->
|
||||||
malformed_request(Req, State) ->
|
{[], 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().
|
|
||||||
|
|
||||||
|
-spec delete_resource(cowboy_req:req(), state()) ->
|
||||||
|
{boolean(), cowboy_req:req(), state()}.
|
||||||
delete_resource(Req, State) ->
|
delete_resource(Req, State) ->
|
||||||
handle_request_json(Req, State).
|
case handle_type_accepted(Req, State) of
|
||||||
|
true ->
|
||||||
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
|
{true, Req, State};
|
||||||
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
|
_ ->
|
||||||
|
{false, Req, 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.
|
end.
|
||||||
|
|
||||||
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
|
-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).
|
||||||
|
|
||||||
handle_request_json(
|
-spec handle_type_provided(cowboy_req:req(), state()) ->
|
||||||
Req0,
|
{cowboy_req:resp_body(), cowboy_req:req(), {{packageName}}_logic_handler:context()}.
|
||||||
State = #state{
|
handle_type_provided(Req, #state{operation_id = OperationID,
|
||||||
operation_id = OperationID,
|
provide_callback = Handler} = State) ->
|
||||||
logic_handler = LogicHandler,
|
Handler({{operations.pathPrefix}}, OperationID, Req, State#state.context).
|
||||||
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).
|
|
||||||
|
@ -1,60 +1,56 @@
|
|||||||
-module({{packageName}}_logic_handler).
|
-module({{packageName}}_logic_handler).
|
||||||
|
|
||||||
-export([handle_request/4]).
|
-include_lib("kernel/include/logger.hrl").
|
||||||
{{#authMethods}}
|
|
||||||
{{#isApiKey}}
|
-type api_key_callback() ::
|
||||||
{{#-first}}
|
fun(({{packageName}}_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
|
||||||
-export([authorize_api_key/3]).
|
-type accept_callback() ::
|
||||||
{{/-first}}
|
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
{{/isApiKey}}
|
boolean() | {created, iodata()} | {see_other, iodata()}).
|
||||||
{{/authMethods}}
|
-type provide_callback() ::
|
||||||
{{^authMethods}}
|
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
-export([authorize_api_key/3]).
|
{cowboy_req:resp_body(), cowboy_req:req(), context()}).
|
||||||
{{/authMethods}}
|
|
||||||
-type context() :: #{binary() => any()}.
|
-type context() :: #{binary() => any()}.
|
||||||
-type handler_response() ::{
|
|
||||||
Status :: cowboy:http_status(),
|
|
||||||
Headers :: cowboy:http_headers(),
|
|
||||||
Body :: jsx:json_term()}.
|
|
||||||
|
|
||||||
-export_type([handler_response/0]).
|
-export_type([context/0, api_key_callback/0, accept_callback/0, provide_callback/0]).
|
||||||
|
|
||||||
{{#authMethods}}
|
-optional_callbacks([api_key_callback/2]).
|
||||||
{{#isApiKey}}
|
|
||||||
-callback authorize_api_key(
|
|
||||||
OperationID :: {{packageName}}_api:operation_id(),
|
|
||||||
ApiKey :: binary()
|
|
||||||
) ->
|
|
||||||
Result :: boolean() | {boolean(), context()}.
|
|
||||||
{{/isApiKey}}
|
|
||||||
{{/authMethods}}
|
|
||||||
|
|
||||||
|
-callback api_key_callback({{packageName}}_api:operation_id(), binary()) ->
|
||||||
|
{true, context()} | {false, iodata()}.
|
||||||
|
|
||||||
-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) ->
|
-callback accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
handler_response().
|
boolean() | {created, iodata()} | {see_other, iodata()}.
|
||||||
|
|
||||||
-spec handle_request(
|
-callback provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
Handler :: atom(),
|
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
|
||||||
OperationID :: {{packageName}}_api:operation_id(),
|
|
||||||
Request :: cowboy_req:req(),
|
|
||||||
Context :: context()
|
|
||||||
) ->
|
|
||||||
handler_response().
|
|
||||||
|
|
||||||
handle_request(Handler, OperationID, Req, Context) ->
|
-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
|
||||||
Handler:handle_request(OperationID, Req, Context).
|
-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
|
||||||
|
|
||||||
{{#authMethods}}
|
-spec api_key_callback({{packageName}}_api:operation_id(), binary()) -> {true, #{}}.
|
||||||
{{#isApiKey}}
|
api_key_callback(OperationID, ApiKey) ->
|
||||||
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
|
?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
|
||||||
Result :: false | {true, context()}.
|
operation_id => OperationID,
|
||||||
authorize_api_key(Handler, OperationID, ApiKey) ->
|
api_key => ApiKey}),
|
||||||
Handler:authorize_api_key(OperationID, ApiKey).
|
{true, #{}}.
|
||||||
{{/isApiKey}}
|
|
||||||
{{/authMethods}}
|
-spec accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
{{^authMethods}}
|
{cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
|
||||||
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
|
accept_callback(Class, OperationID, Req, Context) ->
|
||||||
Result :: false.
|
?LOG_ERROR(#{what => "Got not implemented request to process",
|
||||||
authorize_api_key(_Handler, _OperationID, _ApiKey) ->
|
class => Class,
|
||||||
false.
|
operation_id => OperationID,
|
||||||
{{/authMethods}}
|
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}.
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
{minimum_otp_vsn, "27"}.
|
||||||
|
|
||||||
{deps, [
|
{deps, [
|
||||||
{cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
|
{cowboy, "2.12.0"},
|
||||||
{rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
|
{ranch, "2.1.0"},
|
||||||
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
|
{jesse, "1.8.1"}
|
||||||
{jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{dialyzer,
|
||||||
|
[{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
|
||||||
|
{warnings, [missing_return, unknown]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{xref_checks,
|
||||||
|
[undefined_function_calls, deprecated_function_calls, deprecated_functions]}.
|
||||||
|
@ -1,57 +1,36 @@
|
|||||||
-module({{packageName}}_router).
|
-module({{packageName}}_router).
|
||||||
|
|
||||||
-export([get_paths/1, get_validator_state/0]).
|
-export([get_paths/1]).
|
||||||
|
|
||||||
-type operations() :: #{
|
-type method() :: binary().
|
||||||
Method :: binary() => {{packageName}}_api:operation_id()
|
-type operations() :: #{method() => {{packageName}}_api:operation_id()}.
|
||||||
}.
|
-type init_opts() :: {operations(), module()}.
|
||||||
|
|
||||||
-type init_opts() :: {
|
|
||||||
Operations :: operations(),
|
|
||||||
LogicHandler :: atom(),
|
|
||||||
ValidatorMod :: module()
|
|
||||||
}.
|
|
||||||
|
|
||||||
-export_type([init_opts/0]).
|
-export_type([init_opts/0]).
|
||||||
|
|
||||||
-spec get_paths(LogicHandler :: atom()) -> [{'_',[{
|
-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
|
||||||
Path :: string(),
|
|
||||||
Handler :: atom(),
|
|
||||||
InitOpts :: init_opts()
|
|
||||||
}]}].
|
|
||||||
|
|
||||||
get_paths(LogicHandler) ->
|
get_paths(LogicHandler) ->
|
||||||
ValidatorState = prepare_validator(),
|
|
||||||
PreparedPaths = maps:fold(
|
PreparedPaths = maps:fold(
|
||||||
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
|
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
|
||||||
[{Path, Handler, Operations} | Acc]
|
[{Path, Handler, Operations} | Acc]
|
||||||
end,
|
end, [], group_paths()
|
||||||
[],
|
),
|
||||||
group_paths()
|
[{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
|
||||||
),
|
|
||||||
[
|
|
||||||
{'_',
|
|
||||||
[{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
|
|
||||||
}
|
|
||||||
].
|
|
||||||
|
|
||||||
group_paths() ->
|
group_paths() ->
|
||||||
maps:fold(
|
maps:fold(
|
||||||
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
||||||
case maps:find(Path, Acc) of
|
case maps:find(Path, Acc) of
|
||||||
{ok, PathInfo0 = #{operations := Operations0}} ->
|
{ok, PathInfo0 = #{operations := Operations0}} ->
|
||||||
Operations = Operations0#{Method => OperationID},
|
Operations = Operations0#{Method => OperationID},
|
||||||
PathInfo = PathInfo0#{operations => Operations},
|
PathInfo = PathInfo0#{operations => Operations},
|
||||||
Acc#{Path => PathInfo};
|
Acc#{Path => PathInfo};
|
||||||
error ->
|
error ->
|
||||||
Operations = #{Method => OperationID},
|
Operations = #{Method => OperationID},
|
||||||
PathInfo = #{handler => Handler, operations => Operations},
|
PathInfo = #{handler => Handler, operations => Operations},
|
||||||
Acc#{Path => PathInfo}
|
Acc#{Path => PathInfo}
|
||||||
end
|
end
|
||||||
end,
|
end, #{}, get_operations()).
|
||||||
#{},
|
|
||||||
get_operations()
|
|
||||||
).
|
|
||||||
|
|
||||||
get_operations() ->
|
get_operations() ->
|
||||||
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
@ -61,18 +40,3 @@ get_operations() ->
|
|||||||
handler => '{{classname}}'
|
handler => '{{classname}}'
|
||||||
}{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}
|
}{{^-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").
|
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
-module({{packageName}}_server).
|
-module({{packageName}}_server).
|
||||||
|
|
||||||
|
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_logic_handler).
|
||||||
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler).
|
|
||||||
|
|
||||||
-export([start/2]).
|
-export([start/2]).
|
||||||
|
-ignore_xref([start/2]).
|
||||||
|
|
||||||
-spec start( ID :: any(), #{
|
-spec start(term(), #{transport => tcp | ssl,
|
||||||
ip => inet:ip_address(),
|
transport_opts => ranch:opts(),
|
||||||
port => inet:port_number(),
|
protocol_opts => cowboy:opts(),
|
||||||
logic_handler => module(),
|
logic_handler => module()}) ->
|
||||||
net_opts => []
|
{ok, pid()} | {error, any()}.
|
||||||
}) -> {ok, pid()} | {error, any()}.
|
start(ID, Params) ->
|
||||||
|
Transport = maps:get(transport, Params, tcp),
|
||||||
start(ID, #{
|
TransportOpts = maps:get(transport_opts, Params, #{}),
|
||||||
ip := IP ,
|
ProtocolOpts = maps:get(procotol_opts, Params, #{}),
|
||||||
port := Port,
|
|
||||||
net_opts := NetOpts
|
|
||||||
} = Params) ->
|
|
||||||
{Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
|
|
||||||
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
|
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
|
||||||
ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
|
CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
|
||||||
CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
|
|
||||||
case Transport of
|
case Transport of
|
||||||
ssl ->
|
ssl ->
|
||||||
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
|
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
|
||||||
@ -28,33 +23,17 @@ start(ID, #{
|
|||||||
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
|
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
|
||||||
end.
|
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_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
|
DefaultOpts = get_default_opts(LogicHandler),
|
||||||
|
maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
|
||||||
|
|
||||||
get_cowboy_config(_LogicHandler, [], Opts) ->
|
get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
|
||||||
Opts;
|
maps:put(env, Env, AccIn);
|
||||||
|
get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
|
||||||
get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
|
Env = maps:merge(OldEnv, NewEnv),
|
||||||
NewEnv = case proplists:get_value(dispatch, Env) of
|
maps:put(env, Env, AccIn);
|
||||||
undefined -> [get_default_dispatch(LogicHandler) | Env];
|
get_cowboy_config(Key, Value, AccIn) ->
|
||||||
_ -> Env
|
maps:put(Key, Value, AccIn).
|
||||||
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_default_dispatch(LogicHandler) ->
|
get_default_dispatch(LogicHandler) ->
|
||||||
Paths = {{packageName}}_router:get_paths(LogicHandler),
|
Paths = {{packageName}}_router:get_paths(LogicHandler),
|
||||||
@ -62,6 +41,3 @@ get_default_dispatch(LogicHandler) ->
|
|||||||
|
|
||||||
get_default_opts(LogicHandler) ->
|
get_default_opts(LogicHandler) ->
|
||||||
#{env => get_default_dispatch(LogicHandler)}.
|
#{env => get_default_dispatch(LogicHandler)}.
|
||||||
|
|
||||||
store_key(Key, Value, Opts) ->
|
|
||||||
maps:put(Key, Value, Opts).
|
|
||||||
|
@ -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, <<C, _/binary>>, _Acc) when C > 127 ->
|
|
||||||
error(badarg);
|
|
||||||
|
|
||||||
to_case(Case = lower, <<C, Rest/binary>>, Acc) ->
|
|
||||||
to_case(Case, Rest, <<Acc/binary, (to_lower_char(C))>>);
|
|
||||||
|
|
||||||
to_case(Case = upper, <<C, Rest/binary>>, Acc) ->
|
|
||||||
to_case(Case, Rest, <<Acc/binary, (to_upper_char(C))>>).
|
|
||||||
|
|
||||||
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.
|
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
|||||||
|
7.9.0-SNAPSHOT
|
36
samples/server/echo_api/erlang-server/README.md
Normal file
36
samples/server/echo_api/erlang-server/README.md
Normal file
@ -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.
|
46
samples/server/echo_api/erlang-server/pom.xml
Normal file
46
samples/server/echo_api/erlang-server/pom.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.openapitools</groupId>
|
||||||
|
<artifactId>ErlangServerEchoTests</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<name>Erlang Echo Server</name>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>1.6.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile-test</id>
|
||||||
|
<phase>integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>exec</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<executable>rebar3</executable>
|
||||||
|
<arguments>
|
||||||
|
<argument>compile</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
1282
samples/server/echo_api/erlang-server/priv/openapi.json
Normal file
1282
samples/server/echo_api/erlang-server/priv/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
15
samples/server/echo_api/erlang-server/rebar.config
Normal file
15
samples/server/echo_api/erlang-server/rebar.config
Normal file
@ -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]}.
|
11
samples/server/echo_api/erlang-server/src/openapi.app.src
Normal file
11
samples/server/echo_api/erlang-server/src/openapi.app.src
Normal file
@ -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, []}]}.
|
942
samples/server/echo_api/erlang-server/src/openapi_api.erl
Normal file
942
samples/server/echo_api/erlang-server/src/openapi_api.erl
Normal file
@ -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.
|
45
samples/server/echo_api/erlang-server/src/openapi_auth.erl
Normal file
45
samples/server/echo_api/erlang-server/src/openapi_auth.erl
Normal file
@ -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.
|
@ -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).
|
@ -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).
|
@ -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).
|
@ -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).
|
@ -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}.
|
@ -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).
|
@ -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).
|
172
samples/server/echo_api/erlang-server/src/openapi_router.erl
Normal file
172
samples/server/echo_api/erlang-server/src/openapi_router.erl
Normal file
@ -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'
|
||||||
|
}
|
||||||
|
}.
|
43
samples/server/echo_api/erlang-server/src/openapi_server.erl
Normal file
43
samples/server/echo_api/erlang-server/src/openapi_server.erl
Normal file
@ -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)}.
|
@ -4,11 +4,9 @@ rebar.config
|
|||||||
src/openapi.app.src
|
src/openapi.app.src
|
||||||
src/openapi_api.erl
|
src/openapi_api.erl
|
||||||
src/openapi_auth.erl
|
src/openapi_auth.erl
|
||||||
src/openapi_default_logic_handler.erl
|
|
||||||
src/openapi_logic_handler.erl
|
src/openapi_logic_handler.erl
|
||||||
src/openapi_pet_handler.erl
|
src/openapi_pet_handler.erl
|
||||||
src/openapi_router.erl
|
src/openapi_router.erl
|
||||||
src/openapi_server.erl
|
src/openapi_server.erl
|
||||||
src/openapi_store_handler.erl
|
src/openapi_store_handler.erl
|
||||||
src/openapi_user_handler.erl
|
src/openapi_user_handler.erl
|
||||||
src/openapi_utils.erl
|
|
||||||
|
@ -4,54 +4,33 @@
|
|||||||
|
|
||||||
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
|
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
|
## Prerequisites
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
Use erlang-server with erlang.mk
|
Use erlang-server with rebar3
|
||||||
|
|
||||||
1, Create an application by using erlang.mk
|
1, Create an application by using rebar3
|
||||||
$ mkdir http_server
|
$ rebar3 new app 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:
|
2, Generate erlang-server project using openapi-generator
|
||||||
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
|
|
||||||
https://github.com/OpenAPITools/openapi-generator#2---getting-started
|
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
|
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=>[]})
|
openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
|
||||||
2, Compilation http_server project
|
2, Compile your http_server project
|
||||||
$ make
|
$ rebar3 compile
|
||||||
3, Start erlang virtual machine
|
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
|
4, Start project
|
||||||
application:ensure_all_started(http_server).
|
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.
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
{minimum_otp_vsn, "27"}.
|
||||||
|
|
||||||
{deps, [
|
{deps, [
|
||||||
{cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
|
{cowboy, "2.12.0"},
|
||||||
{rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
|
{ranch, "2.1.0"},
|
||||||
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
|
{jesse, "1.8.1"}
|
||||||
{jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{dialyzer,
|
||||||
|
[{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
|
||||||
|
{warnings, [missing_return, unknown]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{xref_checks,
|
||||||
|
[undefined_function_calls, deprecated_function_calls, deprecated_functions]}.
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
{application, openapi, [
|
{application,
|
||||||
{description, "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters."},
|
openapi,
|
||||||
{vsn, "1.0.0"},
|
[{description,
|
||||||
{registered, []},
|
"This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters."},
|
||||||
{applications, [
|
{vsn, "1.0.0"},
|
||||||
kernel,
|
{registered, []},
|
||||||
stdlib,
|
{applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
|
||||||
ssl,
|
{env, []},
|
||||||
inets,
|
{modules, []},
|
||||||
jsx,
|
{licenses, ["Apache-2.0"]},
|
||||||
jesse,
|
{links, []}]}.
|
||||||
cowboy
|
|
||||||
]},
|
|
||||||
{env, [
|
|
||||||
]},
|
|
||||||
{modules, []},
|
|
||||||
{licenses, ["Apache-2.0"]},
|
|
||||||
{links, []}
|
|
||||||
]}.
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,47 +2,44 @@
|
|||||||
|
|
||||||
-export([authorize_api_key/5]).
|
-export([authorize_api_key/5]).
|
||||||
|
|
||||||
-spec authorize_api_key(
|
-spec authorize_api_key(openapi_logic_handler:api_key_callback(),
|
||||||
LogicHandler :: atom(),
|
openapi_api:operation_id(),
|
||||||
OperationID :: openapi_api:operation_id(),
|
header | qs_val,
|
||||||
From :: header | qs_val,
|
iodata() | atom(),
|
||||||
KeyParam :: iodata() | atom(),
|
cowboy_req:req()) ->
|
||||||
Req ::cowboy_req:req()
|
{true, openapi_logic_handler:context(), cowboy_req:req()} |
|
||||||
)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
|
{false, binary(), cowboy_req:req()}.
|
||||||
{false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
|
authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
|
||||||
|
|
||||||
authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
|
|
||||||
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
|
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
|
||||||
case ApiKey of
|
case ApiKey of
|
||||||
undefined ->
|
undefined ->
|
||||||
AuthHeader = <<"">>,
|
AuthHeader = <<>>,
|
||||||
{false, AuthHeader, Req};
|
{false, AuthHeader, Req};
|
||||||
_ ->
|
_ ->
|
||||||
Result = openapi_logic_handler:authorize_api_key(
|
case Handler(OperationID, ApiKey) of
|
||||||
LogicHandler,
|
{true, Context} ->
|
||||||
OperationID,
|
|
||||||
ApiKey
|
|
||||||
),
|
|
||||||
case Result of
|
|
||||||
{true, Context} ->
|
|
||||||
{true, Context, Req};
|
{true, Context, Req};
|
||||||
false ->
|
{false, AuthHeader} ->
|
||||||
AuthHeader = <<"">>,
|
|
||||||
{false, AuthHeader, Req}
|
{false, AuthHeader, Req}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_api_key(header, KeyParam, Req) ->
|
get_api_key(header, KeyParam, Req) ->
|
||||||
Headers = cowboy_req:headers(Req),
|
Headers = cowboy_req:headers(Req),
|
||||||
{
|
{maps:get(KeyParam, Headers, undefined), Req};
|
||||||
maps:get(
|
|
||||||
openapi_utils:to_header(KeyParam),
|
|
||||||
Headers,
|
|
||||||
undefined
|
|
||||||
),
|
|
||||||
Req
|
|
||||||
};
|
|
||||||
|
|
||||||
get_api_key(qs_val, KeyParam, Req) ->
|
get_api_key(qs_val, KeyParam, Req) ->
|
||||||
QS = cowboy_req:parse_qs(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.
|
||||||
|
@ -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, #{}, #{}}.
|
|
@ -1,36 +1,56 @@
|
|||||||
-module(openapi_logic_handler).
|
-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 context() :: #{binary() => any()}.
|
||||||
-type handler_response() ::{
|
|
||||||
Status :: cowboy:http_status(),
|
|
||||||
Headers :: cowboy:http_headers(),
|
|
||||||
Body :: jsx:json_term()}.
|
|
||||||
|
|
||||||
-export_type([handler_response/0]).
|
-export_type([context/0, api_key_callback/0, accept_callback/0, provide_callback/0]).
|
||||||
|
|
||||||
-callback authorize_api_key(
|
-optional_callbacks([api_key_callback/2]).
|
||||||
OperationID :: openapi_api:operation_id(),
|
|
||||||
ApiKey :: binary()
|
|
||||||
) ->
|
|
||||||
Result :: boolean() | {boolean(), context()}.
|
|
||||||
|
|
||||||
|
-callback api_key_callback(openapi_api:operation_id(), binary()) ->
|
||||||
|
{true, context()} | {false, iodata()}.
|
||||||
|
|
||||||
-callback handle_request(OperationID :: openapi_api:operation_id(), cowboy_req:req(), Context :: context()) ->
|
-callback accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
handler_response().
|
boolean() | {created, iodata()} | {see_other, iodata()}.
|
||||||
|
|
||||||
-spec handle_request(
|
-callback provide_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
|
||||||
Handler :: atom(),
|
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
|
||||||
OperationID :: openapi_api:operation_id(),
|
|
||||||
Request :: cowboy_req:req(),
|
|
||||||
Context :: context()
|
|
||||||
) ->
|
|
||||||
handler_response().
|
|
||||||
|
|
||||||
handle_request(Handler, OperationID, Req, Context) ->
|
-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
|
||||||
Handler:handle_request(OperationID, Req, Context).
|
-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
|
||||||
|
|
||||||
-spec authorize_api_key(Handler :: atom(), OperationID :: openapi_api:operation_id(), ApiKey :: binary()) ->
|
-spec api_key_callback(openapi_api:operation_id(), binary()) -> {true, #{}}.
|
||||||
Result :: false | {true, context()}.
|
api_key_callback(OperationID, ApiKey) ->
|
||||||
authorize_api_key(Handler, OperationID, ApiKey) ->
|
?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
|
||||||
Handler:authorize_api_key(OperationID, ApiKey).
|
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}.
|
||||||
|
@ -1,485 +1,251 @@
|
|||||||
%% basic handler
|
%% basic handler
|
||||||
-module(openapi_pet_handler).
|
-module(openapi_pet_handler).
|
||||||
|
|
||||||
|
-behaviour(cowboy_rest).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/logger.hrl").
|
||||||
|
|
||||||
%% Cowboy REST callbacks
|
%% Cowboy REST callbacks
|
||||||
-export([allowed_methods/2]).
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
-export([allow_missing_post/2]).
|
-export([allowed_methods/2]).
|
||||||
-export([content_types_accepted/2]).
|
-export([content_types_accepted/2]).
|
||||||
-export([content_types_provided/2]).
|
-export([content_types_provided/2]).
|
||||||
-export([delete_resource/2]).
|
-export([delete_resource/2]).
|
||||||
-export([is_authorized/2]).
|
-export([is_authorized/2]).
|
||||||
-export([known_content_type/2]).
|
|
||||||
-export([malformed_request/2]).
|
|
||||||
-export([valid_content_headers/2]).
|
-export([valid_content_headers/2]).
|
||||||
-export([valid_entity_length/2]).
|
-export([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
|
|
||||||
%% Handlers
|
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
-export([handle_request_json/2]).
|
|
||||||
|
|
||||||
-record(state, {
|
-record(state,
|
||||||
operation_id :: openapi_api:operation_id(),
|
{operation_id :: openapi_api:operation_id(),
|
||||||
logic_handler :: atom(),
|
accept_callback :: openapi_logic_handler:accept_callback(),
|
||||||
validator_state :: jesse_state:state(),
|
provide_callback :: openapi_logic_handler:provide_callback(),
|
||||||
context=#{} :: #{}
|
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()) ->
|
-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
|
||||||
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
|
{cowboy_rest, cowboy_req:req(), state()}.
|
||||||
|
init(Req, {Operations, Module}) ->
|
||||||
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
|
|
||||||
Method = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
OperationID = maps:get(Method, Operations, undefined),
|
OperationID = maps:get(Method, Operations, undefined),
|
||||||
|
?LOG_INFO(#{what => "Attempt to process operation",
|
||||||
ValidatorState = ValidatorMod:get_validator_state(),
|
method => Method,
|
||||||
|
operation_id => OperationID}),
|
||||||
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
|
State = #state{operation_id = OperationID,
|
||||||
|
accept_callback = fun Module:accept_callback/4,
|
||||||
State = #state{
|
provide_callback = fun Module:provide_callback/4,
|
||||||
operation_id = OperationID,
|
api_key_handler = fun Module:authorize_api_key/2},
|
||||||
logic_handler = LogicHandler,
|
|
||||||
validator_state = ValidatorState
|
|
||||||
},
|
|
||||||
{cowboy_rest, Req, State}.
|
{cowboy_rest, Req, State}.
|
||||||
|
|
||||||
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
|
-spec allowed_methods(cowboy_req:req(), state()) ->
|
||||||
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
|
{[binary()], cowboy_req:req(), state()}.
|
||||||
|
allowed_methods(Req, #state{operation_id = 'AddPet'} = State) ->
|
||||||
|
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'AddPet'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'DeletePet'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'DeletePet'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"DELETE">>], Req, State};
|
{[<<"DELETE">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'FindPetsByStatus'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'FindPetsByTags'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'GetPetById'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'GetPetById'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'UpdatePet'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'UpdatePet'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"PUT">>], Req, State};
|
{[<<"PUT">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'UpdatePetWithForm'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'UploadFile'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'UploadFile'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
|
||||||
allowed_methods(Req, State) ->
|
allowed_methods(Req, State) ->
|
||||||
{[], Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
|
-spec is_authorized(cowboy_req:req(), state()) ->
|
||||||
{
|
{true | {false, iodata()}, cowboy_req:req(), state()}.
|
||||||
Value :: true | {false, AuthHeader :: iodata()},
|
is_authorized(Req0,
|
||||||
Req :: cowboy_req:req(),
|
#state{operation_id = 'AddPet' = OperationID,
|
||||||
State :: state()
|
api_key_handler = Handler} = State) ->
|
||||||
}.
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
is_authorized(
|
{true, Context, Req} ->
|
||||||
Req0,
|
{true, Req, State#state{context = Context}};
|
||||||
State = #state{
|
{false, AuthHeader, Req} ->
|
||||||
operation_id = 'AddPet' = OperationID,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'DeletePet' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'DeletePet' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'FindPetsByStatus' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'FindPetsByStatus' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'FindPetsByTags' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'FindPetsByTags' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'GetPetById' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'GetPetById' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'UpdatePet' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'UpdatePet' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'UpdatePetWithForm' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'UpdatePetWithForm' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'UploadFile' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'UploadFile' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(Req, State) ->
|
is_authorized(Req, State) ->
|
||||||
{{false, <<"">>}, Req, State}.
|
{true, Req, State}.
|
||||||
is_authorized(Req, State) ->
|
|
||||||
{{false, <<"">>}, Req, State}.
|
|
||||||
|
|
||||||
-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_accepted(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), AcceptResource :: atom()}],
|
content_types_accepted(Req, #state{operation_id = 'AddPet'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
|
||||||
State :: state()
|
|
||||||
}.
|
|
||||||
|
|
||||||
content_types_accepted(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{<<"application/json">>, handle_type_accepted},
|
||||||
], Req, State}.
|
{<<"application/xml">>, handle_type_accepted}
|
||||||
|
], Req, State};
|
||||||
-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
|
content_types_accepted(Req, #state{operation_id = 'DeletePet'} = State) ->
|
||||||
{Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
|
{[], Req, State};
|
||||||
|
content_types_accepted(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
|
||||||
valid_content_headers(
|
{[], Req, State};
|
||||||
Req0,
|
content_types_accepted(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
|
||||||
State = #state{
|
{[], Req, State};
|
||||||
operation_id = 'AddPet'
|
content_types_accepted(Req, #state{operation_id = 'GetPetById'} = State) ->
|
||||||
}
|
{[], Req, State};
|
||||||
) ->
|
content_types_accepted(Req, #state{operation_id = 'UpdatePet'} = State) ->
|
||||||
Headers = [],
|
{[
|
||||||
{Result, Req} = validate_headers(Headers, Req0),
|
{<<"application/json">>, handle_type_accepted},
|
||||||
{Result, Req, State};
|
{<<"application/xml">>, handle_type_accepted}
|
||||||
|
], Req, State};
|
||||||
valid_content_headers(
|
content_types_accepted(Req, #state{operation_id = 'UpdatePetWithForm'} = State) ->
|
||||||
Req0,
|
{[
|
||||||
State = #state{
|
{<<"application/x-www-form-urlencoded">>, handle_type_accepted}
|
||||||
operation_id = 'DeletePet'
|
], Req, State};
|
||||||
}
|
content_types_accepted(Req, #state{operation_id = 'UploadFile'} = State) ->
|
||||||
) ->
|
{[
|
||||||
Headers = ["api_key"],
|
{<<"multipart/form-data">>, handle_type_accepted}
|
||||||
{Result, Req} = validate_headers(Headers, Req0),
|
], Req, State};
|
||||||
{Result, Req, State};
|
content_types_accepted(Req, State) ->
|
||||||
|
{[], 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};
|
|
||||||
|
|
||||||
|
-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) ->
|
valid_content_headers(Req, State) ->
|
||||||
{false, Req, State}.
|
{false, Req, State}.
|
||||||
|
|
||||||
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_provided(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), ProvideResource :: atom()}],
|
content_types_provided(Req, #state{operation_id = 'AddPet'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
|
||||||
State :: state()
|
|
||||||
}.
|
|
||||||
|
|
||||||
content_types_provided(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{<<"application/xml">>, handle_type_provided},
|
||||||
], Req, State}.
|
{<<"application/json">>, handle_type_provided}
|
||||||
|
], Req, State};
|
||||||
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
|
content_types_provided(Req, #state{operation_id = 'DeletePet'} = State) ->
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
{[], Req, State};
|
||||||
|
content_types_provided(Req, #state{operation_id = 'FindPetsByStatus'} = State) ->
|
||||||
malformed_request(Req, State) ->
|
{[
|
||||||
{false, Req, State}.
|
{<<"application/xml">>, handle_type_provided},
|
||||||
|
{<<"application/json">>, handle_type_provided}
|
||||||
-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
|
], Req, State};
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
content_types_provided(Req, #state{operation_id = 'FindPetsByTags'} = State) ->
|
||||||
|
{[
|
||||||
allow_missing_post(Req, State) ->
|
{<<"application/xml">>, handle_type_provided},
|
||||||
{false, Req, State}.
|
{<<"application/json">>, handle_type_provided}
|
||||||
|
], Req, State};
|
||||||
-spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
|
content_types_provided(Req, #state{operation_id = 'GetPetById'} = State) ->
|
||||||
processed_response().
|
{[
|
||||||
|
{<<"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) ->
|
delete_resource(Req, State) ->
|
||||||
handle_request_json(Req, State).
|
case handle_type_accepted(Req, State) of
|
||||||
|
true ->
|
||||||
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
|
{true, Req, State};
|
||||||
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
|
_ ->
|
||||||
|
{false, Req, 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.
|
end.
|
||||||
|
|
||||||
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
|
-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).
|
||||||
|
|
||||||
handle_request_json(
|
-spec handle_type_provided(cowboy_req:req(), state()) ->
|
||||||
Req0,
|
{cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
|
||||||
State = #state{
|
handle_type_provided(Req, #state{operation_id = OperationID,
|
||||||
operation_id = OperationID,
|
provide_callback = Handler} = State) ->
|
||||||
logic_handler = LogicHandler,
|
Handler(pet, OperationID, Req, State#state.context).
|
||||||
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).
|
|
||||||
|
@ -1,57 +1,36 @@
|
|||||||
-module(openapi_router).
|
-module(openapi_router).
|
||||||
|
|
||||||
-export([get_paths/1, get_validator_state/0]).
|
-export([get_paths/1]).
|
||||||
|
|
||||||
-type operations() :: #{
|
-type method() :: binary().
|
||||||
Method :: binary() => openapi_api:operation_id()
|
-type operations() :: #{method() => openapi_api:operation_id()}.
|
||||||
}.
|
-type init_opts() :: {operations(), module()}.
|
||||||
|
|
||||||
-type init_opts() :: {
|
|
||||||
Operations :: operations(),
|
|
||||||
LogicHandler :: atom(),
|
|
||||||
ValidatorMod :: module()
|
|
||||||
}.
|
|
||||||
|
|
||||||
-export_type([init_opts/0]).
|
-export_type([init_opts/0]).
|
||||||
|
|
||||||
-spec get_paths(LogicHandler :: atom()) -> [{'_',[{
|
-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
|
||||||
Path :: string(),
|
|
||||||
Handler :: atom(),
|
|
||||||
InitOpts :: init_opts()
|
|
||||||
}]}].
|
|
||||||
|
|
||||||
get_paths(LogicHandler) ->
|
get_paths(LogicHandler) ->
|
||||||
ValidatorState = prepare_validator(),
|
|
||||||
PreparedPaths = maps:fold(
|
PreparedPaths = maps:fold(
|
||||||
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
|
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
|
||||||
[{Path, Handler, Operations} | Acc]
|
[{Path, Handler, Operations} | Acc]
|
||||||
end,
|
end, [], group_paths()
|
||||||
[],
|
),
|
||||||
group_paths()
|
[{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
|
||||||
),
|
|
||||||
[
|
|
||||||
{'_',
|
|
||||||
[{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
|
|
||||||
}
|
|
||||||
].
|
|
||||||
|
|
||||||
group_paths() ->
|
group_paths() ->
|
||||||
maps:fold(
|
maps:fold(
|
||||||
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
|
||||||
case maps:find(Path, Acc) of
|
case maps:find(Path, Acc) of
|
||||||
{ok, PathInfo0 = #{operations := Operations0}} ->
|
{ok, PathInfo0 = #{operations := Operations0}} ->
|
||||||
Operations = Operations0#{Method => OperationID},
|
Operations = Operations0#{Method => OperationID},
|
||||||
PathInfo = PathInfo0#{operations => Operations},
|
PathInfo = PathInfo0#{operations => Operations},
|
||||||
Acc#{Path => PathInfo};
|
Acc#{Path => PathInfo};
|
||||||
error ->
|
error ->
|
||||||
Operations = #{Method => OperationID},
|
Operations = #{Method => OperationID},
|
||||||
PathInfo = #{handler => Handler, operations => Operations},
|
PathInfo = #{handler => Handler, operations => Operations},
|
||||||
Acc#{Path => PathInfo}
|
Acc#{Path => PathInfo}
|
||||||
end
|
end
|
||||||
end,
|
end, #{}, get_operations()).
|
||||||
#{},
|
|
||||||
get_operations()
|
|
||||||
).
|
|
||||||
|
|
||||||
get_operations() ->
|
get_operations() ->
|
||||||
#{
|
#{
|
||||||
@ -156,18 +135,3 @@ get_operations() ->
|
|||||||
handler => 'openapi_user_handler'
|
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").
|
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
-module(openapi_server).
|
-module(openapi_server).
|
||||||
|
|
||||||
|
-define(DEFAULT_LOGIC_HANDLER, openapi_logic_handler).
|
||||||
-define(DEFAULT_LOGIC_HANDLER, openapi_default_logic_handler).
|
|
||||||
|
|
||||||
-export([start/2]).
|
-export([start/2]).
|
||||||
|
-ignore_xref([start/2]).
|
||||||
|
|
||||||
-spec start( ID :: any(), #{
|
-spec start(term(), #{transport => tcp | ssl,
|
||||||
ip => inet:ip_address(),
|
transport_opts => ranch:opts(),
|
||||||
port => inet:port_number(),
|
protocol_opts => cowboy:opts(),
|
||||||
logic_handler => module(),
|
logic_handler => module()}) ->
|
||||||
net_opts => []
|
{ok, pid()} | {error, any()}.
|
||||||
}) -> {ok, pid()} | {error, any()}.
|
start(ID, Params) ->
|
||||||
|
Transport = maps:get(transport, Params, tcp),
|
||||||
start(ID, #{
|
TransportOpts = maps:get(transport_opts, Params, #{}),
|
||||||
ip := IP ,
|
ProtocolOpts = maps:get(procotol_opts, Params, #{}),
|
||||||
port := Port,
|
|
||||||
net_opts := NetOpts
|
|
||||||
} = Params) ->
|
|
||||||
{Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
|
|
||||||
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
|
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
|
||||||
ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
|
CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
|
||||||
CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
|
|
||||||
case Transport of
|
case Transport of
|
||||||
ssl ->
|
ssl ->
|
||||||
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
|
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
|
||||||
@ -28,33 +23,17 @@ start(ID, #{
|
|||||||
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
|
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
|
||||||
end.
|
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_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
|
DefaultOpts = get_default_opts(LogicHandler),
|
||||||
|
maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
|
||||||
|
|
||||||
get_cowboy_config(_LogicHandler, [], Opts) ->
|
get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
|
||||||
Opts;
|
maps:put(env, Env, AccIn);
|
||||||
|
get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
|
||||||
get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
|
Env = maps:merge(OldEnv, NewEnv),
|
||||||
NewEnv = case proplists:get_value(dispatch, Env) of
|
maps:put(env, Env, AccIn);
|
||||||
undefined -> [get_default_dispatch(LogicHandler) | Env];
|
get_cowboy_config(Key, Value, AccIn) ->
|
||||||
_ -> Env
|
maps:put(Key, Value, AccIn).
|
||||||
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_default_dispatch(LogicHandler) ->
|
get_default_dispatch(LogicHandler) ->
|
||||||
Paths = openapi_router:get_paths(LogicHandler),
|
Paths = openapi_router:get_paths(LogicHandler),
|
||||||
@ -62,6 +41,3 @@ get_default_dispatch(LogicHandler) ->
|
|||||||
|
|
||||||
get_default_opts(LogicHandler) ->
|
get_default_opts(LogicHandler) ->
|
||||||
#{env => get_default_dispatch(LogicHandler)}.
|
#{env => get_default_dispatch(LogicHandler)}.
|
||||||
|
|
||||||
store_key(Key, Value, Opts) ->
|
|
||||||
maps:put(Key, Value, Opts).
|
|
||||||
|
@ -1,280 +1,139 @@
|
|||||||
%% basic handler
|
%% basic handler
|
||||||
-module(openapi_store_handler).
|
-module(openapi_store_handler).
|
||||||
|
|
||||||
|
-behaviour(cowboy_rest).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/logger.hrl").
|
||||||
|
|
||||||
%% Cowboy REST callbacks
|
%% Cowboy REST callbacks
|
||||||
-export([allowed_methods/2]).
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
-export([allow_missing_post/2]).
|
-export([allowed_methods/2]).
|
||||||
-export([content_types_accepted/2]).
|
-export([content_types_accepted/2]).
|
||||||
-export([content_types_provided/2]).
|
-export([content_types_provided/2]).
|
||||||
-export([delete_resource/2]).
|
-export([delete_resource/2]).
|
||||||
-export([is_authorized/2]).
|
-export([is_authorized/2]).
|
||||||
-export([known_content_type/2]).
|
|
||||||
-export([malformed_request/2]).
|
|
||||||
-export([valid_content_headers/2]).
|
-export([valid_content_headers/2]).
|
||||||
-export([valid_entity_length/2]).
|
-export([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
|
|
||||||
%% Handlers
|
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
-export([handle_request_json/2]).
|
|
||||||
|
|
||||||
-record(state, {
|
-record(state,
|
||||||
operation_id :: openapi_api:operation_id(),
|
{operation_id :: openapi_api:operation_id(),
|
||||||
logic_handler :: atom(),
|
accept_callback :: openapi_logic_handler:accept_callback(),
|
||||||
validator_state :: jesse_state:state(),
|
provide_callback :: openapi_logic_handler:provide_callback(),
|
||||||
context=#{} :: #{}
|
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()) ->
|
-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
|
||||||
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
|
{cowboy_rest, cowboy_req:req(), state()}.
|
||||||
|
init(Req, {Operations, Module}) ->
|
||||||
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
|
|
||||||
Method = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
OperationID = maps:get(Method, Operations, undefined),
|
OperationID = maps:get(Method, Operations, undefined),
|
||||||
|
?LOG_INFO(#{what => "Attempt to process operation",
|
||||||
ValidatorState = ValidatorMod:get_validator_state(),
|
method => Method,
|
||||||
|
operation_id => OperationID}),
|
||||||
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
|
State = #state{operation_id = OperationID,
|
||||||
|
accept_callback = fun Module:accept_callback/4,
|
||||||
State = #state{
|
provide_callback = fun Module:provide_callback/4,
|
||||||
operation_id = OperationID,
|
api_key_handler = fun Module:authorize_api_key/2},
|
||||||
logic_handler = LogicHandler,
|
|
||||||
validator_state = ValidatorState
|
|
||||||
},
|
|
||||||
{cowboy_rest, Req, State}.
|
{cowboy_rest, Req, State}.
|
||||||
|
|
||||||
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
|
-spec allowed_methods(cowboy_req:req(), state()) ->
|
||||||
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
|
{[binary()], cowboy_req:req(), state()}.
|
||||||
|
allowed_methods(Req, #state{operation_id = 'DeleteOrder'} = State) ->
|
||||||
|
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'DeleteOrder'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"DELETE">>], Req, State};
|
{[<<"DELETE">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'GetInventory'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'GetInventory'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'GetOrderById'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'GetOrderById'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'PlaceOrder'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'PlaceOrder'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
|
||||||
allowed_methods(Req, State) ->
|
allowed_methods(Req, State) ->
|
||||||
{[], Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
|
-spec is_authorized(cowboy_req:req(), state()) ->
|
||||||
{
|
{true | {false, iodata()}, cowboy_req:req(), state()}.
|
||||||
Value :: true | {false, AuthHeader :: iodata()},
|
is_authorized(Req0,
|
||||||
Req :: cowboy_req:req(),
|
#state{operation_id = 'GetInventory' = OperationID,
|
||||||
State :: state()
|
api_key_handler = Handler} = State) ->
|
||||||
}.
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
is_authorized(
|
{true, Context, Req} ->
|
||||||
Req0,
|
{true, Req, State#state{context = Context}};
|
||||||
State = #state{
|
{false, AuthHeader, Req} ->
|
||||||
operation_id = 'GetInventory' = OperationID,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(Req, State) ->
|
is_authorized(Req, State) ->
|
||||||
{{false, <<"">>}, Req, State}.
|
{true, Req, State}.
|
||||||
is_authorized(Req, State) ->
|
|
||||||
{{false, <<"">>}, Req, State}.
|
|
||||||
|
|
||||||
-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_accepted(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), AcceptResource :: atom()}],
|
content_types_accepted(Req, #state{operation_id = 'DeleteOrder'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
{[], Req, State};
|
||||||
State :: state()
|
content_types_accepted(Req, #state{operation_id = 'GetInventory'} = State) ->
|
||||||
}.
|
{[], Req, State};
|
||||||
|
content_types_accepted(Req, #state{operation_id = 'GetOrderById'} = State) ->
|
||||||
content_types_accepted(Req, State) ->
|
{[], Req, State};
|
||||||
|
content_types_accepted(Req, #state{operation_id = 'PlaceOrder'} = State) ->
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{<<"application/json">>, handle_type_accepted}
|
||||||
], Req, State}.
|
], Req, State};
|
||||||
|
content_types_accepted(Req, State) ->
|
||||||
-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
|
{[], Req, 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};
|
|
||||||
|
|
||||||
|
-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) ->
|
valid_content_headers(Req, State) ->
|
||||||
{false, Req, State}.
|
{false, Req, State}.
|
||||||
|
|
||||||
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_provided(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), ProvideResource :: atom()}],
|
content_types_provided(Req, #state{operation_id = 'DeleteOrder'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
{[], Req, State};
|
||||||
State :: state()
|
content_types_provided(Req, #state{operation_id = 'GetInventory'} = State) ->
|
||||||
}.
|
|
||||||
|
|
||||||
content_types_provided(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{<<"application/json">>, handle_type_provided}
|
||||||
], Req, State}.
|
], Req, State};
|
||||||
|
content_types_provided(Req, #state{operation_id = 'GetOrderById'} = State) ->
|
||||||
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
|
{[
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
{<<"application/xml">>, handle_type_provided},
|
||||||
|
{<<"application/json">>, handle_type_provided}
|
||||||
malformed_request(Req, State) ->
|
], Req, State};
|
||||||
{false, Req, State}.
|
content_types_provided(Req, #state{operation_id = 'PlaceOrder'} = State) ->
|
||||||
|
{[
|
||||||
-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
|
{<<"application/xml">>, handle_type_provided},
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
{<<"application/json">>, handle_type_provided}
|
||||||
|
], Req, State};
|
||||||
allow_missing_post(Req, State) ->
|
content_types_provided(Req, State) ->
|
||||||
{false, Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
|
|
||||||
processed_response().
|
|
||||||
|
|
||||||
|
-spec delete_resource(cowboy_req:req(), state()) ->
|
||||||
|
{boolean(), cowboy_req:req(), state()}.
|
||||||
delete_resource(Req, State) ->
|
delete_resource(Req, State) ->
|
||||||
handle_request_json(Req, State).
|
case handle_type_accepted(Req, State) of
|
||||||
|
true ->
|
||||||
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
|
{true, Req, State};
|
||||||
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
|
_ ->
|
||||||
|
{false, Req, 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.
|
end.
|
||||||
|
|
||||||
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
|
-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).
|
||||||
|
|
||||||
handle_request_json(
|
-spec handle_type_provided(cowboy_req:req(), state()) ->
|
||||||
Req0,
|
{cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
|
||||||
State = #state{
|
handle_type_provided(Req, #state{operation_id = OperationID,
|
||||||
operation_id = OperationID,
|
provide_callback = Handler} = State) ->
|
||||||
logic_handler = LogicHandler,
|
Handler(store, OperationID, Req, State#state.context).
|
||||||
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).
|
|
||||||
|
@ -1,447 +1,220 @@
|
|||||||
%% basic handler
|
%% basic handler
|
||||||
-module(openapi_user_handler).
|
-module(openapi_user_handler).
|
||||||
|
|
||||||
|
-behaviour(cowboy_rest).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/logger.hrl").
|
||||||
|
|
||||||
%% Cowboy REST callbacks
|
%% Cowboy REST callbacks
|
||||||
-export([allowed_methods/2]).
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
-export([allow_missing_post/2]).
|
-export([allowed_methods/2]).
|
||||||
-export([content_types_accepted/2]).
|
-export([content_types_accepted/2]).
|
||||||
-export([content_types_provided/2]).
|
-export([content_types_provided/2]).
|
||||||
-export([delete_resource/2]).
|
-export([delete_resource/2]).
|
||||||
-export([is_authorized/2]).
|
-export([is_authorized/2]).
|
||||||
-export([known_content_type/2]).
|
|
||||||
-export([malformed_request/2]).
|
|
||||||
-export([valid_content_headers/2]).
|
-export([valid_content_headers/2]).
|
||||||
-export([valid_entity_length/2]).
|
-export([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
|
|
||||||
%% Handlers
|
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
|
||||||
-export([handle_request_json/2]).
|
|
||||||
|
|
||||||
-record(state, {
|
-record(state,
|
||||||
operation_id :: openapi_api:operation_id(),
|
{operation_id :: openapi_api:operation_id(),
|
||||||
logic_handler :: atom(),
|
accept_callback :: openapi_logic_handler:accept_callback(),
|
||||||
validator_state :: jesse_state:state(),
|
provide_callback :: openapi_logic_handler:provide_callback(),
|
||||||
context=#{} :: #{}
|
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()) ->
|
-spec init(cowboy_req:req(), openapi_router:init_opts()) ->
|
||||||
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
|
{cowboy_rest, cowboy_req:req(), state()}.
|
||||||
|
init(Req, {Operations, Module}) ->
|
||||||
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
|
|
||||||
Method = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
OperationID = maps:get(Method, Operations, undefined),
|
OperationID = maps:get(Method, Operations, undefined),
|
||||||
|
?LOG_INFO(#{what => "Attempt to process operation",
|
||||||
ValidatorState = ValidatorMod:get_validator_state(),
|
method => Method,
|
||||||
|
operation_id => OperationID}),
|
||||||
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
|
State = #state{operation_id = OperationID,
|
||||||
|
accept_callback = fun Module:accept_callback/4,
|
||||||
State = #state{
|
provide_callback = fun Module:provide_callback/4,
|
||||||
operation_id = OperationID,
|
api_key_handler = fun Module:authorize_api_key/2},
|
||||||
logic_handler = LogicHandler,
|
|
||||||
validator_state = ValidatorState
|
|
||||||
},
|
|
||||||
{cowboy_rest, Req, State}.
|
{cowboy_rest, Req, State}.
|
||||||
|
|
||||||
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
|
-spec allowed_methods(cowboy_req:req(), state()) ->
|
||||||
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
|
{[binary()], cowboy_req:req(), state()}.
|
||||||
|
allowed_methods(Req, #state{operation_id = 'CreateUser'} = State) ->
|
||||||
|
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'CreateUser'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'CreateUsersWithArrayInput'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'CreateUsersWithListInput'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"POST">>], Req, State};
|
{[<<"POST">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'DeleteUser'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'DeleteUser'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"DELETE">>], Req, State};
|
{[<<"DELETE">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'GetUserByName'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'GetUserByName'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'LoginUser'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'LoginUser'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'LogoutUser'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'LogoutUser'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"GET">>], Req, State};
|
{[<<"GET">>], Req, State};
|
||||||
|
allowed_methods(Req, #state{operation_id = 'UpdateUser'} = State) ->
|
||||||
allowed_methods(
|
|
||||||
Req,
|
|
||||||
State = #state{
|
|
||||||
operation_id = 'UpdateUser'
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{[<<"PUT">>], Req, State};
|
{[<<"PUT">>], Req, State};
|
||||||
|
|
||||||
allowed_methods(Req, State) ->
|
allowed_methods(Req, State) ->
|
||||||
{[], Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
|
-spec is_authorized(cowboy_req:req(), state()) ->
|
||||||
{
|
{true | {false, iodata()}, cowboy_req:req(), state()}.
|
||||||
Value :: true | {false, AuthHeader :: iodata()},
|
is_authorized(Req0,
|
||||||
Req :: cowboy_req:req(),
|
#state{operation_id = 'CreateUser' = OperationID,
|
||||||
State :: state()
|
api_key_handler = Handler} = State) ->
|
||||||
}.
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
is_authorized(
|
{true, Context, Req} ->
|
||||||
Req0,
|
{true, Req, State#state{context = Context}};
|
||||||
State = #state{
|
{false, AuthHeader, Req} ->
|
||||||
operation_id = 'CreateUser' = OperationID,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'CreateUsersWithArrayInput' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'CreateUsersWithArrayInput' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'CreateUsersWithListInput' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'CreateUsersWithListInput' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'DeleteUser' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'DeleteUser' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'LogoutUser' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'LogoutUser' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(
|
is_authorized(Req0,
|
||||||
Req0,
|
#state{operation_id = 'UpdateUser' = OperationID,
|
||||||
State = #state{
|
api_key_handler = Handler} = State) ->
|
||||||
operation_id = 'UpdateUser' = OperationID,
|
case openapi_auth:authorize_api_key(Handler, OperationID, header, "authorization", Req0) of
|
||||||
logic_handler = LogicHandler
|
{true, Context, Req} ->
|
||||||
}
|
{true, Req, State#state{context = Context}};
|
||||||
) ->
|
{false, AuthHeader, Req} ->
|
||||||
From = header,
|
{{false, AuthHeader}, Req, State}
|
||||||
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}
|
|
||||||
end;
|
end;
|
||||||
is_authorized(Req, State) ->
|
is_authorized(Req, State) ->
|
||||||
{{false, <<"">>}, Req, State}.
|
{true, Req, State}.
|
||||||
is_authorized(Req, State) ->
|
|
||||||
{{false, <<"">>}, Req, State}.
|
|
||||||
|
|
||||||
-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_accepted(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), AcceptResource :: atom()}],
|
content_types_accepted(Req, #state{operation_id = 'CreateUser'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
|
||||||
State :: state()
|
|
||||||
}.
|
|
||||||
|
|
||||||
content_types_accepted(Req, State) ->
|
|
||||||
{[
|
{[
|
||||||
{<<"application/json">>, handle_request_json}
|
{<<"application/json">>, handle_type_accepted}
|
||||||
], Req, State}.
|
], Req, State};
|
||||||
|
content_types_accepted(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
|
||||||
-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
|
{[
|
||||||
{Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
|
{<<"application/json">>, handle_type_accepted}
|
||||||
|
], Req, State};
|
||||||
valid_content_headers(
|
content_types_accepted(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
|
||||||
Req0,
|
{[
|
||||||
State = #state{
|
{<<"application/json">>, handle_type_accepted}
|
||||||
operation_id = 'CreateUser'
|
], Req, State};
|
||||||
}
|
content_types_accepted(Req, #state{operation_id = 'DeleteUser'} = State) ->
|
||||||
) ->
|
{[], Req, State};
|
||||||
Headers = [],
|
content_types_accepted(Req, #state{operation_id = 'GetUserByName'} = State) ->
|
||||||
{Result, Req} = validate_headers(Headers, Req0),
|
{[], Req, State};
|
||||||
{Result, Req, State};
|
content_types_accepted(Req, #state{operation_id = 'LoginUser'} = State) ->
|
||||||
|
{[], Req, State};
|
||||||
valid_content_headers(
|
content_types_accepted(Req, #state{operation_id = 'LogoutUser'} = State) ->
|
||||||
Req0,
|
{[], Req, State};
|
||||||
State = #state{
|
content_types_accepted(Req, #state{operation_id = 'UpdateUser'} = State) ->
|
||||||
operation_id = 'CreateUsersWithArrayInput'
|
{[
|
||||||
}
|
{<<"application/json">>, handle_type_accepted}
|
||||||
) ->
|
], Req, State};
|
||||||
Headers = [],
|
content_types_accepted(Req, State) ->
|
||||||
{Result, Req} = validate_headers(Headers, Req0),
|
{[], Req, State}.
|
||||||
{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};
|
|
||||||
|
|
||||||
|
-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) ->
|
valid_content_headers(Req, State) ->
|
||||||
{false, Req, State}.
|
{false, Req, State}.
|
||||||
|
|
||||||
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
|
-spec content_types_provided(cowboy_req:req(), state()) ->
|
||||||
{
|
{[{binary(), atom()}], cowboy_req:req(), state()}.
|
||||||
Value :: [{binary(), ProvideResource :: atom()}],
|
content_types_provided(Req, #state{operation_id = 'CreateUser'} = State) ->
|
||||||
Req :: cowboy_req:req(),
|
{[], Req, State};
|
||||||
State :: state()
|
content_types_provided(Req, #state{operation_id = 'CreateUsersWithArrayInput'} = State) ->
|
||||||
}.
|
{[], Req, State};
|
||||||
|
content_types_provided(Req, #state{operation_id = 'CreateUsersWithListInput'} = State) ->
|
||||||
content_types_provided(Req, 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}
|
{<<"application/xml">>, handle_type_provided},
|
||||||
], Req, State}.
|
{<<"application/json">>, handle_type_provided}
|
||||||
|
], Req, State};
|
||||||
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
|
content_types_provided(Req, #state{operation_id = 'LoginUser'} = State) ->
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
{[
|
||||||
|
{<<"application/xml">>, handle_type_provided},
|
||||||
malformed_request(Req, State) ->
|
{<<"application/json">>, handle_type_provided}
|
||||||
{false, Req, State}.
|
], Req, State};
|
||||||
|
content_types_provided(Req, #state{operation_id = 'LogoutUser'} = State) ->
|
||||||
-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
|
{[], Req, State};
|
||||||
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
|
content_types_provided(Req, #state{operation_id = 'UpdateUser'} = State) ->
|
||||||
|
{[], Req, State};
|
||||||
allow_missing_post(Req, State) ->
|
content_types_provided(Req, State) ->
|
||||||
{false, Req, State}.
|
{[], Req, State}.
|
||||||
|
|
||||||
-spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
|
|
||||||
processed_response().
|
|
||||||
|
|
||||||
|
-spec delete_resource(cowboy_req:req(), state()) ->
|
||||||
|
{boolean(), cowboy_req:req(), state()}.
|
||||||
delete_resource(Req, State) ->
|
delete_resource(Req, State) ->
|
||||||
handle_request_json(Req, State).
|
case handle_type_accepted(Req, State) of
|
||||||
|
true ->
|
||||||
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
|
{true, Req, State};
|
||||||
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
|
_ ->
|
||||||
|
{false, Req, 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.
|
end.
|
||||||
|
|
||||||
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
|
-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).
|
||||||
|
|
||||||
handle_request_json(
|
-spec handle_type_provided(cowboy_req:req(), state()) ->
|
||||||
Req0,
|
{cowboy_req:resp_body(), cowboy_req:req(), openapi_logic_handler:context()}.
|
||||||
State = #state{
|
handle_type_provided(Req, #state{operation_id = OperationID,
|
||||||
operation_id = OperationID,
|
provide_callback = Handler} = State) ->
|
||||||
logic_handler = LogicHandler,
|
Handler(user, OperationID, Req, State#state.context).
|
||||||
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).
|
|
||||||
|
@ -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, <<C, _/binary>>, _Acc) when C > 127 ->
|
|
||||||
error(badarg);
|
|
||||||
|
|
||||||
to_case(Case = lower, <<C, Rest/binary>>, Acc) ->
|
|
||||||
to_case(Case, Rest, <<Acc/binary, (to_lower_char(C))>>);
|
|
||||||
|
|
||||||
to_case(Case = upper, <<C, Rest/binary>>, Acc) ->
|
|
||||||
to_case(Case, Rest, <<Acc/binary, (to_upper_char(C))>>).
|
|
||||||
|
|
||||||
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.
|
|
Loading…
x
Reference in New Issue
Block a user