diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 099bffd..119f547 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,34 @@ --- name: build -on: - push: - branches: - - master - pull_request: - branches: - - master +on: [push, pull_request] jobs: ci: - name: Run checks and tests over ${{matrix.otp_vsn}} and ${{matrix.os}} - runs-on: ${{matrix.os}} + name: Run checks and tests over ${{matrix.otp_vsn}} + runs-on: ubuntu-22.04 strategy: matrix: - otp_vsn: [23, 24] - os: [ubuntu-latest] + otp_vsn: ['24', '25', '26'] + rebar3_vsn: ['3.22'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: erlef/setup-beam@v1 with: otp-version: ${{matrix.otp_vsn}} - rebar3-version: '3.14' + rebar3-version: ${{matrix.rebar3_vsn}} + - name: Format check + run: rebar3 format --verify - run: rebar3 test + - name: test our example + run: | + cd example + rebar3 release + _build/default/rel/example/bin/example daemon + _build/default/rel/example/bin/example ping + - name: Format check + run: | + cd example + rebar3 format --verify + - name: check our example + run: | + cd example + rebar3 test diff --git a/.gitignore b/.gitignore index ea26b62..ec92b13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,6 @@ -_rel/ -relx -*.d -.eunit -/deps -*.o -*.beam -*.plt +_* erl_crash.dump -ebin -rel/example_project -.concrete/DEV_MODE .rebar -.erlang.mk -log +doc/ +.rebar3 logs -doc -_build -.rebar3/ -example/compile_commands.json -example/_checkouts/ diff --git a/LICENSE b/LICENSE index 8f71f43..d5dd862 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +192,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/README.md b/README.md index 92fa596..3953d77 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,40 @@ - + # cowboy-swagger -[Swagger](http://swagger.io/) integration for [Cowboy](https://github.com/ninenines/cowboy) (built on [trails](https://github.com/inaka/cowboy-trails)). + +[Swagger](https://swagger.io/) integration for [Cowboy](https://github.com/ninenines/cowboy) (built on [trails](https://github.com/inaka/cowboy-trails)). ![build](https://github.com/inaka/cowboy_swagger/workflows/build/badge.svg) ## Contact Us + If you find any **bugs** or have a **problem** while using this library, please -[open an issue](https://github.com/inaka/elvis/issues/new) in this repo +[open an issue](https://github.com/inaka/cowboy_swagger/issues/new) in this repo (or a pull request :)). -## Requirements -Cowboy Swagger requires Erlang 18+ after 0.1.0 version - ## Why Cowboy Swagger? + Simple, because there isn't a tool in Erlang to document Cowboy RESTful APIs easy and fast, and to improve development productivity. With `cowboy_swagger` is possible to integrate Swagger to your Erlang projects that use Cowboy as a web server. It is extremely easy to use, and with just a few steps you'll have a nice Web documentation for your RESTful APIs. -To learn a bit more about Swagger, please check this [blog post](http://inaka.net/blog/2015/06/23/erlang-swagger-2015/). +To learn a bit more about Swagger, please check this [blog post](https://web.archive.org/web/20161110235900/https://inaka.net/blog/2015/06/23/erlang-swagger-2015/). ## How to Use it? + This is the best part. It is extremely easy. ### 1. Document each Cowboy Handler + Because `cowboy_swagger` runs on top of `trails`, the first thing that you have to do is document all about your handler within the trails metadata. Keep in mind that all fields defined within each method into the metadata must be compliant with the -[Swagger specification](http://swagger.io/specification). +[Swagger specification](https://swagger.io/specification). -For example, suppose that you have `example_echo_handler`, so it must implement the `trails/0` -callback from `trails_handler` behaviour: +For example, suppose that you have `example_echo_handler`, so it must implement the +`c:trails_handler:trails/0` callback: ```erlang trails() -> @@ -58,9 +60,10 @@ trails() -> [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. ``` -To get a better idea of how your handler should look like, please check [here](./example/src/example_echo_handler.erl). +To get a better idea of how your handler should look like, please check [`example/src/example_echo_handler.erl`](https://github.com/inaka/cowboy_swagger/blob/master/example/src/example_echo_handler.erl). ### 2. Include cowboy_swagger in your app + First, you need to include `cowboy_swagger_handler` module in your list of trails to be compiled. ```erlang @@ -74,7 +77,7 @@ trails:store(Trails), Dispatch = trails:single_host_compile(Trails), ``` -The snippet of code above is usually placed when you start `cowboy`. Check it [here](./example/src/example.erl#L31). +The snippet of code above is usually placed when you start `cowboy`. Check it [here](https://github.com/inaka/cowboy_swagger/blob/master/example/src/example.erl). Then add `cowboy_swagger` to the list of apps to be loaded in your `*.app.src` file. @@ -140,10 +143,10 @@ Additionally, `cowboy_swagger` can be configured/customized from a `*.config` fi ### Definitions -[Definitions](http://swagger.io/specification/#definitionsObject) can be used for describing -[parameters](http://swagger.io/specification/#parametersDefinitionsObject), -[responses](http://swagger.io/specification/#responsesDefinitionsObject) and -[security](http://swagger.io/specification/#securityDefinitionsObject) schemas. +[Definitions](https://swagger.io/specification/#definitionsObject) can be used for describing +[parameters](https://swagger.io/specification/#parametersDefinitionsObject), +[responses](https://swagger.io/specification/#responsesDefinitionsObject) and +[security](https://swagger.io/specification/#securityDefinitionsObject) schemas. For adding definitions to your app, you have 2 choices: @@ -155,6 +158,7 @@ Let's say you want to describe a `POST` call to a `newspapers` endpoint that req `name` and `description` fields only, you can do it like this: **Option 1:** + ```erlang [ ... % other configurations , { cowboy_swagger @@ -204,7 +208,6 @@ trails() -> ... ``` - Now in your handler's trails callback function you can use it: ```erlang @@ -241,4 +244,5 @@ model in swagger-ui, so client developers will know exactly what parameters the API expects for every endpoint. ## Example -For more information about `cowboy_swagger` and how to use it, please check this [Example](./example). + +For more information about `cowboy_swagger` and how to use it, please check this [Example](https://github.com/inaka/cowboy_swagger/tree/master/example). diff --git a/elvis.config b/elvis.config index 8acb4b5..69034d3 100644 --- a/elvis.config +++ b/elvis.config @@ -1,23 +1,15 @@ -[ - { - elvis, - [ - {config, - [#{dirs => ["src", "test"], - filter => "*.erl", - ruleset => erl_files, - rules => [{elvis_style, atom_naming_convention, #{ regex => "^(([a-z][a-z0-9]*_?)*(_SUITE)?|basePath|swagger_2_0|openapi_3_0_0)$" }}] - }, - #{dirs => ["."], - filter => "rebar.config", - ruleset => rebar_config - }, - #{dirs => ["."], - filter => "elvis.config", - ruleset => elvis_config - } - ] - } - ] - } -]. +[{elvis, + [{config, + [#{dirs => ["src", "test"], + filter => "*.erl", + ruleset => erl_files, + rules => + [{elvis_style, + atom_naming_convention, + #{regex => "^(([a-z][a-z0-9]*_?)*(_SUITE)?|basePath|swagger_2_0|openapi_3_0_0)$"}}]}, + #{dirs => ["."], + filter => "rebar.config", + ruleset => rebar_config}, + #{dirs => ["."], + filter => "elvis.config", + ruleset => elvis_config}]}]}]. diff --git a/example/config/sys.config b/example/config/sys.config index 14d836b..04cb4eb 100644 --- a/example/config/sys.config +++ b/example/config/sys.config @@ -1,13 +1,4 @@ -[ { example - , [{http_port, 8080}] - } -, { cowboy_swagger - , [ {static_files, "priv/swagger"} - , { global_spec - , #{ openapi => "3.0.0" - , info => #{title => "Example API"} - } - } - ] - } -]. +[{example, [{http_port, 8080}]}, + {cowboy_swagger, + [{static_files, "priv/swagger"}, + {global_spec, #{openapi => "3.0.0", info => #{title => "Example API"}}}]}]. diff --git a/example/config/vm.args b/example/config/vm.args new file mode 100644 index 0000000..5713fd1 --- /dev/null +++ b/example/config/vm.args @@ -0,0 +1,2 @@ +-name example@127.0.0.1 +-setcookie bogus diff --git a/example/elvis.config b/example/elvis.config deleted file mode 100644 index d00fae0..0000000 --- a/example/elvis.config +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - elvis, - [ - {config, - [#{dirs => ["src"], - filter => "*.erl", - rules => [{elvis_style, line_length, #{limit => 80, - skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace}, - {elvis_style, macro_names}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, - {right, "++"}, - {left, "++"}]}}, - {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, god_modules, #{limit => 25}}, - {elvis_style, no_if_expression}, - {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - { - elvis_style, - module_naming_convention, - #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$", - ignore => []} - }, - {elvis_style, state_record_and_type}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} - ] - }, - #{dirs => ["."], - filter => "Makefile", - rules => [{elvis_project, no_deps_master_erlang_mk, #{ignore => []}}, - {elvis_project, git_for_deps_erlang_mk, #{regex => "^https://.*"}}] - }, - #{dirs => ["."], - filter => "rebar.config", - rules => [{elvis_project, no_deps_master_rebar, #{ignore => []}}, - {elvis_project, git_for_deps_rebar, #{ignore => []}}] - }, - #{dirs => ["."], - filter => "elvis.config", - rules => [{elvis_project, old_configuration_format}] - } - ] - } - ] - } -]. diff --git a/example/rebar.config b/example/rebar.config index 78a7531..6879a8a 100644 --- a/example/rebar.config +++ b/example/rebar.config @@ -1,41 +1,47 @@ -%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 ft=erlang et - -%% == Erlang Compiler == - -%% Erlang compiler options -{erl_opts, [ warn_unused_vars - , warn_export_all - , warn_shadow_vars - , warn_unused_import - , warn_unused_function - , warn_bif_clash - , warn_unused_record - , warn_deprecated_function - , warn_obsolete_guard - , strict_validation - , warn_export_vars - , warn_exported_vars - , warn_untyped_record - , debug_info]}. - -%% == Dependencies == - -{deps, [ {mixer, "1.2.0", {pkg, inaka_mixer}} - , cowboy_swagger - ]}. +%% == Compiler and Profiles == + +{erl_opts, + [warn_unused_import, warn_export_vars, warnings_as_errors, verbose, report, debug_info]}. + +{minimum_otp_vsn, "23"}. + +{alias, [{test, [compile, format, hank, lint, xref, dialyzer]}]}. + +%% == Dependencies and plugins == + +{deps, [{mixer, "1.2.0", {pkg, inaka_mixer}}, cowboy_swagger]}. + +{project_plugins, + [{rebar3_hank, "~> 1.4.0"}, {rebar3_format, "~> 1.3.0"}, {rebar3_lint, "~> 3.0.1"}]}. + +%% == Format == + +{format, [{files, ["*.config", "src/*"]}]}. + +%% == Hank == + +{hank, [{ignore, ["_build/**", "_checkouts"]}]}. + +%% == Dialyzer + XRef == + +{dialyzer, + [{warnings, [no_return, underspecs, unmatched_returns, error_handling, unknown]}]}. + +{xref_checks, + [undefined_function_calls, deprecated_function_calls, deprecated_functions]}. + +{xref_extra_paths, ["test/**"]}. %% == Release == -{relx, [{release, {example, "0.1"}, [example]}, - {sys_config, "./config/sys.config"}, - {extended_start_script, true}, - {overlay, - [ - {copy, "./_checkouts/cowboy_swagger/priv/swagger", "priv/swagger"} - %% The above should be changes to the following when cowboy_swagger is +{relx, + [{include_src, false}, + {extended_start_script, true}, + {release, {example, "0.1"}, [example, sasl]}, + {sys_config, "./config/sys.config"}, + {vm_args, "./config/vm.args"}, + {overlay, [{copy, "./_checkouts/cowboy_swagger/priv/swagger", "priv/swagger"}]}]}. + + %% The above should be changed to the following when cowboy_swagger is %% used as a normal dependency (instead of _checkouts): %% {copy, "./_build/default/lib/cowboy_swagger/priv/swagger", "priv/swagger"} - ] - } - ]}. diff --git a/example/src/example.app.src b/example/src/example.app.src index a9579c9..5240aa8 100644 --- a/example/src/example.app.src +++ b/example/src/example.app.src @@ -1,19 +1,10 @@ -{application, example, - [ - {description, "Cowboy Swagger OpenAPI 3.0.0 Basic Example."}, +{application, + example, + [{description, "Cowboy Swagger OpenAPI 3.0.0 Basic Example."}, {vsn, "0.1"}, - {applications, - [kernel, - stdlib, - jsx, - cowboy, - trails, - cowboy_swagger - ]}, + {applications, [kernel, stdlib, jsx, cowboy, trails, cowboy_swagger]}, {modules, []}, {mod, {example, []}}, {registered, []}, {start_phases, [{start_trails_http, []}]}, - {build_tools,["rebar3"]} - ] -}. + {build_tools, ["rebar3"]}]}. diff --git a/example/src/example.erl b/example/src/example.erl index fcd7494..d1e98e6 100644 --- a/example/src/example.erl +++ b/example/src/example.erl @@ -6,37 +6,40 @@ -export([stop/1]). -export([start_phase/3]). +-hank([unnecessary_function_arguments]). + %% application %% @doc Starts the application start() -> - application:ensure_all_started(example). + application:ensure_all_started(example). %% @doc Stops the application stop() -> - application:stop(example). + application:stop(example). %% behaviour %% @private start(_StartType, _StartArgs) -> - example_sup:start_link(). + example_sup:start_link(). %% @private stop(_State) -> - ok = cowboy:stop_listener(example_http). + ok = cowboy:stop_listener(example_http). --spec start_phase(atom(), application:start_type(), []) -> ok | {error, term()}. +-spec start_phase(atom(), application:start_type(), []) -> ok. start_phase(start_trails_http, _StartType, []) -> - {ok, Port} = application:get_env(example, http_port), - Trails = trails:trails([example_echo_handler, - example_description_handler, - cowboy_swagger_handler]), - trails:store(Trails), - Dispatch = trails:single_host_compile(Trails), - RanchOptions = [{port, Port}], - CowboyOptions = #{ env => #{dispatch => Dispatch} - , compress => true - , timeout => 12000 - }, - - {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), - ok. + {ok, Port} = application:get_env(example, http_port), + Trails = + trails:trails([example_echo_handler, + example_description_handler, + cowboy_swagger_handler]), + trails:store(Trails), + Dispatch = trails:single_host_compile(Trails), + RanchOptions = [{port, Port}], + CowboyOptions = + #{env => #{dispatch => Dispatch}, + compress => true, + timeout => 12000}, + + {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), + ok. diff --git a/example/src/example_default.erl b/example/src/example_default.erl index 7a226de..5c980ee 100644 --- a/example/src/example_default.erl +++ b/example/src/example_default.erl @@ -2,29 +2,24 @@ -behaviour(cowboy_rest). --export([ init/2 - , rest_init/2 - , content_types_accepted/2 - , content_types_provided/2 - , forbidden/2 - , resource_exists/2 - ]). +-export([init/2, rest_init/2, content_types_accepted/2, content_types_provided/2, + forbidden/2, resource_exists/2]). %% cowboy init(Req, _Opts) -> - {cowboy_rest, Req, #{}}. + {cowboy_rest, Req, #{}}. rest_init(Req, _Opts) -> - {ok, Req, #{}}. + {ok, Req, #{}}. content_types_accepted(Req, State) -> - {[{'*', handle_put}], Req, State}. + {[{'*', handle_put}], Req, State}. content_types_provided(Req, State) -> - {[{<<"text/plain">>, handle_get}], Req, State}. + {[{<<"text/plain">>, handle_get}], Req, State}. forbidden(Req, State) -> - {false, Req, State}. + {false, Req, State}. resource_exists(Req, State) -> - {true, Req, State}. + {true, Req, State}. diff --git a/example/src/example_description_handler.erl b/example/src/example_description_handler.erl index 40a0ba2..90eb6b0 100644 --- a/example/src/example_description_handler.erl +++ b/example/src/example_description_handler.erl @@ -3,51 +3,37 @@ -behaviour(cowboy_rest). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {example_default, - [ - init/2, + +-mixin([{example_default, + [init/2, rest_init/2, content_types_accepted/2, content_types_provided/2, - resource_exists/2 - ]} - ]). + resource_exists/2]}]). --export([ allowed_methods/2 - , handle_get/2 - ]). +-export([allowed_methods/2, handle_get/2]). %trails -behaviour(trails_handler). + -export([trails/0]). trails() -> - Metadata = - #{get => - #{tags => ["example"], - description => "Retrives trails's server description", - responses => #{ - <<"200">> => #{ - description => <<"Retrives trails's server description 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - } - }, - [trails:trail("/description", example_description_handler, [], Metadata)]. + Metadata = + #{get => + #{tags => ["example"], + description => "Retrives trails's server description", + responses => + #{<<"200">> => + #{description => <<"Retrives trails's server description 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}}, + [trails:trail("/description", example_description_handler, [], Metadata)]. %% cowboy allowed_methods(Req, State) -> - {[<<"GET">>], Req, State}. + {[<<"GET">>], Req, State}. %% internal handle_get(Req, State) -> - Body = trails:all(), - {io_lib:format("~p~n", [Body]), Req, State}. + Body = trails:all(), + {io_lib:format("~p~n", [Body]), Req, State}. diff --git a/example/src/example_echo_handler.erl b/example/src/example_echo_handler.erl index 2bea107..735e99d 100644 --- a/example/src/example_echo_handler.erl +++ b/example/src/example_echo_handler.erl @@ -3,75 +3,54 @@ -behaviour(cowboy_rest). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {example_default, - [ - init/2, + +-mixin([{example_default, + [init/2, rest_init/2, content_types_accepted/2, content_types_provided/2, - resource_exists/2 - ]} - ]). + resource_exists/2]}]). --export([ allowed_methods/2 - , handle_put/2 - , handle_get/2 - ]). +-export([allowed_methods/2, handle_put/2, handle_get/2]). %trails -behaviour(trails_handler). + -export([trails/0]). trails() -> - Metadata = - #{get => - #{tags => ["echo"], - description => "Gets echo var from the server", - responses => #{ - <<"200">> => #{ - description => <<"Gets echo var from the server 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - }, - put => - #{tags => ["echo"], - description => "Sets echo var in the server", - parameters => [ - #{name => <<"echo">>, - description => <<"Echo message">>, - in => <<"path">>, - required => false, - schema => - #{type => string, - example => <<"Hello, World!">> - } - } - ] - } - }, - [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. + Metadata = + #{get => + #{tags => ["echo"], + description => "Gets echo var from the server", + responses => + #{<<"200">> => + #{description => <<"Gets echo var from the server 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}, + put => + #{tags => ["echo"], + description => "Sets echo var in the server", + parameters => + [#{name => <<"echo">>, + description => <<"Echo message">>, + in => <<"path">>, + required => false, + schema => #{type => string, example => <<"Hello, World!">>}}]}}, + [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. %% cowboy allowed_methods(Req, State) -> - {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. + {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. %% internal handle_get(Req, State) -> - Echo = application:get_env(example, echo, ""), - Body = [<<"You Get an echo!">> , Echo], - {Body, Req, State}. + Echo = application:get_env(example, echo, ""), + Body = [<<"You Get an echo!">>, Echo], + {Body, Req, State}. handle_put(Req, State) -> - Echo = cowboy_req:binding(echo, Req, ""), - application:set_env(example, echo, Echo), - Body = [<<"You put an echo! ">> , Echo], - Req1 = cowboy_req:set_resp_body(Body, Req), - {true, Req1, State}. + Echo = cowboy_req:binding(echo, Req, ""), + application:set_env(example, echo, Echo), + Body = [<<"You put an echo! ">>, Echo], + Req1 = cowboy_req:set_resp_body(Body, Req), + {true, Req1, State}. diff --git a/example/src/example_sup.erl b/example/src/example_sup.erl index a910efe..1594e59 100644 --- a/example/src/example_sup.erl +++ b/example/src/example_sup.erl @@ -7,8 +7,8 @@ %% admin api start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, {}). + supervisor:start_link({local, ?MODULE}, ?MODULE, {}). %% behaviour callbacks init({}) -> - {ok, {{one_for_one, 5, 10}, []} }. + {ok, {{one_for_one, 5, 10}, []}}. diff --git a/rebar.config b/rebar.config index f554635..1cb6bc8 100644 --- a/rebar.config +++ b/rebar.config @@ -1,118 +1,54 @@ -%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 ft=erlang et - -%% == Erlang Compiler == - -%% Erlang compiler options -{erl_opts, [ warn_unused_vars - , warn_export_all - , warn_shadow_vars - , warn_unused_import - , warn_unused_function - , warn_bif_clash - , warn_unused_record - , warn_deprecated_function - , warn_obsolete_guard - , strict_validation - , warn_export_vars - , warn_exported_vars - , warn_missing_spec - , warn_untyped_record - , debug_info]}. - -{minimum_otp_vsn, "18"}. - -{profiles, [ - {test, [ - {deps, [ {mixer, "1.2.0", {pkg, inaka_mixer}} - , {shotgun, "0.5.0"} - ]} - ]} -]}. - -{alias, [{test, [xref, dialyzer, hank, ct, cover, edoc]}]}. - -%% == Common Test == - -{ct_compile_opts, [ warn_unused_vars - , warn_export_all - , warn_shadow_vars - , warn_unused_import - , warn_unused_function - , warn_bif_clash - , warn_unused_record - , warn_deprecated_function - , warn_obsolete_guard - , strict_validation - , warn_export_vars - , warn_exported_vars - , warn_missing_spec - , warn_untyped_record - , debug_info]}. - -{ct_opts, [ {sys_config, ["./test/test.config"]} - , {verbose, true}]}. - -%% == Cover == - -{cover_enabled, true}. - -{cover_opts, [verbose]}. - -%% == Dependencies == - -{deps, [ {jsx, "2.9.0"} - , {cowboy, "2.8.0"} - , {ranch, "2.0.0"} - , {trails, "2.3.0"} - ]}. - -%% == xref == - -{xref_ignores, [{cowboy_swagger, to_json, 1}, - {cowboy_swagger, add_definition, 2}, - {cowboy_swagger, get_global_spec, 0}, - {cowboy_swagger, get_global_spec, 1}, - {cowboy_swagger, set_global_spec, 1}, - {cowboy_swagger, schema, 1}, - {cowboy_swagger, enc_json, 1}, - {cowboy_swagger, dec_json, 1}, - {cowboy_swagger, normalize_json, 1}, - {cowboy_swagger, swagger_paths, 1}, - {cowboy_swagger, validate_metadata, 1}, - {cowboy_swagger, filter_cowboy_swagger_handler, 1}, - {cowboy_swagger_handler, trails, 1}, - {cowboy_swagger_json_handler, handle_get, 2}, - {cowboy_swagger, add_definition_array, 2}, - {cowboy_swagger, get_existing_definitions, 2}]}. - -%% == Dialyzer == - -{dialyzer, [ {warnings, [ no_return - , unmatched_returns - , error_handling - ]} - , {plt_apps, top_level_deps} - , {plt_extra_apps, [cowboy, trails, ranch, jsx]} - , {plt_location, local} - , {base_plt_apps, [stdlib, kernel, erts]} - , {base_plt_location, global}]}. - - -%% == Shell == - -{project_plugins, [ - rebar3_auto, - rebar3_lint, - rebar3_hex, - rebar3_hank -]}. - -%% == hank == - -{hank, [ - {ignore, [ - {"example/**", unnecessary_function_arguments}, - {"test/**", unnecessary_function_arguments} - ]} -]}. +%% == Compiler and Profiles == + +{erl_opts, + [warn_unused_import, warn_export_vars, warnings_as_errors, verbose, report, debug_info]}. + +{minimum_otp_vsn, "23"}. + +{profiles, + [{test, + [{deps, [{mixer, "1.2.0", {pkg, inaka_mixer}}, {shotgun, "1.0.1"}]}, + {ct_opts, [{sys_config, ["./test/test.config"]}, {verbose, true}]}, + {cover_enabled, true}, + {cover_opts, [verbose]}, + {dialyzer, + [{warnings, [no_return, unmatched_returns, error_handling, underspecs, unknown]}, + {plt_extra_apps, [cowboy, trails, ranch, jsx, common_test, shotgun]}]}]}]}. + +{alias, [{test, [compile, format, hank, lint, xref, dialyzer, ct, cover, ex_doc]}]}. + +%% == Dependencies and plugins == + +{deps, [{jsx, "3.1.0"}, {cowboy, "2.10.0"}, {ranch, "2.1.0"}, {trails, "2.3.0"}]}. + +{project_plugins, + [{rebar3_hank, "~> 1.4.0"}, + {rebar3_hex, "~> 7.0.7"}, + {rebar3_format, "~> 1.3.0"}, + {rebar3_lint, "~> 3.0.1"}, + {rebar3_ex_doc, "0.2.18"}]}. + +%% == Documentation == + +{ex_doc, + [{source_url, <<"https://github.com/inaka/cowboy_swagger">>}, + {extras, [<<"README.md">>, <<"LICENSE">>]}, + {main, <<"readme">>}]}. + +{hex, [{doc, #{provider => ex_doc}}]}. + +%% == Format == + +{format, + [{files, ["*.config", "src/*", "test/*", "example/**/*{.config,.erl,.src,.script}"]}]}. + +%% == Dialyzer + XRef == + +{dialyzer, + [{warnings, [no_return, unmatched_returns, error_handling, underspecs, unknown]}, + {plt_extra_apps, [cowboy, trails, ranch, jsx]}]}. + +{xref_checks, + [undefined_function_calls, deprecated_function_calls, deprecated_functions]}. + +{xref_extra_paths, ["test/**"]}. diff --git a/rebar3 b/rebar3 deleted file mode 100755 index eec3760..0000000 Binary files a/rebar3 and /dev/null differ diff --git a/src/cowboy_swagger.app.src b/src/cowboy_swagger.app.src index 4e2c62c..8c07700 100644 --- a/src/cowboy_swagger.app.src +++ b/src/cowboy_swagger.app.src @@ -1,23 +1,14 @@ -{application, cowboy_swagger, - [ - {description, - "Swagger for Cowboy Erlang projects"}, +{application, + cowboy_swagger, + [{description, "Swagger for Cowboy Erlang projects"}, {vsn, git}, - {applications, - [kernel, - stdlib, - ranch, - cowboy, - trails, - jsx - ]}, + {applications, [kernel, stdlib, ranch, cowboy, trails, jsx]}, {modules, []}, {registered, []}, - {licenses,["Apache 2.0"]}, - {links,[ {"Github","https://github.com/inaka/cowboy-swagger"}, - {"Blog", "http://inaka.net/blog/2015/08/19/cowboy-swagger/"}, - {"Example", "https://github.com/inaka/cowboy-swagger/tree/master/example"} - ]}, - {build_tools,["rebar3"]} - ] -}. + {licenses, ["Apache 2.0"]}, + {links, + [{"GitHub", "https://github.com/inaka/cowboy-swagger"}, + {"Blog", + "https://web.archive.org/web/20160621233046/http://inaka.net/blog/2015/08/19/cowboy-swagger/"}, + {"Example", "https://github.com/inaka/cowboy-swagger/tree/master/example"}]}, + {build_tools, ["rebar3"]}]}. diff --git a/src/cowboy_swagger.erl b/src/cowboy_swagger.erl index 53132f0..7d4e23a 100644 --- a/src/cowboy_swagger.erl +++ b/src/cowboy_swagger.erl @@ -1,81 +1,76 @@ %%% @doc cowboy-swagger main interface. -module(cowboy_swagger). --ignore_xref([{?MODULE, add_definition, 1}]). %% API --export([to_json/1, add_definition/1, add_definition/2, add_definition_array/2, schema/1]). - +-export([to_json/1, add_definition/1, add_definition/2, add_definition_array/2, + schema/1]). %% Utilities -export([enc_json/1, dec_json/1, normalize_json/1]). -export([swagger_paths/1, validate_metadata/1]). -export([filter_cowboy_swagger_handler/1]). --export([get_existing_definitions/2, - get_global_spec/0, get_global_spec/1, set_global_spec/1]). +-export([get_existing_definitions/2, get_global_spec/0, get_global_spec/1, + set_global_spec/1]). % is_visible is used as a maps:filter/2 predicate, which requires a /2 arity function --hank([{unnecessary_function_arguments, [is_visible/2]}]). +-hank([{unnecessary_function_arguments, [{is_visible, 2}]}]). + +-elvis([{elvis_style, no_throw, disable}]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Types. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -opaque parameter_obj() :: - #{ name => binary() - , in => binary() - , description => binary() - , required => boolean() - , type => binary() - , schema => binary() - }. + #{name => binary(), + in => binary(), + description => binary(), + required => boolean(), + type => binary(), + schema => binary()}. + -export_type([parameter_obj/0]). --opaque response_obj() :: - #{ description => binary() - }. +-opaque response_obj() :: #{description => binary()}. + -type responses_definitions() :: #{binary() => response_obj()}. + -export_type([response_obj/0, responses_definitions/0]). --type parameter_definition_name () :: binary(). +-type parameter_definition_name() :: binary(). -type property_desc() :: - #{ type => binary() - , description => binary() - , example => binary() - , items => property_desc() - }. + #{type => binary(), + description => binary(), + example => binary(), + items => property_desc()}. -type property_obj() :: #{binary() => property_desc()}. -type parameters_definitions() :: - #{parameter_definition_name() => - #{ type => binary() - , properties => property_obj() - }}. + #{parameter_definition_name() => + #{type => binary(), + properties => property_obj(), + _ => _}}. -type parameters_definition_array() :: - #{parameter_definition_name() => - #{ type => binary() - , items => #{ type => binary() - , properties => property_obj() - } - }}. --export_type([ parameter_definition_name/0 - , property_obj/0 - , parameters_definitions/0 - , parameters_definition_array/0 - ]). + #{parameter_definition_name() => + #{type => binary(), items => #{type => binary(), properties => property_obj()}}}. + +-export_type([parameter_definition_name/0, property_obj/0, parameters_definitions/0, + parameters_definition_array/0]). %% Swagger map spec -opaque swagger_map() :: - #{ description => binary() - , summary => binary() - , parameters => [parameter_obj()] - , tags => [binary()] - , consumes => [binary()] - , produces => [binary()] - , responses => responses_definitions() - }. + #{description => binary(), + summary => binary(), + parameters => [parameter_obj()], + tags => [binary()], + consumes => [binary()], + produces => [binary()], + responses => responses_definitions()}. + -type metadata() :: trails:metadata(swagger_map()). + -export_type([swagger_map/0, metadata/0]). --type swagger_version() :: swagger_2_0 - | openapi_3_0_0. +-type swagger_version() :: swagger_2_0 | openapi_3_0_0. + -export_type([swagger_version/0]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -83,82 +78,78 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @doc Returns the swagger json specification from given `trails'. -%% This function basically takes the metadata from each `trails:trail()' +%% This function basically takes the metadata from each `t:trails:trail()' %% (which must be compliant with Swagger specification) and builds the %% required `swagger.json'. -spec to_json([trails:trail()]) -> jsx:json_text(). to_json(Trails) -> - Default = #{info => #{title => <<"API-DOCS">>}}, - GlobalSpec = get_global_spec(Default), - SanitizeTrails = filter_cowboy_swagger_handler(Trails), - SwaggerSpec = create_swagger_spec(GlobalSpec, SanitizeTrails), - enc_json(SwaggerSpec). - --spec add_definition_array( Name::parameter_definition_name() - , Properties::property_obj() - ) -> - ok. + Default = #{info => #{title => <<"API-DOCS">>}}, + GlobalSpec = get_global_spec(Default), + SanitizeTrails = filter_cowboy_swagger_handler(Trails), + SwaggerSpec = create_swagger_spec(GlobalSpec, SanitizeTrails), + enc_json(SwaggerSpec). + +-spec add_definition_array(Name :: parameter_definition_name(), + Properties :: property_obj()) -> + ok. add_definition_array(Name, Properties) -> - DefinitionArray = build_definition_array(Name, Properties), - add_definition(DefinitionArray). + DefinitionArray = build_definition_array(Name, Properties), + add_definition(DefinitionArray). --spec add_definition( Name::parameter_definition_name() - , Properties::property_obj() - ) -> - ok. +-spec add_definition(Name :: parameter_definition_name(), Properties :: property_obj()) -> + ok. add_definition(Name, Properties) -> - Definition = build_definition(Name, Properties), - add_definition(Definition). + Definition = build_definition(Name, Properties), + add_definition(Definition). --spec add_definition( Definition :: parameters_definitions() - | parameters_definition_array() - ) -> - ok. +-spec add_definition(Definition :: + parameters_definitions() | parameters_definition_array()) -> + ok. add_definition(Definition) -> - CurrentSpec = get_global_spec(), - NormDefinition = normalize_json(Definition), - Type = definition_type(NormDefinition), - NewDefinitions = maps:merge( get_existing_definitions(CurrentSpec, Type) - , normalize_json(NormDefinition) - ), - NewSpec = prepare_new_global_spec(CurrentSpec, NewDefinitions, Type), - set_global_spec(NewSpec). + CurrentSpec = get_global_spec(), + NormDefinition = normalize_json(Definition), + Type = definition_type(NormDefinition), + NewDefinitions = + maps:merge(get_existing_definitions(CurrentSpec, Type), normalize_json(NormDefinition)), + NewSpec = prepare_new_global_spec(CurrentSpec, NewDefinitions, Type), + set_global_spec(NewSpec). definition_type(Definition) -> - case maps:values(Definition) of - [#{<<"in">> := In}] when In =:= <<"query">>; In =:= <<"path">>; In =:= <<"header">> -> - <<"parameters">>; - _ -> <<"schemas">> - end. - --spec schema(DefinitionName::parameter_definition_name()) -> - map(). + case maps:values(Definition) of + [#{<<"in">> := In}] when In =:= <<"query">>; In =:= <<"path">>; In =:= <<"header">> -> + <<"parameters">>; + _ -> + <<"schemas">> + end. + +-spec schema(DefinitionName :: parameter_definition_name()) -> + #{<<_:32>> => <<_:64, _:_*8>>}. schema(DefinitionName) -> - case swagger_version() of - swagger_2_0 -> - #{<<"$ref">> => <<"#/definitions/", DefinitionName/binary>>}; - openapi_3_0_0 -> - #{<<"$ref">> => <<"#/components/schemas/", DefinitionName/binary>>} - end. - + case swagger_version() of + swagger_2_0 -> + #{<<"$ref">> => <<"#/definitions/", DefinitionName/binary>>}; + openapi_3_0_0 -> + #{<<"$ref">> => <<"#/components/schemas/", DefinitionName/binary>>} + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Utilities. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden +%% @private -spec enc_json(jsx:json_term()) -> jsx:json_text(). enc_json(Json) -> - jsx:encode(Json, [uescape]). + jsx:encode(Json, [uescape]). -%% @hidden +%% @private -spec dec_json(iodata()) -> jsx:json_term(). dec_json(Data) -> - try jsx:decode(Data, [return_maps]) - catch - _:{error, _} -> - throw(bad_json) - end. + try + jsx:decode(Data, [return_maps]) + catch + _:{error, _} -> + throw(bad_json) + end. %% We assume the jsx representation of JSON as Erlang terms: %% true/false/null: 'true' | 'false' | 'null' @@ -177,23 +168,31 @@ dec_json(Data) -> %% When normalizing, we make all strings and labels be binaries, %% and all objects be maps, not proplists. -%% @hidden +%% @private -spec normalize_json(jsx:json_term()) -> jsx:json_term(). normalize_json(Json) when is_map(Json) -> normalize_json_proplist(maps:to_list(Json)); -normalize_json([]) -> []; % empty array -normalize_json([{}]) -> #{}; % special case in jsx for empty map as list +normalize_json([]) -> + []; % empty array +normalize_json([{}]) -> + #{}; % special case in jsx for empty map as list normalize_json([{_K, _V} | _] = Json) -> normalize_json_proplist(Json); % map as proplist normalize_json(Json) when is_list(Json) -> case io_lib:printable_list(Json) of - true -> unicode:characters_to_binary(Json); - false -> normalize_json_list(Json) + true -> + unicode:characters_to_binary(Json); + false -> + normalize_json_list(Json) end; -normalize_json(true) -> true; -normalize_json(false) -> false; -normalize_json(null) -> null; -normalize_json(Json) when is_atom(Json) -> erlang:atom_to_binary(Json, utf8); +normalize_json(true) -> + true; +normalize_json(false) -> + false; +normalize_json(null) -> + null; +normalize_json(Json) when is_atom(Json) -> + erlang:atom_to_binary(Json, utf8); normalize_json(Json) -> Json. @@ -205,59 +204,59 @@ normalize_json_key(K) -> K. normalize_json_proplist(Proplist) -> - F = fun({K, V}, Acc) -> - maps:put(normalize_json_key(K), normalize_json(V), Acc) - end, - lists:foldl(F, #{}, Proplist). + F = fun({K, V}, Acc) -> maps:put(normalize_json_key(K), normalize_json(V), Acc) end, + lists:foldl(F, #{}, Proplist). normalize_json_list(List) -> - F = fun(V, Acc) -> - [normalize_json(V) | Acc] - end, - lists:foldr(F, [], List). + F = fun(V, Acc) -> [normalize_json(V) | Acc] end, + lists:foldr(F, [], List). -%% @hidden +%% @private -spec swagger_paths([trails:trail()]) -> map(). swagger_paths(Trails) -> - swagger_paths(Trails, undefined). + swagger_paths(Trails, undefined). -spec swagger_paths([trails:trail()], binary() | string() | undefined) -> map(). swagger_paths(Trails, BasePath) -> Paths = translate_swagger_paths(Trails, #{}), refactor_base_path(Paths, BasePath). -%% @hidden +%% @private -spec validate_metadata(trails:metadata(_)) -> metadata(). validate_metadata(Metadata) -> - validate_swagger_map(Metadata). + validate_swagger_map(Metadata). -%% @hidden +%% @private -spec filter_cowboy_swagger_handler([trails:trail()]) -> [trails:trail()]. filter_cowboy_swagger_handler(Trails) -> - %% Keeps only trails with at least one non-hidden method. - %% (All the cowboy_swagger_handler methdods are marked as hidden.) - F = fun(Trail) -> - MD = get_metadata(Trail), - maps:size(maps:filter(fun is_visible/2, MD)) /= 0 - end, - lists:filter(F, Trails). - --spec get_existing_definitions(CurrentSpec :: jsx:json_term(), Type :: atom() | binary()) -> - Definition :: parameters_definitions() - | parameters_definition_array(). + %% Keeps only trails with at least one non-hidden method. + %% (All the cowboy_swagger_handler methdods are marked as hidden.) + F = fun(Trail) -> + MD = get_metadata(Trail), + maps:size( + maps:filter(fun is_visible/2, MD)) + /= 0 + end, + lists:filter(F, Trails). + +-spec get_existing_definitions(CurrentSpec :: jsx:json_term(), + Type :: atom() | binary()) -> + Definition :: + parameters_definitions() | parameters_definition_array(). get_existing_definitions(CurrentSpec, Type) when is_atom(Type) -> get_existing_definitions(CurrentSpec, atom_to_binary(Type, utf8)); get_existing_definitions(CurrentSpec, Type) when is_binary(Type) -> - case swagger_version() of - swagger_2_0 -> - maps:get(<<"definitions">>, CurrentSpec, #{}); - openapi_3_0_0 -> - case CurrentSpec of - #{<<"components">> := - #{Type := Def }} -> Def; - _Other -> #{} - end - end. + case swagger_version() of + swagger_2_0 -> + maps:get(<<"definitions">>, CurrentSpec, #{}); + openapi_3_0_0 -> + case CurrentSpec of + #{<<"components">> := #{Type := Def}} -> + Def; + _Other -> + #{} + end + end. -spec get_global_spec() -> jsx:json_term(). get_global_spec() -> @@ -271,12 +270,10 @@ get_global_spec(Default) -> set_global_spec(NewSpec) -> application:set_env(cowboy_swagger, global_spec, normalize_json(NewSpec)). - -spec get_metadata(trails:trail()) -> jsx:json_term(). get_metadata(Trail) -> normalize_json(trails:metadata(Trail)). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Private API. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -284,11 +281,14 @@ get_metadata(Trail) -> %% @private -spec swagger_version() -> swagger_version(). swagger_version() -> - case get_global_spec() of - #{<<"openapi">> := <<"3.0.0">>} -> openapi_3_0_0; - #{<<"swagger">> := <<"2.0">>} -> swagger_2_0; - _Other -> swagger_2_0 - end. + case get_global_spec() of + #{<<"openapi">> := <<"3.0.0">>} -> + openapi_3_0_0; + #{<<"swagger">> := <<"2.0">>} -> + swagger_2_0; + _Other -> + swagger_2_0 + end. %% @private is_visible(_Key, Metadata) when is_map(Metadata) -> @@ -299,135 +299,127 @@ is_visible(_Key, _Metadata) -> %% @private translate_swagger_paths([], Acc) -> - Acc; + Acc; translate_swagger_paths([Trail | T], Acc) -> - Path = normalize_path(trails:path_match(Trail)), - Metadata = validate_metadata(get_metadata(Trail)), - translate_swagger_paths(T, maps:put(Path, Metadata, Acc)). + Path = normalize_path(trails:path_match(Trail)), + Metadata = validate_metadata(get_metadata(Trail)), + translate_swagger_paths(T, maps:put(Path, Metadata, Acc)). %% @private refactor_base_path(PathMap, undefined) -> - PathMap; + PathMap; refactor_base_path(PathMap, BasePath) when is_list(BasePath) -> - refactor_base_path(PathMap, list_to_binary(BasePath)); + refactor_base_path(PathMap, list_to_binary(BasePath)); refactor_base_path(PathMap, BasePath) -> - Fun = - fun(Path, NextPathMap) -> - maps:put(remove_base_path(Path, BasePath), maps:get(Path, PathMap), NextPathMap) - end, - lists:foldl(Fun, #{}, maps:keys(PathMap)). + Fun = fun(Path, NextPathMap) -> + maps:put(remove_base_path(Path, BasePath), maps:get(Path, PathMap), NextPathMap) + end, + lists:foldl(Fun, #{}, maps:keys(PathMap)). %% /base_path/api -> /api %% @private --spec(remove_base_path(binary(), binary()) -> binary()). +-spec remove_base_path(binary(), binary()) -> binary(). remove_base_path(Path, BasePath) -> - BasePathLength = erlang:size(BasePath), - MatchLength = BasePathLength + 1, - case binary:match(Path, <>) of - {0, MatchLength} -> - binary:part(Path, BasePathLength, erlang:size(Path) - BasePathLength); - _ -> - Path - end. + BasePathLength = erlang:size(BasePath), + MatchLength = BasePathLength + 1, + case binary:match(Path, <>) of + {0, MatchLength} -> + binary:part(Path, BasePathLength, erlang:size(Path) - BasePathLength); + _ -> + Path + end. %% @private normalize_path(Path) -> - re:replace( - re:replace(Path, "\\:\\w+", "\\{&\\}", [global]), - "\\[|\\]|\\:", "", [{return, binary}, global]). + re:replace( + re:replace(Path, "\\:\\w+", "\\{&\\}", [global]), + "\\[|\\]|\\:", + "", + [{return, binary}, global]). %% @private create_swagger_spec(#{<<"swagger">> := _Version} = GlobalSpec, SanitizeTrails) -> - BasePath = maps:get(<<"basePath">>, GlobalSpec, undefined), - SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), - GlobalSpec#{<<"paths">> => SwaggerPaths}; + BasePath = maps:get(<<"basePath">>, GlobalSpec, undefined), + SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), + GlobalSpec#{<<"paths">> => SwaggerPaths}; create_swagger_spec(#{<<"openapi">> := _Version} = GlobalSpec, SanitizeTrails) -> - BasePath = deconstruct_openapi_url(GlobalSpec), - SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), - GlobalSpec#{<<"paths">> => SwaggerPaths}; + BasePath = deconstruct_openapi_url(GlobalSpec), + SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), + GlobalSpec#{<<"paths">> => SwaggerPaths}; create_swagger_spec(GlobalSpec, SanitizeTrails) -> - create_swagger_spec(GlobalSpec#{<<"openapi">> => <<"3.0.0">>}, SanitizeTrails). + create_swagger_spec(GlobalSpec#{<<"openapi">> => <<"3.0.0">>}, SanitizeTrails). %% @private deconstruct_openapi_url(GlobalSpec) -> - [Server|_] = maps:get(<<"servers">>, GlobalSpec, [#{}]), + [Server | _] = maps:get(<<"servers">>, GlobalSpec, [#{}]), Url = maps:get(<<"url">>, Server, <<"">>), maps:get(path, uri_string:parse(Url)). %% @private validate_swagger_map(Map) when is_map(Map) -> - %% Note that although per-path entries are usually methods such as - %% `"get": {...}`, there may also be entries whose values are not maps, - %% such as path-global `"parameters": [...]'. - F = fun(_K, V) when is_map(V) -> - Params = validate_swagger_map_params(maps:get(<<"parameters">>, V, [])), - Responses = validate_swagger_map_responses(maps:get(<<"responses">>, V, #{})), - V#{<<"parameters">> => Params, <<"responses">> => Responses}; - (_K, V) -> - V - end, - maps:map(F, Map); + %% Note that although per-path entries are usually methods such as + %% `"get": {...}`, there may also be entries whose values are not maps, + %% such as path-global `"parameters": [...]'. + F = fun (_K, V) when is_map(V) -> + Params = validate_swagger_map_params(maps:get(<<"parameters">>, V, [])), + Responses = validate_swagger_map_responses(maps:get(<<"responses">>, V, #{})), + V#{<<"parameters">> => Params, <<"responses">> => Responses}; + (_K, V) -> + V + end, + maps:map(F, Map); validate_swagger_map(Other) -> - Other. + Other. %% @private validate_swagger_map_params(Params) -> - ValidateParams = - fun(E) -> - case maps:get(<<"name">>, E, undefined) of - undefined -> maps:is_key(<<"$ref">>, E); - _ -> {true, E#{<<"in">> => maps:get(<<"in">>, E, <<"path">>)}} - end - end, - lists:filtermap(ValidateParams, Params). + ValidateParams = + fun(E) -> + case maps:get(<<"name">>, E, undefined) of + undefined -> + maps:is_key(<<"$ref">>, E); + _ -> + {true, E#{<<"in">> => maps:get(<<"in">>, E, <<"path">>)}} + end + end, + lists:filtermap(ValidateParams, Params). %% @private validate_swagger_map_responses(Responses) -> - F = fun(_K, V) -> V#{<<"description">> => maps:get(<<"description">>, V, <<"">>)} end, - maps:map(F, Responses). + F = fun(_K, V) -> V#{<<"description">> => maps:get(<<"description">>, V, <<"">>)} end, + maps:map(F, Responses). %% @private --spec build_definition( Name::parameter_definition_name() - , Properties::property_obj() - ) -> - parameters_definitions(). +-spec build_definition(Name :: parameter_definition_name(), + Properties :: property_obj()) -> + parameters_definitions(). build_definition(Name, Properties) when is_atom(Name) -> build_definition(erlang:atom_to_binary(Name, utf8), Properties); build_definition(Name, Properties) when is_binary(Name) -> - #{Name => #{ <<"type">> => <<"object">> - , <<"properties">> => Properties - }}. + #{Name => #{<<"type">> => <<"object">>, <<"properties">> => Properties}}. %% @private --spec build_definition_array( Name::parameter_definition_name() - , Properties::property_obj() - ) -> - parameters_definition_array(). +-spec build_definition_array(Name :: parameter_definition_name(), + Properties :: property_obj()) -> + parameters_definition_array(). build_definition_array(Name, Properties) when is_atom(Name) -> build_definition_array(erlang:atom_to_binary(Name, utf8), Properties); build_definition_array(Name, Properties) when is_binary(Name) -> - #{Name => #{ <<"type">> => <<"array">> - , <<"items">> => #{ <<"type">> => <<"object">> - , <<"properties">> => Properties - } - }}. + #{Name => + #{<<"type">> => <<"array">>, + <<"items">> => #{<<"type">> => <<"object">>, <<"properties">> => Properties}}}. %% @private --spec prepare_new_global_spec( CurrentSpec :: jsx:json_term() - , Definitions :: parameters_definitions() - | parameters_definition_array() - , Type ::binary() - ) -> - NewSpec :: jsx:json_term(). +-spec prepare_new_global_spec(CurrentSpec :: jsx:json_term(), + Definitions :: + parameters_definitions() | parameters_definition_array(), + Type :: binary()) -> + NewSpec :: jsx:json_term(). prepare_new_global_spec(CurrentSpec, Definitions, Type) -> - case swagger_version() of - swagger_2_0 -> - CurrentSpec#{<<"definitions">> => Definitions - }; - openapi_3_0_0 -> - Components = maps:get(<<"components">>, CurrentSpec, #{}), - CurrentSpec#{<<"components">> => - Components#{ Type => Definitions - } - } - end. + case swagger_version() of + swagger_2_0 -> + CurrentSpec#{<<"definitions">> => Definitions}; + openapi_3_0_0 -> + Components = maps:get(<<"components">>, CurrentSpec, #{}), + CurrentSpec#{<<"components">> => Components#{Type => Definitions}} + end. diff --git a/src/cowboy_swagger_handler.erl b/src/cowboy_swagger_handler.erl index 29c848a..2691bad 100644 --- a/src/cowboy_swagger_handler.erl +++ b/src/cowboy_swagger_handler.erl @@ -5,51 +5,60 @@ %% Trails -behaviour(trails_handler). + -export([trails/0, trails/1]). -type route_match() :: '_' | iodata(). + -export_type([route_match/0]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Trails %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden +%% @private %% @doc Implements `trails_handler:trails/0' callback. This function returns %% trails routes for both: static content (Swagger-UI) and this handler %% that returns the `swagger.json'. -spec trails() -> trails:trails(). -trails() -> trails(#{}). +trails() -> + trails(#{}). + -spec trails(cowboy_swagger_json_handler:options()) -> trails:trails(). trails(Options) -> - StaticFiles = - case application:get_env(cowboy_swagger, static_files) of - {ok, Val} -> Val; - _ -> filename:join(cowboy_swagger_priv(), "swagger") - end, - Redirect = trails:trail( - "/api-docs", - cowboy_swagger_redirect_handler, - {file, StaticFiles ++ "/index.html"}, - #{get => #{hidden => true}}), - Static = trails:trail( - "/api-docs/[...]", - cowboy_static, - {dir, StaticFiles, [{mimetypes, cow_mimetypes, all}]}, - #{get => #{hidden => true}}), - MD = #{get => #{hidden => true}}, - Handler = trails:trail( - "/api-docs/swagger.json", cowboy_swagger_json_handler, Options, MD), - [Redirect, Handler, Static]. + StaticFiles = + case application:get_env(cowboy_swagger, static_files) of + {ok, Val} -> + Val; + _ -> + filename:join(cowboy_swagger_priv(), "swagger") + end, + Redirect = + trails:trail("/api-docs", + cowboy_swagger_redirect_handler, + {file, StaticFiles ++ "/index.html"}, + #{get => #{hidden => true}}), + Static = + trails:trail("/api-docs/[...]", + cowboy_static, + {dir, StaticFiles, [{mimetypes, cow_mimetypes, all}]}, + #{get => #{hidden => true}}), + MD = #{get => #{hidden => true}}, + Handler = + trails:trail("/api-docs/swagger.json", cowboy_swagger_json_handler, Options, MD), + [Redirect, Handler, Static]. %% @private -spec cowboy_swagger_priv() -> string(). cowboy_swagger_priv() -> - case code:priv_dir(cowboy_swagger) of - {error, bad_name} -> - case code:which(cowboy_swagger_handler) of - cover_compiled -> "../../priv"; % required for tests to work - BeamPath -> filename:join([filename:dirname(BeamPath) , ".." , "priv"]) - end; - Path -> Path - end. \ No newline at end of file + case code:priv_dir(cowboy_swagger) of + {error, bad_name} -> + case code:which(cowboy_swagger_handler) of + cover_compiled -> + "../../priv"; % required for tests to work + BeamPath -> + filename:join([filename:dirname(BeamPath), "..", "priv"]) + end; + Path -> + Path + end. diff --git a/src/cowboy_swagger_json_handler.erl b/src/cowboy_swagger_json_handler.erl index e4aac4f..f810acc 100644 --- a/src/cowboy_swagger_json_handler.erl +++ b/src/cowboy_swagger_json_handler.erl @@ -1,23 +1,23 @@ +%% @private %%% @doc Cowboy Swagger Handler. This handler exposes a GET operation %%% to enable `swagger.json' to be retrieved from embedded %%% Swagger-UI (located in `priv/swagger' folder). -module(cowboy_swagger_json_handler). %% Cowboy callbacks --export([ init/2 - , content_types_provided/2 - ]). +-export([init/2, content_types_provided/2]). -behaviour(cowboy_rest). %% Handlers -export([handle_get/2]). --type options() :: #{ server => ranch:ref() - , host => cowboy_swagger_handler:route_match() - , _ => _ - }. --export_type([options/0]). +-type options() :: + #{server => ranch:ref(), + host => cowboy_swagger_handler:route_match(), + _ => _}. + +-export_type([options/0, state/0]). -type state() :: options(). @@ -25,28 +25,23 @@ %%% Cowboy Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden --spec init(cowboy_req:req(), options()) -> - {cowboy_rest, cowboy_req:req(), state()}. +-spec init(cowboy_req:req(), options()) -> {cowboy_rest, cowboy_req:req(), state()}. init(Req, Opts) -> - State = Opts, - {cowboy_rest, Req, State}. + State = Opts, + {cowboy_rest, Req, State}. -%% @hidden -spec content_types_provided(cowboy_req:req(), state()) -> - {[{binary(), atom()}], cowboy_req:req(), state()}. + {[{binary(), atom()}], cowboy_req:req(), state()}. content_types_provided(Req, State) -> - {[{<<"application/json">>, handle_get}], Req, State}. + {[{<<"application/json">>, handle_get}], Req, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Handlers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden --spec handle_get(cowboy_req:req(), state()) -> - {iodata(), cowboy_req:req(), state()}. +-spec handle_get(cowboy_req:req(), state()) -> {iodata(), cowboy_req:req(), state()}. handle_get(Req, State) -> - Server = maps:get(server, State, '_'), - HostMatch = maps:get(host, State, '_'), - Trails = trails:all(Server, HostMatch), - {cowboy_swagger:to_json(Trails), Req, State}. + Server = maps:get(server, State, '_'), + HostMatch = maps:get(host, State, '_'), + Trails = trails:all(Server, HostMatch), + {cowboy_swagger:to_json(Trails), Req, State}. diff --git a/src/cowboy_swagger_redirect_handler.erl b/src/cowboy_swagger_redirect_handler.erl index 66de5f1..92e2bf4 100644 --- a/src/cowboy_swagger_redirect_handler.erl +++ b/src/cowboy_swagger_redirect_handler.erl @@ -1,42 +1,39 @@ +%% @private -module(cowboy_swagger_redirect_handler). -behaviour(cowboy_rest). %% Cowboy callbacks -export([init/2]). - %% Handlers -export([resource_exists/2, previously_existed/2, moved_permanently/2]). -type state() :: #{}. +-export_type([state/0]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Cowboy Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden --spec init(cowboy_req:req(), state()) -> - {cowboy_rest, cowboy_req:req(), state()}. +-spec init(cowboy_req:req(), state()) -> {cowboy_rest, cowboy_req:req(), state()}. init(Req, State) -> - {cowboy_rest, Req, State}. + {cowboy_rest, Req, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Handlers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% @hidden --spec resource_exists(Req::cowboy_req:req(), State::state()) -> - {boolean(), cowboy_req:req(), state()}. +-spec resource_exists(Req :: cowboy_req:req(), State :: state()) -> + {boolean(), cowboy_req:req(), state()}. resource_exists(Req, State) -> - {false, Req, State}. + {false, Req, State}. -%% @hidden --spec previously_existed(Req::cowboy_req:req(), State::state())-> - {boolean(), cowboy_req:req(), state()}. +-spec previously_existed(Req :: cowboy_req:req(), State :: state()) -> + {boolean(), cowboy_req:req(), state()}. previously_existed(Req, State) -> - {true, Req, State}. + {true, Req, State}. -%% @hidden --spec moved_permanently(Req::cowboy_req:req(), State::state()) -> - {{true, iodata()}, cowboy_req:req(), state()}. +-spec moved_permanently(Req :: cowboy_req:req(), State :: state()) -> + {{true, iodata()}, cowboy_req:req(), state()}. moved_permanently(Req, State) -> - {{true, "/api-docs/index.html"}, Req, State}. + {{true, "/api-docs/index.html"}, Req, State}. diff --git a/test/cover.spec b/test/cover.spec index 3b880ed..b5a1f58 100644 --- a/test/cover.spec +++ b/test/cover.spec @@ -1,10 +1,6 @@ %% Specific modules to include in cover. -{ - incl_mods, - [ - cowboy_swagger, +{incl_mods, + [cowboy_swagger, cowboy_swagger_handler, cowboy_swagger_redirect_handler, - cowboy_swagger_json_handler - ] -}. + cowboy_swagger_json_handler]}. diff --git a/test/cowboy_swagger_SUITE.erl b/test/cowboy_swagger_SUITE.erl index 5af643b..b9985de 100644 --- a/test/cowboy_swagger_SUITE.erl +++ b/test/cowboy_swagger_SUITE.erl @@ -1,20 +1,14 @@ -module(cowboy_swagger_SUITE). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {cowboy_swagger_test_utils, - [ init_per_suite/1 - , end_per_suite/1 - ]} - ]). + +-mixin([{cowboy_swagger_test_utils, [init_per_suite/1, end_per_suite/1]}]). -export([all/0]). --export([ to_json_test/1 - , add_definition_test/1 - , add_definition_array_test/1 - , schema_test/1 - , parameters_ref_test/1 - ]). +-export([to_json_test/1, add_definition_test/1, add_definition_array_test/1, + schema_test/1, parameters_ref_test/1]). + +-hank([unnecessary_function_arguments]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test @@ -22,7 +16,7 @@ -spec all() -> [atom()]. all() -> - cowboy_swagger_test_utils:all(?MODULE). + cowboy_swagger_test_utils:all(?MODULE). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test Cases @@ -30,382 +24,336 @@ all() -> -spec to_json_test(cowboy_swagger_test_utils:config()) -> {atom(), string()}. to_json_test(_Config) -> - set_openapi_url("/basepath"), - Trails = test_trails(), - SwaggerJson = cowboy_swagger:to_json(Trails), - Result = jsx:decode(SwaggerJson, [return_maps]), - #{<<"info">> := #{<<"title">> := <<"Example API">>}, - <<"openapi">> := <<"3.0.0">>, - <<"servers">> := [#{<<"url">> := <<"/basepath">>}], - <<"paths">> := - #{<<"/a">> := - #{<<"get">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := [], - <<"responses">> := - #{<<"200">> := - #{<<"content">> := - #{<<"application/json">> := - #{<<"schema">> := - #{<<"title">> := <<"bla">>, - <<"type">> := - <<"string">>}}}, - <<"description">> := <<"200 OK">>}}}}, - <<"/a/{b}/{c}">> := - #{<<"delete">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"b">>, - <<"required">> := false, - <<"schema">> := #{<<"type">> := <<"string">>}}], - <<"responses">> := #{}}, - <<"get">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"b">>, - <<"required">> := false, - <<"schema">> := #{<<"type">> := <<"string">>}}, - #{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"c">>, - <<"required">> := false, - <<"schema">> := - #{<<"example">> := <<"c">>, - <<"type">> := <<"string">>}}], - <<"responses">> := - #{<<"200">> := - #{<<"content">> := - #{<<"application/json">> := - #{<<"schema">> := - #{<<"title">> := <<"bla">>, - <<"type">> := - <<"string">>}}}, - <<"description">> := <<"200 OK">>}}}, - <<"post">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"body">>, - <<"name">> := <<"Request Body">>, - <<"required">> := true, - <<"schema">> := #{<<"type">> := <<"string">>}}], - <<"responses">> := - #{<<"200">> := - #{<<"description">> := <<"bla">>}}}}, - <<"/a/{b}/{c}/{d}">> := - #{<<"delete">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"b">>, - <<"required">> := false, - <<"schema">> := #{<<"type">> := <<"string">>}}], - <<"responses">> := #{}}, - <<"get">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"b">>, - <<"required">> := false, - <<"schema">> := #{<<"type">> := <<"string">>}}, - #{<<"description">> := <<"bla">>, - <<"in">> := <<"path">>, <<"name">> := <<"c">>, - <<"required">> := false, - <<"schema">> := - #{<<"example">> := <<"c">>, - <<"type">> := <<"string">>}}], - <<"responses">> := - #{<<"200">> := - #{<<"content">> := - #{<<"application/json">> := - #{<<"schema">> := - #{<<"title">> := <<"bla">>, - <<"type">> := - <<"string">>}}}, - <<"description">> := <<"200 OK">>}}}, - <<"post">> := - #{<<"description">> := <<"bla bla bla">>, - <<"parameters">> := - [#{<<"description">> := <<"bla">>, - <<"in">> := <<"body">>, - <<"name">> := <<"Request Body">>, - <<"required">> := true, - <<"schema">> := #{<<"type">> := <<"string">>}}], - <<"responses">> := - #{<<"200">> := - #{<<"description">> := <<"bla">>}}}}}} = Result, - #{<<"paths">> := Paths} = Result, - 3 = maps:size(Paths), - {comment, ""}. - --spec add_definition_test(Config::cowboy_swagger_test_utils:config()) -> - {comment, string()}. + set_openapi_url("/basepath"), + Trails = test_trails(), + SwaggerJson = cowboy_swagger:to_json(Trails), + Result = jsx:decode(SwaggerJson, [return_maps]), + #{<<"info">> := #{<<"title">> := <<"Example API">>}, + <<"openapi">> := <<"3.0.0">>, + <<"servers">> := [#{<<"url">> := <<"/basepath">>}], + <<"paths">> := + #{<<"/a">> := + #{<<"get">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := [], + <<"responses">> := + #{<<"200">> := + #{<<"content">> := + #{<<"application/json">> := + #{<<"schema">> := + #{<<"title">> := <<"bla">>, + <<"type">> := <<"string">>}}}, + <<"description">> := <<"200 OK">>}}}}, + <<"/a/{b}/{c}">> := + #{<<"delete">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"b">>, + <<"required">> := false, + <<"schema">> := #{<<"type">> := <<"string">>}}], + <<"responses">> := #{}}, + <<"get">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"b">>, + <<"required">> := false, + <<"schema">> := #{<<"type">> := <<"string">>}}, + #{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"c">>, + <<"required">> := false, + <<"schema">> := + #{<<"example">> := <<"c">>, <<"type">> := <<"string">>}}], + <<"responses">> := + #{<<"200">> := + #{<<"content">> := + #{<<"application/json">> := + #{<<"schema">> := + #{<<"title">> := <<"bla">>, + <<"type">> := <<"string">>}}}, + <<"description">> := <<"200 OK">>}}}, + <<"post">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"body">>, + <<"name">> := <<"Request Body">>, + <<"required">> := true, + <<"schema">> := #{<<"type">> := <<"string">>}}], + <<"responses">> := #{<<"200">> := #{<<"description">> := <<"bla">>}}}}, + <<"/a/{b}/{c}/{d}">> := + #{<<"delete">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"b">>, + <<"required">> := false, + <<"schema">> := #{<<"type">> := <<"string">>}}], + <<"responses">> := #{}}, + <<"get">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"b">>, + <<"required">> := false, + <<"schema">> := #{<<"type">> := <<"string">>}}, + #{<<"description">> := <<"bla">>, + <<"in">> := <<"path">>, + <<"name">> := <<"c">>, + <<"required">> := false, + <<"schema">> := + #{<<"example">> := <<"c">>, <<"type">> := <<"string">>}}], + <<"responses">> := + #{<<"200">> := + #{<<"content">> := + #{<<"application/json">> := + #{<<"schema">> := + #{<<"title">> := <<"bla">>, + <<"type">> := <<"string">>}}}, + <<"description">> := <<"200 OK">>}}}, + <<"post">> := + #{<<"description">> := <<"bla bla bla">>, + <<"parameters">> := + [#{<<"description">> := <<"bla">>, + <<"in">> := <<"body">>, + <<"name">> := <<"Request Body">>, + <<"required">> := true, + <<"schema">> := #{<<"type">> := <<"string">>}}], + <<"responses">> := #{<<"200">> := #{<<"description">> := <<"bla">>}}}}}} = + Result, + #{<<"paths">> := Paths} = Result, + 3 = maps:size(Paths), + {comment, ""}. + +-spec add_definition_test(Config :: cowboy_swagger_test_utils:config()) -> + {comment, string()}. add_definition_test(_Config) -> - set_swagger_version(swagger_2_0), - perform_add_definition_test(), - perform_add_completed_definition_test(), + set_swagger_version(swagger_2_0), + perform_add_definition_test(), + perform_add_completed_definition_test(), - set_swagger_version(openapi_3_0_0), - perform_add_definition_test(), - perform_add_completed_definition_test(), + set_swagger_version(openapi_3_0_0), + perform_add_definition_test(), + perform_add_completed_definition_test(), - {comment, ""}. + {comment, ""}. --spec add_definition_array_test(Config::cowboy_swagger_test_utils:config()) -> - {comment, string()}. +-spec add_definition_array_test(Config :: cowboy_swagger_test_utils:config()) -> + {comment, string()}. add_definition_array_test(_Config) -> - set_swagger_version(swagger_2_0), - perform_add_definition_array_test(), + set_swagger_version(swagger_2_0), + perform_add_definition_array_test(), - set_swagger_version(openapi_3_0_0), - perform_add_definition_array_test(), + set_swagger_version(openapi_3_0_0), + perform_add_definition_array_test(), - {comment, ""}. + {comment, ""}. --spec schema_test(Config::cowboy_swagger_test_utils:config()) -> - {comment, string()}. +-spec schema_test(Config :: cowboy_swagger_test_utils:config()) -> {comment, string()}. schema_test(_Config) -> - set_swagger_version(swagger_2_0), - #{<<"$ref">> := <<"#/definitions/Pet">>} = cowboy_swagger:schema(<<"Pet">>), + set_swagger_version(swagger_2_0), + #{<<"$ref">> := <<"#/definitions/Pet">>} = cowboy_swagger:schema(<<"Pet">>), - set_swagger_version(openapi_3_0_0), - #{<<"$ref">> := <<"#/components/schemas/Pet">>} = cowboy_swagger:schema(<<"Pet">>), + set_swagger_version(openapi_3_0_0), + #{<<"$ref">> := <<"#/components/schemas/Pet">>} = cowboy_swagger:schema(<<"Pet">>), - {comment, ""}. + {comment, ""}. --spec parameters_ref_test(Config::cowboy_swagger_test_utils:config()) -> - {comment, string()}. +-spec parameters_ref_test(Config :: cowboy_swagger_test_utils:config()) -> + {comment, string()}. parameters_ref_test(_Config) -> - set_swagger_version(openapi_3_0_0), - cowboy_swagger:add_definition( - #{<<"page">> => - #{description => <<"results per page (max 100)">>, example => 1, - in => query, name => per_page, - schema => #{example => 1, maximum => 100, minimum => 1, type => integer}}}), - {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), - JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, parameters), - true = maps:is_key(<<"page">>, JsonDefinitions), - {comment, ""}. + set_swagger_version(openapi_3_0_0), + cowboy_swagger:add_definition(#{<<"page">> => + #{description => <<"results per page (max 100)">>, + example => 1, + in => query, + name => per_page, + schema => + #{example => 1, + maximum => 100, + minimum => 1, + type => integer}}}), + {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), + JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, parameters), + true = maps:is_key(<<"page">>, JsonDefinitions), + {comment, ""}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% perform_add_completed_definition_test() -> - %% - %% Given - %% - ct:comment("Add first definition"), - Name1 = <<"CostumerDefinition">>, - Properties1 = test_properties_one(), - Definition1 = - #{ Name1 => - #{ type => <<"object">> - , properties => Properties1} - }, - - ct:comment("Add second definition"), - Name2 = <<"CarDefinition">>, - Properties2 = test_properties_two(), - Definition2 = - #{ Name1 => - #{ type => <<"object">> - , properties => Properties2} - }, - %% - %% When - %% - ok = cowboy_swagger:add_definition(Definition1), - ok = cowboy_swagger:add_definition(Definition2), - - %% - %% Then - %% - {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), - JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), - true = maps:is_key(Name1, JsonDefinitions), - true = maps:is_key(Name2, JsonDefinitions), - ok. + %% + %% Given + %% + ct:comment("Add first definition"), + Name1 = <<"CostumerDefinition">>, + Properties1 = test_properties_one(), + Definition1 = #{Name1 => #{type => <<"object">>, properties => Properties1}}, + + ct:comment("Add second definition"), + Name2 = <<"CarDefinition">>, + Properties2 = test_properties_two(), + Definition2 = #{Name1 => #{type => <<"object">>, properties => Properties2}}, + %% + %% When + %% + ok = cowboy_swagger:add_definition(Definition1), + ok = cowboy_swagger:add_definition(Definition2), + + %% + %% Then + %% + {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), + JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), + true = maps:is_key(Name1, JsonDefinitions), + true = maps:is_key(Name2, JsonDefinitions), + ok. %% @private perform_add_definition_test() -> - %% - %% Given - %% - ct:comment("Add first definition"), - Name1 = <<"CostumerDefinition">>, - Properties1 = test_properties_one(), - - ct:comment("Add second definition"), - Name2 = <<"CarDefinition">>, - Properties2 = test_properties_two(), - - %% - %% When - %% - ok = cowboy_swagger:add_definition(Name1, Properties1), - ok = cowboy_swagger:add_definition(Name2, Properties2), - - %% - %% Then - %% - {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), - JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), - true = maps:is_key(Name1, JsonDefinitions), - true = maps:is_key(Name2, JsonDefinitions), - ok. + %% + %% Given + %% + ct:comment("Add first definition"), + Name1 = <<"CostumerDefinition">>, + Properties1 = test_properties_one(), + + ct:comment("Add second definition"), + Name2 = <<"CarDefinition">>, + Properties2 = test_properties_two(), + + %% + %% When + %% + ok = cowboy_swagger:add_definition(Name1, Properties1), + ok = cowboy_swagger:add_definition(Name2, Properties2), + + %% + %% Then + %% + {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), + JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), + true = maps:is_key(Name1, JsonDefinitions), + true = maps:is_key(Name2, JsonDefinitions), + ok. %% @private perform_add_definition_array_test() -> - %% - %% Given - %% - ct:comment("Add first definition"), - Name1 = <<"CostumerDefinition">>, - Properties1 = test_properties_one(), - - ct:comment("Add second definition"), - Name2 = <<"CarDefinition">>, - Properties2 = test_properties_two(), - - %% - %% When - %% - ok = cowboy_swagger:add_definition_array(Name1, Properties1), - ok = cowboy_swagger:add_definition_array(Name2, Properties2), - - %% - %% Then - %% - {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), - JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), - true = maps:is_key(<<"items">>, maps:get(Name1, JsonDefinitions)), - true = maps:is_key(<<"items">>, maps:get(Name2, JsonDefinitions)), - <<"array">> = maps:get(<<"type">>, maps:get(Name1, JsonDefinitions)), - <<"array">> = maps:get(<<"type">>, maps:get(Name2, JsonDefinitions)), - ok. + %% + %% Given + %% + ct:comment("Add first definition"), + Name1 = <<"CostumerDefinition">>, + Properties1 = test_properties_one(), + + ct:comment("Add second definition"), + Name2 = <<"CarDefinition">>, + Properties2 = test_properties_two(), + + %% + %% When + %% + ok = cowboy_swagger:add_definition_array(Name1, Properties1), + ok = cowboy_swagger:add_definition_array(Name2, Properties2), + + %% + %% Then + %% + {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), + JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), + true = maps:is_key(<<"items">>, maps:get(Name1, JsonDefinitions)), + true = maps:is_key(<<"items">>, maps:get(Name2, JsonDefinitions)), + <<"array">> = maps:get(<<"type">>, maps:get(Name1, JsonDefinitions)), + <<"array">> = maps:get(<<"type">>, maps:get(Name2, JsonDefinitions)), + ok. %% @private test_trails() -> - Metadata = - #{get => #{description => <<"bla bla bla">>, - parameters => [ - #{name => "b", - in => "path", - description => "bla", - schema => #{ - type => string - }, - required => false}, - #{name => "c", - in => "path", - description => "bla", - schema => #{ - type => string, - example => <<"c">> - }, - required => false} - ], - responses => #{ - <<"200">> => - #{description => <<"200 OK">>, - content => - #{'application/json' => - #{schema => - #{type => string, - title => <<"bla">> - } - } - } - } - } - }, - delete => #{description => <<"bla bla bla">>, - parameters => [ - #{name => <<"b">>, - in => <<"path">>, - description => <<"bla">>, - required => false, - schema => #{ - type => string - } - } - ] - }, - post => #{description => <<"bla bla bla">>, - parameters => [ - #{name => <<"Request Body">>, - in => <<"body">>, - description => <<"bla">>, - required => true, - schema => #{ - type => string - } - } - ], - responses => #{<<"200">> => #{description => "bla"}} - } - }, - Metadata1 = - #{ - get => #{description => <<"bla bla bla">>, - responses => #{ - <<"200">> => - #{description => <<"200 OK">>, - content => - #{'application/json' => - #{schema => - #{type => string, - title => <<"bla">> - } - } - } - } - } - } - }, - [trails:trail("/a/[:b/[:c/[:d]]]", handler1, [], Metadata), - trails:trail("/a/:b/[:c]", handler2, [], Metadata), - trails:trail("/a", handler3, [], Metadata1)| - cowboy_swagger_handler:trails()]. + Metadata = + #{get => + #{description => <<"bla bla bla">>, + parameters => + [#{name => "b", + in => "path", + description => "bla", + schema => #{type => string}, + required => false}, + #{name => "c", + in => "path", + description => "bla", + schema => #{type => string, example => <<"c">>}, + required => false}], + responses => + #{<<"200">> => + #{description => <<"200 OK">>, + content => + #{'application/json' => + #{schema => #{type => string, title => <<"bla">>}}}}}}, + delete => + #{description => <<"bla bla bla">>, + parameters => + [#{name => <<"b">>, + in => <<"path">>, + description => <<"bla">>, + required => false, + schema => #{type => string}}]}, + post => + #{description => <<"bla bla bla">>, + parameters => + [#{name => <<"Request Body">>, + in => <<"body">>, + description => <<"bla">>, + required => true, + schema => #{type => string}}], + responses => #{<<"200">> => #{description => "bla"}}}}, + Metadata1 = + #{get => + #{description => <<"bla bla bla">>, + responses => + #{<<"200">> => + #{description => <<"200 OK">>, + content => + #{'application/json' => + #{schema => #{type => string, title => <<"bla">>}}}}}}}, + [trails:trail("/a/[:b/[:c/[:d]]]", handler1, [], Metadata), + trails:trail("/a/:b/[:c]", handler2, [], Metadata), + trails:trail("/a", handler3, [], Metadata1) + | cowboy_swagger_handler:trails()]. %% @private test_properties_one() -> - #{ <<"first_name">> => - #{ type => <<"string">> - , description => <<"User first name">> - , example => <<"Pepito">> - } - , <<"last_name">> => - #{ type => <<"string">> - , description => <<"User last name">> - , example => <<"Perez">> - } - }. + #{<<"first_name">> => + #{type => <<"string">>, + description => <<"User first name">>, + example => <<"Pepito">>}, + <<"last_name">> => + #{type => <<"string">>, + description => <<"User last name">>, + example => <<"Perez">>}}. %% @private test_properties_two() -> - #{ <<"brand">> => - #{ type => <<"string">> - , description => <<"Car brand">> - } - , <<"year">> => - #{ type => <<"string">> - , description => <<"Production time">> - , example => <<"1995">> - } - }. + #{<<"brand">> => #{type => <<"string">>, description => <<"Car brand">>}, + <<"year">> => + #{type => <<"string">>, + description => <<"Production time">>, + example => <<"1995">>}}. %% @private set_swagger_version(swagger_2_0) -> - Spec0 = maps:remove(<<"openapi">>, cowboy_swagger:get_global_spec()), - cowboy_swagger:set_global_spec(Spec0#{swagger => "2.0"}); + Spec0 = maps:remove(<<"openapi">>, cowboy_swagger:get_global_spec()), + cowboy_swagger:set_global_spec(Spec0#{swagger => "2.0"}); set_swagger_version(openapi_3_0_0) -> - Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), - cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0"}). + Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), + cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0"}). set_openapi_url(Url) -> - Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), - cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0", - servers => [#{url => Url}]}). + Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), + cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0", servers => [#{url => Url}]}). diff --git a/test/cowboy_swagger_handler_SUITE.erl b/test/cowboy_swagger_handler_SUITE.erl index 5aa5fda..05ec40d 100644 --- a/test/cowboy_swagger_handler_SUITE.erl +++ b/test/cowboy_swagger_handler_SUITE.erl @@ -1,16 +1,12 @@ -module(cowboy_swagger_handler_SUITE). %% CT --export([ all/0 - , init_per_suite/1 - , end_per_suite/1 - , init_per_testcase/2 - , end_per_testcase/2 - ]). - +-export([all/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2, + end_per_testcase/2]). %% Test cases --export([ handler_test/1 - , multiple_hosts_test/1]). +-export([handler_test/1, multiple_hosts_test/1]). + +-hank([unnecessary_function_arguments]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test @@ -18,50 +14,48 @@ -spec all() -> [atom()]. all() -> - cowboy_swagger_test_utils:all(?MODULE). + cowboy_swagger_test_utils:all(?MODULE). --spec init_per_suite( - cowboy_swagger_test_utils:config() -) -> cowboy_swagger_test_utils:config(). +-spec init_per_suite(cowboy_swagger_test_utils:config()) -> + cowboy_swagger_test_utils:config(). init_per_suite(Config) -> - {ok, _} = shotgun:start(), - Config. + {ok, _} = shotgun:start(), + Config. --spec end_per_suite( - cowboy_swagger_test_utils:config() -) -> cowboy_swagger_test_utils:config(). +-spec end_per_suite(cowboy_swagger_test_utils:config()) -> + cowboy_swagger_test_utils:config(). end_per_suite(Config) -> - _ = shotgun:stop(), - Config. + _ = shotgun:stop(), + Config. --spec init_per_testcase(TestCase::atom(), - Config::cowboy_swagger_test_utils:config()) -> - cowboy_swagger_test_utils:config(). +-spec init_per_testcase(TestCase :: atom(), + Config :: cowboy_swagger_test_utils:config()) -> + cowboy_swagger_test_utils:config(). init_per_testcase(handler_test, Config) -> - {ok, _} = example:start(), - Config; + {ok, _} = example:start(), + Config; init_per_testcase(multiple_hosts_test, Config) -> - {ok, _} = multiple_hosts_servers_example:start(), - Config. + {ok, _} = multiple_hosts_servers_example:start(), + Config. --spec end_per_testcase(TestCase::atom(), - Config::cowboy_swagger_test_utils:config()) -> - cowboy_swagger_test_utils:config(). +-spec end_per_testcase(TestCase :: atom(), + Config :: cowboy_swagger_test_utils:config()) -> + cowboy_swagger_test_utils:config(). end_per_testcase(handler_test, Config) -> - _ = example:stop(), - ok = cleanup(), - Config; + _ = example:stop(), + ok = cleanup(), + Config; end_per_testcase(multiple_hosts_test, Config) -> - _ = multiple_hosts_servers_example:stop(), - ok = cleanup(), - Config. + _ = multiple_hosts_servers_example:stop(), + ok = cleanup(), + Config. %% @private -spec cleanup() -> ok. cleanup() -> - _ = application:stop(cowboy_swagger), - _ = application:stop(trails), - ok. + _ = application:stop(cowboy_swagger), + _ = application:stop(trails), + ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test Cases @@ -69,91 +63,89 @@ cleanup() -> -spec handler_test(cowboy_swagger_test_utils:config()) -> {atom(), string()}. handler_test(_Config) -> - %% Expected result - Trails = trails:trails([example_echo_handler, - example_description_handler, - cowboy_swagger_handler]), - SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), - ExpectedPaths = cowboy_swagger:dec_json( - cowboy_swagger:enc_json(cowboy_swagger:swagger_paths(SanitizeTrails))), - - %% GET swagger.json spec - ct:comment("GET /api-docs/swagger.json should return 200 OK"), - #{status_code := 200, body := Body0} = - cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json"), - #{<<"openapi">> := <<"3.0.0">>, - <<"info">> := #{<<"title">> := <<"Example API">>}, - <<"paths">> := ExpectedPaths} = cowboy_swagger:dec_json(Body0), - - %% GET index.html - ct:comment("GET /api-docs should return 301 MOVED PERMANENTLY to " ++ - "/api-docs/index.html"), - #{status_code := 301, headers := Headers} = - cowboy_swagger_test_utils:api_call(get, "/api-docs"), - Location = {<<"location">>, <<"/api-docs/index.html">>}, - Location = lists:keyfind(<<"location">>, 1, Headers), - - %% GET swagger-ui.js - test /api-docs/[...] trail - ct:comment("GET /api-docs/swagger-ui-js should return 200 OK"), - #{status_code := 200, body := SwaggerUIBody} = - cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger-ui.js"), - {ok, SwaggerUIBodySrc} = - file:read_file("../../../../priv/swagger/swagger-ui.js"), - SwaggerUIBody = SwaggerUIBodySrc, - - %% GET unknown-file.ext - test /api-docs/[...] trail - ct:comment("GET /api-docs/unknown-file.ext should return 404 NOT FOUND"), - #{status_code := 404} = - cowboy_swagger_test_utils:api_call(get, "/api-docs/unknown-file.ext"), - {comment, ""}. - --spec multiple_hosts_test(_Config::cowboy_swagger_test_utils:config()) -> - {atom(), string()}. + %% Expected result + Trails = + trails:trails([example_echo_handler, + example_description_handler, + cowboy_swagger_handler]), + SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), + ExpectedPaths = + cowboy_swagger:dec_json( + cowboy_swagger:enc_json( + cowboy_swagger:swagger_paths(SanitizeTrails))), + + %% GET swagger.json spec + ct:comment("GET /api-docs/swagger.json should return 200 OK"), + #{status_code := 200, body := Body0} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json"), + #{<<"openapi">> := <<"3.0.0">>, + <<"info">> := #{<<"title">> := <<"Example API">>}, + <<"paths">> := ExpectedPaths} = + cowboy_swagger:dec_json(Body0), + + %% GET index.html + ct:comment("GET /api-docs should return 301 MOVED PERMANENTLY to " + ++ "/api-docs/index.html"), + #{status_code := 301, headers := Headers} = + cowboy_swagger_test_utils:api_call(get, "/api-docs"), + Location = {<<"location">>, <<"/api-docs/index.html">>}, + Location = lists:keyfind(<<"location">>, 1, Headers), + + %% GET swagger-ui.js - test /api-docs/[...] trail + ct:comment("GET /api-docs/swagger-ui-js should return 200 OK"), + #{status_code := 200, body := SwaggerUIBody} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger-ui.js"), + {ok, SwaggerUIBodySrc} = file:read_file("../../../../priv/swagger/swagger-ui.js"), + SwaggerUIBody = SwaggerUIBodySrc, + + %% GET unknown-file.ext - test /api-docs/[...] trail + ct:comment("GET /api-docs/unknown-file.ext should return 404 NOT FOUND"), + #{status_code := 404} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/unknown-file.ext"), + {comment, ""}. + +-spec multiple_hosts_test(_Config :: cowboy_swagger_test_utils:config()) -> + {atom(), string()}. multiple_hosts_test(_Config) -> - %% api1 - host1 - Trails11 = trails:trails(example_echo_handler), - ExpectedPaths11 = get_expected_paths(Trails11), - %% GET swagger.json spec from localhost:8383 - ct:comment("GET /api-docs/swagger.json should return 200 OK"), - #{status_code := 200, body := Body11} = - cowboy_swagger_test_utils:api_call(get, - "/api-docs/swagger.json", - "localhost", - 8383), - #{<<"openapi">> := <<"3.0.0">>, - <<"info">> := #{<<"title">> := <<"Example API">>}, - <<"paths">> := ExpectedPaths11} = cowboy_swagger:dec_json(Body11), - %% api1 - host2 - Trails12 = trails:trails(host1_handler), - ExpectedPaths12 = get_expected_paths(Trails12), - %% GET swagger.json spec from 127.0.0.1:8383 - ct:comment("GET /api-docs/swagger.json should return 200 OK"), - #{status_code := 200, body := Body12} = - cowboy_swagger_test_utils:api_call(get, - "/api-docs/swagger.json", - "127.0.0.1", - 8383), - #{<<"openapi">> := <<"3.0.0">>, - <<"info">> := #{<<"title">> := <<"Example API">>}, - <<"paths">> := ExpectedPaths12} = cowboy_swagger:dec_json(Body12), - %% api2 - host1 - Trails21 = trails:trails([host1_handler, example_echo_handler]), - ExpectedPaths21 = get_expected_paths(Trails21), - %% GET swagger.json spec from localhost:8282 - ct:comment("GET /api-docs/swagger.json should return 200 OK"), - #{status_code := 200, body := Body21} = - cowboy_swagger_test_utils:api_call(get, - "/api-docs/swagger.json", - "localhost", - 8282), - #{<<"openapi">> := <<"3.0.0">>, - <<"info">> := #{<<"title">> := <<"Example API">>}, - <<"paths">> := ExpectedPaths21} = cowboy_swagger:dec_json(Body21), - {comment, ""}. + %% api1 - host1 + Trails11 = trails:trails(example_echo_handler), + ExpectedPaths11 = get_expected_paths(Trails11), + %% GET swagger.json spec from localhost:8383 + ct:comment("GET /api-docs/swagger.json should return 200 OK"), + #{status_code := 200, body := Body11} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "localhost", 8383), + #{<<"openapi">> := <<"3.0.0">>, + <<"info">> := #{<<"title">> := <<"Example API">>}, + <<"paths">> := ExpectedPaths11} = + cowboy_swagger:dec_json(Body11), + %% api1 - host2 + Trails12 = trails:trails(host1_handler), + ExpectedPaths12 = get_expected_paths(Trails12), + %% GET swagger.json spec from 127.0.0.1:8383 + ct:comment("GET /api-docs/swagger.json should return 200 OK"), + #{status_code := 200, body := Body12} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "127.0.0.1", 8383), + #{<<"openapi">> := <<"3.0.0">>, + <<"info">> := #{<<"title">> := <<"Example API">>}, + <<"paths">> := ExpectedPaths12} = + cowboy_swagger:dec_json(Body12), + %% api2 - host1 + Trails21 = trails:trails([host1_handler, example_echo_handler]), + ExpectedPaths21 = get_expected_paths(Trails21), + %% GET swagger.json spec from localhost:8282 + ct:comment("GET /api-docs/swagger.json should return 200 OK"), + #{status_code := 200, body := Body21} = + cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "localhost", 8282), + #{<<"openapi">> := <<"3.0.0">>, + <<"info">> := #{<<"title">> := <<"Example API">>}, + <<"paths">> := ExpectedPaths21} = + cowboy_swagger:dec_json(Body21), + {comment, ""}. %% @private --spec get_expected_paths(Trails::trails:trails()) -> jsx:json_term(). +-spec get_expected_paths(Trails :: trails:trails()) -> jsx:json_term(). get_expected_paths(Trails) -> - SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), - cowboy_swagger:dec_json( - cowboy_swagger:enc_json(cowboy_swagger:swagger_paths(SanitizeTrails))). + SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), + cowboy_swagger:dec_json( + cowboy_swagger:enc_json( + cowboy_swagger:swagger_paths(SanitizeTrails))). diff --git a/test/cowboy_swagger_test_utils.erl b/test/cowboy_swagger_test_utils.erl index 2ac17fd..3be8ea8 100644 --- a/test/cowboy_swagger_test_utils.erl +++ b/test/cowboy_swagger_test_utils.erl @@ -1,46 +1,44 @@ -module(cowboy_swagger_test_utils). --export([ all/1 - , init_per_suite/1 - , end_per_suite/1 - ]). --export([ api_call/2 - , api_call/4 - ]). - --type response() :: #{ status_code => integer() - , headers => [tuple()] - , body => binary() - }. +-export([all/1, init_per_suite/1, end_per_suite/1]). +-export([api_call/2, api_call/4]). +-type response() :: + #{status_code => integer(), + headers => [tuple()], + body => binary()}. -type config() :: proplists:proplist(). --export_type([config/0]). +-type shotgun_http_verb() :: delete | get | head | options | patch | post | put. + +-export_type([shotgun_http_verb/0]). +-export_type([config/0, response/0]). -spec all(atom()) -> [atom()]. all(Module) -> - ExcludedFuns = [module_info, init_per_suite, end_per_suite, group, all], - Exports = apply(Module, module_info, [exports]), - [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)]. + ExcludedFuns = [module_info, init_per_suite, end_per_suite, group, all], + Exports = apply(Module, module_info, [exports]), + [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)]. -spec init_per_suite(config()) -> config(). init_per_suite(Config) -> - Config. + Config. -spec end_per_suite(config()) -> config(). end_per_suite(Config) -> - Config. + Config. --spec api_call(atom(), string()) -> response(). +-spec api_call(shotgun_http_verb(), [byte()]) -> response(). api_call(Method, Uri) -> - api_call(Method, Uri, "localhost", 8080). + api_call(Method, Uri, "localhost", 8080). --spec api_call(atom(), string(), string(), integer()) -> response(). +-spec api_call(shotgun_http_verb(), string(), string(), integer()) -> response(). api_call(Method, Uri, HostMatch, Port) -> - {ok, Pid} = shotgun:open(HostMatch, Port), - try - Headers = #{}, Body = [], - {ok, Response} = shotgun:request(Pid, Method, Uri, Headers, Body, #{}), - Response - after - shotgun:close(Pid) - end. + {ok, Pid} = shotgun:open(HostMatch, Port), + try + Headers = #{}, + Body = [], + {ok, Response} = shotgun:request(Pid, Method, Uri, Headers, Body, #{}), + Response + after + shotgun:close(Pid) + end. diff --git a/test/example.app b/test/example.app index 0650835..f6b96f1 100644 --- a/test/example.app +++ b/test/example.app @@ -1,16 +1,8 @@ -{application, example, - [ - {description, "Cowboy Swagger Basic Example."}, +{application, + example, + [{description, "Cowboy Swagger Basic Example."}, {vsn, "0.1"}, - {applications, - [kernel, - stdlib, - cowboy, - trails, - cowboy_swagger - ]}, + {applications, [kernel, stdlib, cowboy, trails, cowboy_swagger]}, {modules, []}, {mod, {example, []}}, - {start_phases, [{start_trails_http, []}]} - ] -}. + {start_phases, [{start_trails_http, []}]}]}. diff --git a/test/example.erl b/test/example.erl index fd09615..5d80bb0 100644 --- a/test/example.erl +++ b/test/example.erl @@ -6,45 +6,45 @@ -export([stop/1]). -export([start_phase/3]). +-hank([unnecessary_function_arguments]). + %% application %% @doc Starts the application -spec start() -> {ok, [atom()]}. start() -> - application:ensure_all_started(example). + application:ensure_all_started(example). %% @doc Stops the application -spec stop() -> ok. stop() -> - application:stop(example). + application:stop(example). %% behaviour %% @private --spec start(normal, [any()]) -> {ok, pid()} | - {ok, pid(), any()} | - {error, any()}. +-spec start(normal, [any()]) -> {ok, pid()}. start(_StartType, _StartArgs) -> - example_sup:start_link(). + example_sup:start_link(). %% @private -spec stop(_) -> ok. stop(_State) -> - ok = cowboy:stop_listener(example_http). + ok = cowboy:stop_listener(example_http). --spec start_phase(atom(), application:start_type(), []) -> ok | {error, term()}. +-spec start_phase(atom(), application:start_type(), []) -> ok. start_phase(start_trails_http, _StartType, []) -> - {ok, Port} = application:get_env(example, http_port), - Trails = trails:trails([ example_echo_handler - , example_description_handler - , cowboy_swagger_handler - ]), - trails:store(Trails), - - Dispatch = trails:single_host_compile(Trails), - RanchOptions = [{port, Port}], - CowboyOptions = #{ env => #{dispatch => Dispatch} - , compress => true - , timeout => 12000 - }, - - {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), - ok. + {ok, Port} = application:get_env(example, http_port), + Trails = + trails:trails([example_echo_handler, + example_description_handler, + cowboy_swagger_handler]), + trails:store(Trails), + + Dispatch = trails:single_host_compile(Trails), + RanchOptions = [{port, Port}], + CowboyOptions = + #{env => #{dispatch => Dispatch}, + compress => true, + timeout => 12000}, + + {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), + ok. diff --git a/test/example_default.erl b/test/example_default.erl index a5884cd..ca747b1 100644 --- a/test/example_default.erl +++ b/test/example_default.erl @@ -1,34 +1,31 @@ -module(example_default). --export([ init/3 - , rest_init/2 - , content_types_accepted/2 - , content_types_provided/2 - , forbidden/2 - , resource_exists/2 - ]). +-export([init/3, rest_init/2, content_types_accepted/2, content_types_provided/2, + forbidden/2, resource_exists/2]). + +-hank([unnecessary_function_arguments]). %% cowboy -spec init(any(), any(), any()) -> {upgrade, protocol, cowboy_rest}. init(_Transport, _Req, _Opts) -> - {upgrade, protocol, cowboy_rest}. + {upgrade, protocol, cowboy_rest}. -spec rest_init(Req, any()) -> {ok, Req, #{}}. rest_init(Req, _Opts) -> - {ok, Req, #{}}. + {ok, Req, #{}}. --spec content_types_accepted(Req, State) -> {[{binary(), handle_put}], Req, State}. +-spec content_types_accepted(Req, State) -> {[{<<_:80>>, handle_put}, ...], Req, State}. content_types_accepted(Req, State) -> - {[{<<"text/plain">>, handle_put}], Req, State}. + {[{<<"text/plain">>, handle_put}], Req, State}. --spec content_types_provided(Req, State) -> {[{binary(), handle_get}], Req, State}. +-spec content_types_provided(Req, State) -> {[{<<_:80>>, handle_get}, ...], Req, State}. content_types_provided(Req, State) -> - {[{<<"text/plain">>, handle_get}], Req, State}. + {[{<<"text/plain">>, handle_get}], Req, State}. -spec forbidden(Req, State) -> {false, Req, State}. forbidden(Req, State) -> - {false, Req, State}. + {false, Req, State}. -spec resource_exists(Req, State) -> {true, Req, State}. resource_exists(Req, State) -> - {true, Req, State}. + {true, Req, State}. diff --git a/test/example_description_handler.erl b/test/example_description_handler.erl index a1ad855..853c33f 100644 --- a/test/example_description_handler.erl +++ b/test/example_description_handler.erl @@ -1,54 +1,40 @@ -module(example_description_handler). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {example_default, - [ - init/3, + +-mixin([{example_default, + [init/3, rest_init/2, content_types_accepted/2, content_types_provided/2, - resource_exists/2 - ]} - ]). + resource_exists/2]}]). --export([ allowed_methods/2 - , handle_get/2 - ]). +-export([allowed_methods/2, handle_get/2]). %trails -behaviour(trails_handler). + -export([trails/0]). -spec trails() -> [trails:trail()]. trails() -> - Metadata = - #{get => - #{tags => ["example"], - description => "Retrives trails's server description", - responses => #{ - <<"200">> => #{ - description => <<"Retrives trails's server description 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - } - }, - [trails:trail("/description", example_description_handler, [], Metadata)]. + Metadata = + #{get => + #{tags => ["example"], + description => "Retrives trails's server description", + responses => + #{<<"200">> => + #{description => <<"Retrives trails's server description 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}}, + [trails:trail("/description", example_description_handler, [], Metadata)]. %% cowboy --spec allowed_methods(Req, State) -> {[binary(), ...], Req, State}. +-spec allowed_methods(Req, State) -> {[<<_:24>>, ...], Req, State}. allowed_methods(Req, State) -> - {[<<"GET">>], Req, State}. + {[<<"GET">>], Req, State}. %% internal -spec handle_get(Req, State) -> {[trails:trail()], Req, State}. handle_get(Req, State) -> - Body = trails:all(), - {io_lib:format("~p~n", [Body]), Req, State}. + Body = trails:all(), + {io_lib:format("~p~n", [Body]), Req, State}. diff --git a/test/example_echo_handler.erl b/test/example_echo_handler.erl index fea8513..be1f0d7 100644 --- a/test/example_echo_handler.erl +++ b/test/example_echo_handler.erl @@ -1,91 +1,62 @@ -module(example_echo_handler). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {example_default, - [ - init/3, + +-mixin([{example_default, + [init/3, rest_init/2, content_types_accepted/2, content_types_provided/2, - resource_exists/2 - ]} - ]). + resource_exists/2]}]). --export([ allowed_methods/2 - , handle_put/2 - , handle_get/2 - ]). +-export([allowed_methods/2, handle_put/2, handle_get/2]). %trails -behaviour(trails_handler). + -export([trails/0]). -spec trails() -> [trails:trail()]. trails() -> - Metadata = - #{get => - #{tags => ["echo"], - description => "Gets echo var from the server", - responses => #{ - <<"200">> => #{ - description => <<"Gets echo var from the server 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - }, - put => - #{tags => ["echo"], - description => "Sets echo var in the server", - parameters => [ - #{name => <<"echo">>, - description => <<"Echo message">>, - in => <<"path">>, - required => false, - schema => - #{type => string, - example => <<"Hello, World!">> - } - } - ], - responses => #{ - <<"200">> => #{ - description => <<"Gets echo var from the server 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - } - }, - [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. + Metadata = + #{get => + #{tags => ["echo"], + description => "Gets echo var from the server", + responses => + #{<<"200">> => + #{description => <<"Gets echo var from the server 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}, + put => + #{tags => ["echo"], + description => "Sets echo var in the server", + parameters => + [#{name => <<"echo">>, + description => <<"Echo message">>, + in => <<"path">>, + required => false, + schema => #{type => string, example => <<"Hello, World!">>}}], + responses => + #{<<"200">> => + #{description => <<"Gets echo var from the server 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}}, + [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. %% cowboy --spec allowed_methods(Req, State) -> {[binary(), ...], Req, State}. +-spec allowed_methods(Req, State) -> {[<<_:24, _:_*8>>, ...], Req, State}. allowed_methods(Req, State) -> - {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. + {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. %% internal -spec handle_get(Req, State) -> {[binary(), ...], Req, State}. handle_get(Req, State) -> - Echo = application:get_env(example, echo, ""), - Body = [<<"You Get an echo!">> , Echo], - {Body, Req, State}. + Echo = application:get_env(example, echo, ""), + Body = [<<"You Get an echo!">>, Echo], + {Body, Req, State}. -spec handle_put(cowboy_req:req(), State) -> {true, cowboy_req:req(), State}. handle_put(Req, State) -> - {Echo, Req1} = cowboy_req:binding(echo, Req, ""), - application:set_env(example, echo, Echo), - Body = [<<"You put an echo! ">> , Echo], - Req2 = cowboy_req:set_resp_body(Body, Req1), - {true, Req2, State}. + {Echo, Req1} = cowboy_req:binding(echo, Req, ""), + application:set_env(example, echo, Echo), + Body = [<<"You put an echo! ">>, Echo], + Req2 = cowboy_req:set_resp_body(Body, Req1), + {true, Req2, State}. diff --git a/test/example_sup.erl b/test/example_sup.erl index 250bb72..af1cf2d 100644 --- a/test/example_sup.erl +++ b/test/example_sup.erl @@ -8,9 +8,9 @@ %% admin api -spec start_link() -> {ok, pid()}. start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, {}). + supervisor:start_link({local, ?MODULE}, ?MODULE, {}). %% behaviour callbacks --spec init({}) -> {ok, {{one_for_one, 5, 10}, []} }. +-spec init({}) -> {ok, {{one_for_one, 5, 10}, []}}. init({}) -> - {ok, {{one_for_one, 5, 10}, []} }. + {ok, {{one_for_one, 5, 10}, []}}. diff --git a/test/host1_handler.erl b/test/host1_handler.erl index 4dd751a..7cbc277 100644 --- a/test/host1_handler.erl +++ b/test/host1_handler.erl @@ -1,58 +1,41 @@ -module(host1_handler). -include_lib("mixer/include/mixer.hrl"). --mixin([ - {example_default, - [ - init/3, + +-mixin([{example_default, + [init/3, rest_init/2, content_types_accepted/2, content_types_provided/2, - resource_exists/2 - ]} - ]). + resource_exists/2]}]). --export([ allowed_methods/2 - , handle_get/2]). +-export([allowed_methods/2, handle_get/2]). %trails -behaviour(trails_handler). + -export([trails/0]). -spec trails() -> [trails:trail()]. trails() -> - Metadata = - #{get => - #{tags => ["whoami"], - description => "Get hostname", - responses => #{ - <<"200">> => #{ - description => <<"Get hostname 200 OK">>, - content => #{ - 'text/plain' => - #{schema => #{ - type => string - } - } - } - } - } - } - }, - [trails:trail( - "/whoami", - host1_handler, - #{}, - Metadata)]. + Metadata = + #{get => + #{tags => ["whoami"], + description => "Get hostname", + responses => + #{<<"200">> => + #{description => <<"Get hostname 200 OK">>, + content => #{'text/plain' => #{schema => #{type => string}}}}}}}, + [trails:trail("/whoami", host1_handler, #{}, Metadata)]. %% cowboy --spec allowed_methods(Req, State) -> {[binary(), ...], Req, State}. +-spec allowed_methods(Req, State) -> {[<<_:24>>, ...], Req, State}. allowed_methods(Req, State) -> - {[<<"GET">>], Req, State}. + {[<<"GET">>], Req, State}. %% internal --spec handle_get(Req, State) -> {binary(), Req, State}. +-spec handle_get(cowboy_req:req(), State) -> {<<_:40, _:_*8>>, cowboy_req:req(), State}. handle_get(Req, State) -> - Host = cowboy_req:host(Req), - Body = <<"I am ", Host/binary>>, - {Body, Req, State}. + Host = cowboy_req:host(Req), + Body = <<"I am ", Host/binary>>, + {Body, Req, State}. diff --git a/test/multiple_hosts_servers_example.app b/test/multiple_hosts_servers_example.app index 249e78f..8c3f11d 100644 --- a/test/multiple_hosts_servers_example.app +++ b/test/multiple_hosts_servers_example.app @@ -1,18 +1,8 @@ -{application, multiple_hosts_servers_example, - [ - {description, "Cowboy Swagger Complex Example."}, +{application, + multiple_hosts_servers_example, + [{description, "Cowboy Swagger Complex Example."}, {vsn, "0.1"}, - {applications, - [ kernel - , stdlib - , sasl - - , cowboy - , trails - , cowboy_swagger - ]}, + {applications, [kernel, stdlib, sasl, cowboy, trails, cowboy_swagger]}, {modules, []}, {mod, {multiple_hosts_servers_example, []}}, - {start_phases, [{start_multiple_hosts_servers_example_http, []}]} - ] -}. + {start_phases, [{start_multiple_hosts_servers_example_http, []}]}]}. diff --git a/test/multiple_hosts_servers_example.erl b/test/multiple_hosts_servers_example.erl index 463e807..b84979f 100644 --- a/test/multiple_hosts_servers_example.erl +++ b/test/multiple_hosts_servers_example.erl @@ -6,67 +6,67 @@ -export([stop/1]). -export([start_phase/3]). +-hank([unnecessary_function_arguments]). + %% application %% @doc Starts the application -spec start() -> {ok, [atom()]}. start() -> - application:ensure_all_started(multiple_hosts_servers_example). + application:ensure_all_started(multiple_hosts_servers_example). %% @doc Stops the application -spec stop() -> ok. stop() -> - application:stop(multiple_hosts_servers_example). + application:stop(multiple_hosts_servers_example). %% behaviour %% @private --spec start(normal, [any()]) -> {ok, pid()} | - {ok, pid(), any()} | - {error, any()}. +-spec start(normal, [any()]) -> {ok, pid()}. start(_StartType, _StartArgs) -> - _ = application:stop(lager), - ok = application:stop(sasl), - {ok, _} = application:ensure_all_started(sasl), - {ok, self()}. + _ = application:stop(lager), + ok = application:stop(sasl), + {ok, _} = application:ensure_all_started(sasl), + {ok, self()}. %% @private -spec stop(_) -> ok. stop(_State) -> - ok = cowboy:stop_listener(multiple_hosts_servers_http). + ok = cowboy:stop_listener(multiple_hosts_servers_http). --spec start_phase(atom(), application:start_type(), []) -> ok | {error, term()}. +-spec start_phase(atom(), application:start_type(), []) -> ok. start_phase(start_multiple_hosts_servers_example_http, _StartType, []) -> - %% Host1 - {ok, #{hosts := [HostMatch11, HostMatch12], port := Port1}} = - application:get_env(multiple_hosts_servers_example, api1), - {ok, #{hosts := ['_'], port := Port2}} = - application:get_env(multiple_hosts_servers_example, api2), + %% Host1 + {ok, #{hosts := [HostMatch11, HostMatch12], port := Port1}} = + application:get_env(multiple_hosts_servers_example, api1), + {ok, #{hosts := ['_'], port := Port2}} = + application:get_env(multiple_hosts_servers_example, api2), - Trails11 = - trails:trails(example_echo_handler) ++ - cowboy_swagger_handler:trails(#{server => api1, host => HostMatch11}), - Trails12 = - trails:trails(host1_handler) ++ - cowboy_swagger_handler:trails(#{server => api1, host => HostMatch12}), - Routes1 = [{HostMatch11, Trails11}, {HostMatch12, Trails12}], + Trails11 = + trails:trails(example_echo_handler) + ++ cowboy_swagger_handler:trails(#{server => api1, host => HostMatch11}), + Trails12 = + trails:trails(host1_handler) + ++ cowboy_swagger_handler:trails(#{server => api1, host => HostMatch12}), + Routes1 = [{HostMatch11, Trails11}, {HostMatch12, Trails12}], - trails:store(api1, Routes1), - Dispatch1 = trails:compile(Routes1), - {ok, _} = start_cowboy(api1, Dispatch1, Port1), + trails:store(api1, Routes1), + Dispatch1 = trails:compile(Routes1), + {ok, _} = start_cowboy(api1, Dispatch1, Port1), - Trails21 = - trails:trails([host1_handler, example_echo_handler]) ++ - cowboy_swagger_handler:trails(#{server => api2}), + Trails21 = + trails:trails([host1_handler, example_echo_handler]) + ++ cowboy_swagger_handler:trails(#{server => api2}), - trails:store(api2, Trails21), - Dispatch2 = trails:single_host_compile(Trails21), - {ok, _} = start_cowboy(api2, Dispatch2, Port2), - ok. + trails:store(api2, Trails21), + Dispatch2 = trails:single_host_compile(Trails21), + {ok, _} = start_cowboy(api2, Dispatch2, Port2), + ok. %% @private start_cowboy(Server, Dispatch, Port) -> - RanchOptions = [{port, Port}], - CowboyOptions = #{ env => #{dispatch => Dispatch} - , compress => true - , timeout => 12000 - }, - cowboy:start_clear(Server, RanchOptions, CowboyOptions). + RanchOptions = [{port, Port}], + CowboyOptions = + #{env => #{dispatch => Dispatch}, + compress => true, + timeout => 12000}, + cowboy:start_clear(Server, RanchOptions, CowboyOptions). diff --git a/test/test.config b/test/test.config index fe58134..0b92ce6 100644 --- a/test/test.config +++ b/test/test.config @@ -1,18 +1,6 @@ -[ { example - , [{http_port, 8080}] - } - -, { multiple_hosts_servers_example - , [ {api1, #{hosts => ["localhost", "127.0.0.1"], port => 8383}} - , {api2, #{hosts => ['_'], port => 8282}} - ] - } -, { cowboy_swagger - , [ { global_spec - , #{ openapi => "3.0.0" - , info => #{title => "Example API"} - } - } - ] - } -]. +[{example, [{http_port, 8080}]}, + {multiple_hosts_servers_example, + [{api1, #{hosts => ["localhost", "127.0.0.1"], port => 8383}}, + {api2, #{hosts => ['_'], port => 8282}}]}, + {cowboy_swagger, + [{global_spec, #{openapi => "3.0.0", info => #{title => "Example API"}}}]}].