├── .awconfig ├── .github └── workflows │ ├── ci.yml │ ├── up_swagger.sh │ └── up_swagger.yml ├── .gitignore ├── .hound.yml ├── .jshintrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SWAGGER_VSN ├── elvis.config ├── example ├── .gitignore ├── README.md ├── config │ ├── sys.config │ └── vm.args ├── priv │ ├── favicon.ico │ └── index.html ├── rebar.config ├── rebar.config.script ├── rebar.lock └── src │ ├── example.app.src │ ├── example.erl │ ├── example_default.erl │ ├── example_description_handler.erl │ ├── example_echo_handler.erl │ └── example_sup.erl ├── priv └── swagger │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── absolute-path.js │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── index.html │ ├── index.js │ ├── oauth2-redirect.html │ ├── package.json │ ├── swagger-initializer.js │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-es-bundle-core.js │ ├── swagger-ui-es-bundle-core.js.map │ ├── swagger-ui-es-bundle.js │ ├── swagger-ui-es-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map ├── rebar.config ├── rebar.lock ├── src ├── cowboy_swagger.app.src ├── cowboy_swagger.erl ├── cowboy_swagger_handler.erl ├── cowboy_swagger_json_handler.erl └── cowboy_swagger_redirect_handler.erl └── test ├── cover.spec ├── cowboy_swagger_SUITE.erl ├── cowboy_swagger_handler_SUITE.erl ├── cowboy_swagger_test_utils.erl ├── example.app ├── example.erl ├── example_default.erl ├── example_description_handler.erl ├── example_echo_handler.erl ├── example_sup.erl ├── host1_handler.erl ├── multiple_hosts_servers_example.app ├── multiple_hosts_servers_example.erl └── test.config /.awconfig: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - "*" 10 | workflow_dispatch: {} 11 | jobs: 12 | ci: 13 | name: Run checks and tests over ${{matrix.otp_vsn}} 14 | runs-on: ubuntu-22.04 15 | strategy: 16 | matrix: 17 | otp_vsn: ['24', '25', '26'] 18 | rebar3_vsn: ['3.22'] 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: erlef/setup-beam@v1 22 | id: setup-beam 23 | with: 24 | otp-version: ${{matrix.otp_vsn}} 25 | rebar3-version: ${{matrix.rebar3_vsn}} 26 | - name: Restore _build 27 | uses: actions/cache@v3 28 | with: 29 | path: | 30 | _build 31 | example/_build 32 | key: "_build-cache-for\ 33 | -os-${{runner.os}}\ 34 | -otp-${{steps.setup-beam.outputs.otp-version}}\ 35 | -rebar3-${{steps.setup-beam.outputs.rebar3-version}}\ 36 | -hash-${{hashFiles('rebar.lock')}}" 37 | - name: Restore rebar3's cache 38 | uses: actions/cache@v3 39 | with: 40 | path: ~/.cache/rebar3 41 | key: "rebar3-cache-for\ 42 | -os-${{runner.os}}\ 43 | -otp-${{steps.setup-beam.outputs.otp-version}}\ 44 | -rebar3-${{steps.setup-beam.outputs.rebar3-version}}\ 45 | -hash-${{hashFiles('rebar.lock')}}" 46 | - name: Format check 47 | run: rebar3 format --verify 48 | - run: rebar3 test 49 | - name: test our example 50 | run: | 51 | cd example 52 | rebar3 release 53 | _build/default/rel/example/bin/example daemon 54 | _build/default/rel/example/bin/example ping 55 | - name: Format check 56 | run: | 57 | cd example 58 | rebar3 format --verify 59 | - name: check our example 60 | run: | 61 | cd example 62 | rebar3 test 63 | -------------------------------------------------------------------------------- /.github/workflows/up_swagger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | # Get Swagger UI version and compare to imported one. Exit if same... 6 | npm install swagger-ui-dist 7 | rm -f package-lock.json 8 | rm -f package.json 9 | NEW_SWAGGER_VSN=$(jq -r .version SWAGGER_VSN 29 | 30 | git config user.name "GitHub Actions" 31 | git config user.email "actions@user.noreply.github.com" 32 | 33 | BRANCH=feature/swagger-ui-update 34 | 35 | if git show-ref --verify --quiet "refs/heads/${BRANCH}"; then 36 | # already exists 37 | exit 38 | fi 39 | 40 | git fetch origin 41 | git checkout -b "${BRANCH}" 42 | 43 | if ! git diff --exit-code 1>/dev/null; then 44 | # there's stuff to push 45 | git add . 46 | git commit -m "Update Swagger UI to ${NEW_SWAGGER_VSN}" 47 | git push origin "${BRANCH}" 48 | 49 | gh pr create --fill \ 50 | --title "Update Swagger UI to ${NEW_SWAGGER_VSN} (automation)" \ 51 | --body "This is an automated action to update the repository's Swagger UI version" 52 | fi 53 | -------------------------------------------------------------------------------- /.github/workflows/up_swagger.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Swagger UI 3 | 4 | "on": 5 | schedule: 6 | - cron: '0 12 * * *' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update: 11 | name: Update 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - run: | 16 | ./.github/workflows/up_swagger.sh 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | erl_crash.dump 3 | .rebar 4 | doc/ 5 | .rebar3 6 | logs 7 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | jshint: 2 | config_file: .jshintrc 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "esversion": 6 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.5.0](https://github.com/inaka/cowboy_swagger/tree/2.5.0) (2021-09-23) 4 | 5 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.4.0...2.5.0) 6 | 7 | **Merged pull requests:** 8 | 9 | - support component parameters [\#140](https://github.com/inaka/cowboy_swagger/pull/140) ([zhongwencool](https://github.com/zhongwencool)) 10 | 11 | ## [2.4.0](https://github.com/inaka/cowboy_swagger/tree/2.4.0) (2021-09-03) 12 | 13 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.3.0...2.4.0) 14 | 15 | **Closed issues:** 16 | 17 | - New swagger UI version [\#136](https://github.com/inaka/cowboy_swagger/issues/136) 18 | 19 | **Merged pull requests:** 20 | 21 | - Swagger UI [\#139](https://github.com/inaka/cowboy_swagger/pull/139) ([DDDHuang](https://github.com/DDDHuang)) 22 | 23 | ## [2.3.0](https://github.com/inaka/cowboy_swagger/tree/2.3.0) (2021-07-28) 24 | 25 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.2.2...2.3.0) 26 | 27 | **Closed issues:** 28 | 29 | - Open API servers field & swagger basePath filed [\#134](https://github.com/inaka/cowboy_swagger/issues/134) 30 | 31 | **Merged pull requests:** 32 | 33 | - Allow for analysis under rebar3\_hank [\#137](https://github.com/inaka/cowboy_swagger/pull/137) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 34 | - basePath & servers support; support more schema type [\#135](https://github.com/inaka/cowboy_swagger/pull/135) ([DDDHuang](https://github.com/DDDHuang)) 35 | 36 | ## [2.2.2](https://github.com/inaka/cowboy_swagger/tree/2.2.2) (2021-07-01) 37 | 38 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.2.1...2.2.2) 39 | 40 | **Closed issues:** 41 | 42 | - GlobalSpec not work [\#132](https://github.com/inaka/cowboy_swagger/issues/132) 43 | - cowboy and ranch version not compatible ? [\#130](https://github.com/inaka/cowboy_swagger/issues/130) 44 | 45 | **Merged pull requests:** 46 | 47 | - fix: swagger & openapi root field [\#133](https://github.com/inaka/cowboy_swagger/pull/133) ([DDDHuang](https://github.com/DDDHuang)) 48 | - Update example app's rebar.lock [\#131](https://github.com/inaka/cowboy_swagger/pull/131) ([zmstone](https://github.com/zmstone)) 49 | - Increase consumer confidence [\#129](https://github.com/inaka/cowboy_swagger/pull/129) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 50 | - Move to a GitHub action \(instead of container-based\) CI approach [\#128](https://github.com/inaka/cowboy_swagger/pull/128) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 51 | 52 | ## [2.2.1](https://github.com/inaka/cowboy_swagger/tree/2.2.1) (2021-02-08) 53 | 54 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.2.0...2.2.1) 55 | 56 | **Implemented enhancements:** 57 | 58 | - Add support to others json libraries [\#52](https://github.com/inaka/cowboy_swagger/issues/52) 59 | - The `cowboy\_swagger\_handler` should return also `swagger.yaml`. [\#20](https://github.com/inaka/cowboy_swagger/issues/20) 60 | 61 | **Fixed bugs:** 62 | 63 | - Actually run on different Erlang versions [\#125](https://github.com/inaka/cowboy_swagger/pull/125) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 64 | 65 | **Closed issues:** 66 | 67 | - Move from Travis CI to GitHub Actions? [\#122](https://github.com/inaka/cowboy_swagger/issues/122) 68 | - Fix current resource path `/api-docs` according with Swagger 2.0 conventions \(`/swagger.json`\) [\#19](https://github.com/inaka/cowboy_swagger/issues/19) 69 | 70 | **Merged pull requests:** 71 | 72 | - Update dep.s to adapt to OTP 24 [\#127](https://github.com/inaka/cowboy_swagger/pull/127) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 73 | - Bump OTP-24 -ready dep.s \(at least at the compilation level\) [\#126](https://github.com/inaka/cowboy_swagger/pull/126) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 74 | - README.md: show `build` instead of `CI for cowboy\_swagger` [\#124](https://github.com/inaka/cowboy_swagger/pull/124) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 75 | - Replace Travis CI with GitHub Actions [\#123](https://github.com/inaka/cowboy_swagger/pull/123) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 76 | - Modernize CI [\#121](https://github.com/inaka/cowboy_swagger/pull/121) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 77 | 78 | ## [2.2.0](https://github.com/inaka/cowboy_swagger/tree/2.2.0) (2020-05-26) 79 | 80 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.2.4...2.2.0) 81 | 82 | **Closed issues:** 83 | 84 | - Unknown type cowboy\_router:route\_match [\#115](https://github.com/inaka/cowboy_swagger/issues/115) 85 | 86 | **Merged pull requests:** 87 | 88 | - Release request [\#120](https://github.com/inaka/cowboy_swagger/pull/120) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 89 | - Support 'components' section in openapi 3.0.0 [\#119](https://github.com/inaka/cowboy_swagger/pull/119) ([brucify](https://github.com/brucify)) 90 | - New function add\_definition\_array/2 in cowboy\_swagger [\#118](https://github.com/inaka/cowboy_swagger/pull/118) ([brucify](https://github.com/brucify)) 91 | - Fix/for xref and dialyzer [\#117](https://github.com/inaka/cowboy_swagger/pull/117) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 92 | - handle openapi 3.0 format, use new swagger-ui [\#114](https://github.com/inaka/cowboy_swagger/pull/114) ([szlartibartfaszt79](https://github.com/szlartibartfaszt79)) 93 | 94 | ## [1.2.4](https://github.com/inaka/cowboy_swagger/tree/1.2.4) (2019-02-27) 95 | 96 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.1.0...1.2.4) 97 | 98 | **Merged pull requests:** 99 | 100 | - Fix a few static analysis warnings [\#116](https://github.com/inaka/cowboy_swagger/pull/116) ([paulo-ferraz-oliveira](https://github.com/paulo-ferraz-oliveira)) 101 | 102 | ## [2.1.0](https://github.com/inaka/cowboy_swagger/tree/2.1.0) (2018-05-04) 103 | 104 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/2.0.0...2.1.0) 105 | 106 | **Closed issues:** 107 | 108 | - Update sync by rebar3\_auto [\#95](https://github.com/inaka/cowboy_swagger/issues/95) 109 | 110 | **Merged pull requests:** 111 | 112 | - Update from @freke [\#113](https://github.com/inaka/cowboy_swagger/pull/113) ([elbrujohalcon](https://github.com/elbrujohalcon)) 113 | - Bump Version to 2.1.0 [\#112](https://github.com/inaka/cowboy_swagger/pull/112) ([elbrujohalcon](https://github.com/elbrujohalcon)) 114 | - Update Dependencies [\#111](https://github.com/inaka/cowboy_swagger/pull/111) ([elbrujohalcon](https://github.com/elbrujohalcon)) 115 | - Add rebar3 to root path to fix travis builds [\#110](https://github.com/inaka/cowboy_swagger/pull/110) ([elbrujohalcon](https://github.com/elbrujohalcon)) 116 | 117 | ## [2.0.0](https://github.com/inaka/cowboy_swagger/tree/2.0.0) (2017-11-30) 118 | 119 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.2.3...2.0.0) 120 | 121 | **Closed issues:** 122 | 123 | - Public hipchat room [\#105](https://github.com/inaka/cowboy_swagger/issues/105) 124 | - Consider upgrading to cowboy 2 API [\#49](https://github.com/inaka/cowboy_swagger/issues/49) 125 | 126 | **Merged pull requests:** 127 | 128 | - Bump version to 2.0.0 [\#108](https://github.com/inaka/cowboy_swagger/pull/108) ([jfacorro](https://github.com/jfacorro)) 129 | - \[Closes \#49\] Update to Cowboy 2 [\#107](https://github.com/inaka/cowboy_swagger/pull/107) ([jfacorro](https://github.com/jfacorro)) 130 | - Update README.md [\#106](https://github.com/inaka/cowboy_swagger/pull/106) ([igaray](https://github.com/igaray)) 131 | 132 | ## [1.2.3](https://github.com/inaka/cowboy_swagger/tree/1.2.3) (2017-06-13) 133 | 134 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.2.2...1.2.3) 135 | 136 | **Closed issues:** 137 | 138 | - Bump Version to 1.2.3 [\#103](https://github.com/inaka/cowboy_swagger/issues/103) 139 | - Version Bump to 1.2.2 [\#92](https://github.com/inaka/cowboy_swagger/issues/92) 140 | 141 | **Merged pull requests:** 142 | 143 | - \[Close \#103\] Version Bump to 1.2.3 [\#104](https://github.com/inaka/cowboy_swagger/pull/104) ([Euen](https://github.com/Euen)) 144 | - Use minified version of swagger-ui.js [\#102](https://github.com/inaka/cowboy_swagger/pull/102) ([egobrain](https://github.com/egobrain)) 145 | - Maintain original order in lists [\#101](https://github.com/inaka/cowboy_swagger/pull/101) ([jfacorro](https://github.com/jfacorro)) 146 | - Revert to local schema as default [\#100](https://github.com/inaka/cowboy_swagger/pull/100) ([lucafavatella](https://github.com/lucafavatella)) 147 | - Clean leftovers from jiffy -\> jsx transition [\#99](https://github.com/inaka/cowboy_swagger/pull/99) ([lucafavatella](https://github.com/lucafavatella)) 148 | - Plant CI [\#97](https://github.com/inaka/cowboy_swagger/pull/97) ([lucafavatella](https://github.com/lucafavatella)) 149 | - \[\#95\] update sync by rebar3\_auto [\#96](https://github.com/inaka/cowboy_swagger/pull/96) ([Euen](https://github.com/Euen)) 150 | 151 | ## [1.2.2](https://github.com/inaka/cowboy_swagger/tree/1.2.2) (2017-01-27) 152 | 153 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.2.1...1.2.2) 154 | 155 | **Closed issues:** 156 | 157 | - Update to the latest Swagger-UI assets [\#89](https://github.com/inaka/cowboy_swagger/issues/89) 158 | - remove Makefile from the example [\#86](https://github.com/inaka/cowboy_swagger/issues/86) 159 | 160 | **Merged pull requests:** 161 | 162 | - \[\#92\] Version Bump to 1.2.2 [\#94](https://github.com/inaka/cowboy_swagger/pull/94) ([ferigis](https://github.com/ferigis)) 163 | - \[\#89\] upgrade Swagger-UI to 2.2.8 [\#91](https://github.com/inaka/cowboy_swagger/pull/91) ([ferigis](https://github.com/ferigis)) 164 | - \[\#86\] Fixing Example [\#90](https://github.com/inaka/cowboy_swagger/pull/90) ([ferigis](https://github.com/ferigis)) 165 | 166 | ## [1.2.1](https://github.com/inaka/cowboy_swagger/tree/1.2.1) (2017-01-17) 167 | 168 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.2.0...1.2.1) 169 | 170 | **Closed issues:** 171 | 172 | - This project should be moved to cowboy\_swagger [\#75](https://github.com/inaka/cowboy_swagger/issues/75) 173 | - Version Bump to 1.2.1 [\#84](https://github.com/inaka/cowboy_swagger/issues/84) 174 | - Update dependencies [\#83](https://github.com/inaka/cowboy_swagger/issues/83) 175 | - hex.pm package is broken [\#82](https://github.com/inaka/cowboy_swagger/issues/82) 176 | - CowboySwagger should support either binaries or io\_lists for descriptions [\#67](https://github.com/inaka/cowboy_swagger/issues/67) 177 | - Update cowboy-swager dependency version in the example [\#50](https://github.com/inaka/cowboy_swagger/issues/50) 178 | - Update documentation [\#44](https://github.com/inaka/cowboy_swagger/issues/44) 179 | 180 | **Merged pull requests:** 181 | 182 | - \[\#84\] Version Bump to 1.2.1 [\#88](https://github.com/inaka/cowboy_swagger/pull/88) ([ferigis](https://github.com/ferigis)) 183 | - \[\#44\] Adding 'Requirements' to README [\#87](https://github.com/inaka/cowboy_swagger/pull/87) ([ferigis](https://github.com/ferigis)) 184 | - \[\#83\] updating dependencies on rebar3 config file [\#85](https://github.com/inaka/cowboy_swagger/pull/85) ([ferigis](https://github.com/ferigis)) 185 | 186 | ## [1.2.0](https://github.com/inaka/cowboy_swagger/tree/1.2.0) (2016-11-16) 187 | 188 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.1.0...1.2.0) 189 | 190 | **Fixed bugs:** 191 | 192 | - Broken implementation of jsx [\#79](https://github.com/inaka/cowboy_swagger/issues/79) 193 | 194 | **Closed issues:** 195 | 196 | - Bump version to 1.2.0 [\#78](https://github.com/inaka/cowboy_swagger/issues/78) 197 | - Definitions description in README [\#73](https://github.com/inaka/cowboy_swagger/issues/73) 198 | - Update example to rebar3 [\#72](https://github.com/inaka/cowboy_swagger/issues/72) 199 | 200 | **Merged pull requests:** 201 | 202 | - \[Fix \#78\] Bump version to 1.2.0 [\#81](https://github.com/inaka/cowboy_swagger/pull/81) ([harenson](https://github.com/harenson)) 203 | - fixed dialyser error 'no local return' then definition includes an array [\#80](https://github.com/inaka/cowboy_swagger/pull/80) ([freke](https://github.com/freke)) 204 | - \[Close \#72\] update example build tool to rebar3 [\#77](https://github.com/inaka/cowboy_swagger/pull/77) ([Euen](https://github.com/Euen)) 205 | - Replace jiffy by jsx [\#76](https://github.com/inaka/cowboy_swagger/pull/76) ([BernardNotarianni](https://github.com/BernardNotarianni)) 206 | 207 | ## [1.1.0](https://github.com/inaka/cowboy_swagger/tree/1.1.0) (2016-08-11) 208 | 209 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.0.3...1.1.0) 210 | 211 | **Fixed bugs:** 212 | 213 | - .app missing jiffy in applications section [\#64](https://github.com/inaka/cowboy_swagger/issues/64) 214 | 215 | **Closed issues:** 216 | 217 | - Version Bump to 1.1.0 [\#70](https://github.com/inaka/cowboy_swagger/issues/70) 218 | - Move from erlang.mk to rebar3 [\#68](https://github.com/inaka/cowboy_swagger/issues/68) 219 | - Support for definitions and schemas [\#63](https://github.com/inaka/cowboy_swagger/issues/63) 220 | - Hello can this project support the cowboy REST part? [\#61](https://github.com/inaka/cowboy_swagger/issues/61) 221 | - Do whatever it takes to appears cowboy-swagger on "http://swagger.io/open-source-integrations/" [\#16](https://github.com/inaka/cowboy_swagger/issues/16) 222 | 223 | **Merged pull requests:** 224 | 225 | - \[Close \#70\] version bump 1.1.0 [\#71](https://github.com/inaka/cowboy_swagger/pull/71) ([Euen](https://github.com/Euen)) 226 | - \[Close \#68\] replace erlang.mk by rebar3 [\#69](https://github.com/inaka/cowboy_swagger/pull/69) ([Euen](https://github.com/Euen)) 227 | - \[Fix \#63\] Add support for definitions and schemas [\#66](https://github.com/inaka/cowboy_swagger/pull/66) ([harenson](https://github.com/harenson)) 228 | - Add missing jiffy app in .app.src [\#65](https://github.com/inaka/cowboy_swagger/pull/65) ([jeanparpaillon](https://github.com/jeanparpaillon)) 229 | 230 | ## [1.0.3](https://github.com/inaka/cowboy_swagger/tree/1.0.3) (2016-04-14) 231 | 232 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.0.2...1.0.3) 233 | 234 | **Fixed bugs:** 235 | 236 | - Use cp instead of symlink for example deps [\#59](https://github.com/inaka/cowboy_swagger/pull/59) ([elbrujohalcon](https://github.com/elbrujohalcon)) 237 | 238 | **Closed issues:** 239 | 240 | - Switch build tools to erlang.mk and republish to hex.pm [\#47](https://github.com/inaka/cowboy_swagger/issues/47) 241 | 242 | **Merged pull requests:** 243 | 244 | - Version Bump to 1.0.3 [\#60](https://github.com/inaka/cowboy_swagger/pull/60) ([elbrujohalcon](https://github.com/elbrujohalcon)) 245 | - Handles the property parameter as json list when it's empty [\#57](https://github.com/inaka/cowboy_swagger/pull/57) ([joaohf](https://github.com/joaohf)) 246 | 247 | ## [1.0.2](https://github.com/inaka/cowboy_swagger/tree/1.0.2) (2016-03-15) 248 | 249 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/1.0.1...1.0.2) 250 | 251 | **Closed issues:** 252 | 253 | - Bump version to 1.0.2 [\#55](https://github.com/inaka/cowboy_swagger/issues/55) 254 | - Update repo and make it ready for hex.pm [\#53](https://github.com/inaka/cowboy_swagger/issues/53) 255 | - Wrong inaka\_mixer dependency [\#51](https://github.com/inaka/cowboy_swagger/issues/51) 256 | 257 | **Merged pull requests:** 258 | 259 | - \[Fix \#55\] Bump version to 1.0.2 [\#56](https://github.com/inaka/cowboy_swagger/pull/56) ([harenson](https://github.com/harenson)) 260 | - \[Fix \#53\] Update dependencies; Update erlang.mk; Add ruleset to elvis config [\#54](https://github.com/inaka/cowboy_swagger/pull/54) ([harenson](https://github.com/harenson)) 261 | - Example application uses local soruces [\#48](https://github.com/inaka/cowboy_swagger/pull/48) ([jeanparpaillon](https://github.com/jeanparpaillon)) 262 | 263 | ## [1.0.1](https://github.com/inaka/cowboy_swagger/tree/1.0.1) (2016-01-08) 264 | 265 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/0.1.0-otp17...1.0.1) 266 | 267 | **Merged pull requests:** 268 | 269 | - \[\#quick\] upgrade hexer.mk [\#46](https://github.com/inaka/cowboy_swagger/pull/46) ([elbrujohalcon](https://github.com/elbrujohalcon)) 270 | - Hex Package [\#33](https://github.com/inaka/cowboy_swagger/pull/33) ([elbrujohalcon](https://github.com/elbrujohalcon)) 271 | 272 | ## [0.1.0-otp17](https://github.com/inaka/cowboy_swagger/tree/0.1.0-otp17) (2015-12-15) 273 | 274 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/0.1.0...0.1.0-otp17) 275 | 276 | **Closed issues:** 277 | 278 | - Location in response headers is not reading path values [\#43](https://github.com/inaka/cowboy_swagger/issues/43) 279 | 280 | ## [0.1.0](https://github.com/inaka/cowboy_swagger/tree/0.1.0) (2015-12-03) 281 | 282 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/0.0.1...0.1.0) 283 | 284 | **Fixed bugs:** 285 | 286 | - /api-docs not working as expected [\#38](https://github.com/inaka/cowboy_swagger/issues/38) 287 | - The catch all path '...' added by cowboy-swagger introduces some confusion when expecting 404 [\#28](https://github.com/inaka/cowboy_swagger/issues/28) 288 | - Fix validate\_metadata spec [\#27](https://github.com/inaka/cowboy_swagger/issues/27) 289 | - static\_files as an env variable is not good for releases [\#26](https://github.com/inaka/cowboy_swagger/issues/26) 290 | - swagger UI always sends JSON [\#24](https://github.com/inaka/cowboy_swagger/issues/24) 291 | - Trails added by swagger itself should be ignored in swagger.json [\#23](https://github.com/inaka/cowboy_swagger/issues/23) 292 | - basePath is misinterpreted [\#22](https://github.com/inaka/cowboy_swagger/pull/22) ([elbrujohalcon](https://github.com/elbrujohalcon)) 293 | 294 | **Closed issues:** 295 | 296 | - Version Bump to 0.1.0 [\#37](https://github.com/inaka/cowboy_swagger/issues/37) 297 | - Allow more than one server running on the same node [\#34](https://github.com/inaka/cowboy_swagger/issues/34) 298 | - Add Meta Testing [\#30](https://github.com/inaka/cowboy_swagger/issues/30) 299 | - basePath should be considered part of the trails path [\#25](https://github.com/inaka/cowboy_swagger/issues/25) 300 | 301 | **Merged pull requests:** 302 | 303 | - \[Fix \#37\] Bump version to 0.1.0 [\#42](https://github.com/inaka/cowboy_swagger/pull/42) ([harenson](https://github.com/harenson)) 304 | - \[Fix \#38\] Add redirect for /api-docs ... [\#41](https://github.com/inaka/cowboy_swagger/pull/41) ([harenson](https://github.com/harenson)) 305 | - \[\#25\] Add basePath to swaggerSpec [\#40](https://github.com/inaka/cowboy_swagger/pull/40) ([harenson](https://github.com/harenson)) 306 | - \[Fix \#34\] Allow more than one server running on the same node [\#39](https://github.com/inaka/cowboy_swagger/pull/39) ([harenson](https://github.com/harenson)) 307 | - \[Fix \#28\] Change catch-all path [\#36](https://github.com/inaka/cowboy_swagger/pull/36) ([harenson](https://github.com/harenson)) 308 | - \[Fix \#26\] Fix static\_files trail [\#35](https://github.com/inaka/cowboy_swagger/pull/35) ([harenson](https://github.com/harenson)) 309 | - Add meta testing [\#32](https://github.com/inaka/cowboy_swagger/pull/32) ([harenson](https://github.com/harenson)) 310 | - \[fix \#27\] Fix metadata spec [\#31](https://github.com/inaka/cowboy_swagger/pull/31) ([harenson](https://github.com/harenson)) 311 | - Ferigis.23.ignore swagger trails [\#29](https://github.com/inaka/cowboy_swagger/pull/29) ([ferigis](https://github.com/ferigis)) 312 | 313 | ## [0.0.1](https://github.com/inaka/cowboy_swagger/tree/0.0.1) (2015-09-14) 314 | 315 | [Full Changelog](https://github.com/inaka/cowboy_swagger/compare/8ea2dca534154b7453789eb6ed10cd6074385405...0.0.1) 316 | 317 | **Closed issues:** 318 | 319 | - Validate and/or set default values of `cowboy\_swagger:metadata\(\)`, according with swagger schema. [\#9](https://github.com/inaka/cowboy_swagger/issues/9) 320 | - Implement static handler to serve the .html contained in `priv/swagger` folder [\#6](https://github.com/inaka/cowboy_swagger/issues/6) 321 | - Implement `trails\_handler:trails/0` callback in Cowboy swagger handler module \(swagger\_handler\). [\#5](https://github.com/inaka/cowboy_swagger/issues/5) 322 | - Implement `cowboy\_swagger:to\_json/1` function [\#4](https://github.com/inaka/cowboy_swagger/issues/4) 323 | - Implement GET method in 'swagger\_handler' in order to retrieve the JSON specification \(swagger.json\) [\#3](https://github.com/inaka/cowboy_swagger/issues/3) 324 | - Setup Swagger-UI into the project. [\#2](https://github.com/inaka/cowboy_swagger/issues/2) 325 | - Fulfil the open-source check-list [\#1](https://github.com/inaka/cowboy_swagger/issues/1) 326 | 327 | **Merged pull requests:** 328 | 329 | - \[\#1\] Fixed documentation in modules. Fixed README. [\#21](https://github.com/inaka/cowboy_swagger/pull/21) ([cabol](https://github.com/cabol)) 330 | - Make link text and href point at the same URL [\#18](https://github.com/inaka/cowboy_swagger/pull/18) ([erszcz](https://github.com/erszcz)) 331 | - \[\#1\] fulfil open source list: README. [\#17](https://github.com/inaka/cowboy_swagger/pull/17) ([cabol](https://github.com/cabol)) 332 | - \[\#1\] Fulfil the open-source check-list: implemented example. [\#15](https://github.com/inaka/cowboy_swagger/pull/15) ([cabol](https://github.com/cabol)) 333 | - \[\#9\] Validate mandatory fields in the metadata. [\#14](https://github.com/inaka/cowboy_swagger/pull/14) ([cabol](https://github.com/cabol)) 334 | - Fixed cowboy-swagger to be a lib \(removed app and sup modules\). Added… [\#12](https://github.com/inaka/cowboy_swagger/pull/12) ([cabol](https://github.com/cabol)) 335 | - Cabol.3.cowboy swagger handler [\#10](https://github.com/inaka/cowboy_swagger/pull/10) ([cabol](https://github.com/cabol)) 336 | - Implemented cowboy\_swagger:to\_json/1 function. [\#8](https://github.com/inaka/cowboy_swagger/pull/8) ([cabol](https://github.com/cabol)) 337 | - Project and Swagger-UI setup. [\#7](https://github.com/inaka/cowboy_swagger/pull/7) ([cabol](https://github.com/cabol)) 338 | 339 | 340 | 341 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 342 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # cowboy-swagger 4 | 5 | [Swagger](https://swagger.io/) integration for [Cowboy](https://github.com/ninenines/cowboy) (built on [trails](https://github.com/inaka/cowboy-trails)). 6 | 7 | ![build](https://github.com/inaka/cowboy_swagger/workflows/build/badge.svg) 8 | 9 | ## Contact Us 10 | 11 | If you find any **bugs** or have a **problem** while using this library, please 12 | [open an issue](https://github.com/inaka/cowboy_swagger/issues/new) in this repo 13 | (or a pull request :)). 14 | 15 | ## Why Cowboy Swagger? 16 | 17 | Simple, because there isn't a tool in Erlang to document Cowboy RESTful APIs easy and fast, 18 | and to improve development productivity. 19 | 20 | With `cowboy_swagger` is possible to integrate Swagger to your Erlang projects that use Cowboy as a web server. 21 | It is extremely easy to use, and with just a few steps you'll have a nice Web documentation for your RESTful APIs. 22 | 23 | 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/). 24 | 25 | ## How to Use it? 26 | 27 | This is the best part. It is extremely easy. 28 | 29 | ### 1. Document each Cowboy Handler 30 | 31 | Because `cowboy_swagger` runs on top of `trails`, the first thing that you have to do 32 | is document all about your handler within the trails metadata. Keep in mind that 33 | all fields defined within each method into the metadata must be compliant with the 34 | [Swagger specification](https://swagger.io/specification). 35 | 36 | For example, suppose that you have `example_echo_handler`, so it must implement the 37 | `c:trails_handler:trails/0` callback: 38 | 39 | ```erlang 40 | trails() -> 41 | Metadata = 42 | #{get => 43 | #{tags => ["echo"], 44 | description => "Gets echo var from the server", 45 | produces => ["text/plain"] 46 | }, 47 | put => 48 | #{tags => ["echo"], 49 | description => "Sets echo var in the server", 50 | produces => ["text/plain"], 51 | parameters => [ 52 | #{name => <<"echo">>, 53 | description => <<"Echo message">>, 54 | in => <<"path">>, 55 | required => false, 56 | type => <<"string">>} 57 | ] 58 | } 59 | }, 60 | [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. 61 | ``` 62 | 63 | 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). 64 | 65 | ### 2. Include cowboy_swagger in your app 66 | 67 | First, you need to include `cowboy_swagger_handler` module in your list of trails to be compiled. 68 | 69 | ```erlang 70 | % Include cowboy_swagger_handler in the trails list 71 | Trails = trails:trails([example_echo_handler, 72 | example_description_handler, 73 | cowboy_swagger_handler]), 74 | % store them 75 | trails:store(Trails), 76 | % and then compile them 77 | Dispatch = trails:single_host_compile(Trails), 78 | ``` 79 | 80 | 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). 81 | 82 | Then add `cowboy_swagger` to the list of apps to be loaded in your `*.app.src` file. 83 | 84 | ```erlang 85 | {application, example, 86 | [ 87 | {description, "Cowboy Swagger Basic Example."}, 88 | {vsn, "0.1"}, 89 | {applications, 90 | [kernel, 91 | stdlib, 92 | jsx, 93 | cowboy, 94 | trails, 95 | cowboy_swagger 96 | ]}, 97 | {modules, []}, 98 | {mod, {example, []}}, 99 | {registered, []}, 100 | {start_phases, [{start_trails_http, []}]} 101 | ] 102 | }. 103 | ``` 104 | 105 | And that's it, you got it. Now start your application and then you will have access to the API docs 106 | under the path `/api-docs`. Supposing that you're running the app on `localhost:8080`, 107 | that will be [http://localhost:8080/api-docs](http://localhost:8080/api-docs). 108 | 109 | ## Configuration 110 | 111 | Additionally, `cowboy_swagger` can be configured/customized from a `*.config` file: 112 | 113 | ### app.config 114 | 115 | ```erlang 116 | [ 117 | %% Other apps ... 118 | 119 | %% cowboy_swagger config 120 | {cowboy_swagger, 121 | [ 122 | %% `static_files`: Static content directory. This is where Swagger-UI 123 | %% is located. Default: `priv/swagger`. 124 | %% Remember that Swagger-UI is embedded into `cowboy-swagger` project, 125 | %% within `priv/swagger` folder. BUT you have to reference that path, 126 | %% and depending on how you're using `cowboy-swagger` it will be different. 127 | %% For example, assuming that you want to run your app which has 128 | %% `cowboy-swagger` as dependency from the console, `static_files` will be: 129 | {static_files, "./deps/cowboy_swagger/priv/swagger"}, 130 | 131 | %% `global_spec`: Global fields for Swagger specification. 132 | %% If these fields are not set, `cowboy_swagger` will set default values. 133 | {global_spec, 134 | #{swagger => "2.0", 135 | info => #{title => "Example API"}, 136 | basePath => "/api-docs" 137 | } 138 | } 139 | ] 140 | } 141 | ]. 142 | ``` 143 | 144 | ### Definitions 145 | 146 | [Definitions](https://swagger.io/specification/#definitionsObject) can be used for describing 147 | [parameters](https://swagger.io/specification/#parametersDefinitionsObject), 148 | [responses](https://swagger.io/specification/#responsesDefinitionsObject) and 149 | [security](https://swagger.io/specification/#securityDefinitionsObject) schemas. 150 | 151 | For adding definitions to your app, you have 2 choices: 152 | 153 | 1. Add a `definitions` key to your cowboy_swagger `global_spec` map. 154 | 2. Add them by calling `cowboy_swagger:add_definition/2` and send the 155 | definition's name and properties. 156 | 157 | Let's say you want to describe a `POST` call to a `newspapers` endpoint that requires 158 | `name` and `description` fields only, you can do it like this: 159 | 160 | **Option 1:** 161 | 162 | ```erlang 163 | [ ... % other configurations 164 | , { cowboy_swagger 165 | , [ { global_spec 166 | , #{ swagger => "2.0" 167 | , info => #{title => "My app API"} 168 | , definitions => #{ 169 | "RequestBody" => 170 | #{ "name" => 171 | #{ "type" => "string" 172 | , "description" => "Newspaper name" 173 | } 174 | , "description" => 175 | #{ "type" => "string" 176 | , "description" => "Newspaper description" 177 | } 178 | } 179 | } 180 | } 181 | } 182 | ] 183 | } 184 | ] 185 | ``` 186 | 187 | **Option 2:** 188 | 189 | For the second choice, you can do it for example in one or several `start_phases`, 190 | directly in your handler or any other place you want. 191 | 192 | ```erlang 193 | -spec trails() -> trails:trails(). 194 | trails() -> 195 | DefinitionName = <<"RequestBody">>, 196 | DefinitionProperties = 197 | #{ <<"name">> => 198 | #{ type => <<"string">> 199 | , description => <<"Newspaper name">> 200 | } 201 | , <<"description">> => 202 | #{ type => <<"string">> 203 | , description => <<"Newspaper description">> 204 | } 205 | }, 206 | % Add the definition 207 | ok = cowboy_swagger:add_definition(DefinitionName, DefinitionProperties), 208 | ... 209 | ``` 210 | 211 | Now in your handler's trails callback function you can use it: 212 | 213 | ```erlang 214 | ... 215 | RequestBody = 216 | #{ name => <<"request body">> 217 | , in => body 218 | , description => <<"request body (as json)">> 219 | , required => true 220 | % Use the previously created `RequestBody' definition 221 | , schema => cowboy_swagger:schema(<<"RequestBody">>) 222 | }, 223 | Metadata = 224 | #{ get => 225 | #{ tags => ["newspapers"] 226 | , description => "Returns the list of newspapers" 227 | , produces => ["application/json"] 228 | } 229 | , post => 230 | # { tags => ["newspapers"] 231 | , description => "Creates a new newspaper" 232 | , consumes => ["application/json"] 233 | , produces => ["application/json"] 234 | , parameters => [RequestBody] % and then use that parameter here 235 | } 236 | }, 237 | Path = "/newspapers", 238 | Options = #{path => Path}, 239 | [trails:trail(Path, newspapers_handler, Options, Metadata)]. 240 | ``` 241 | 242 | What this does for you is add a nice `response`, `parameter` or `security` 243 | model in swagger-ui, so client developers will know exactly what parameters 244 | the API expects for every endpoint. 245 | 246 | ## Example 247 | 248 | For more information about `cowboy_swagger` and how to use it, please check this [Example](https://github.com/inaka/cowboy_swagger/tree/master/example). 249 | -------------------------------------------------------------------------------- /SWAGGER_VSN: -------------------------------------------------------------------------------- 1 | 5.17.14 2 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [{elvis, 2 | [{config, 3 | [#{dirs => ["src", "test"], 4 | filter => "*.erl", 5 | ruleset => erl_files, 6 | rules => 7 | [{elvis_style, 8 | atom_naming_convention, 9 | #{regex => "^(([a-z][a-z0-9]*_?)*(_SUITE)?|basePath|swagger_2_0|openapi_3_0_0)$"}}]}, 10 | #{dirs => ["."], 11 | filter => "rebar.config", 12 | ruleset => rebar_config}, 13 | #{dirs => ["."], 14 | filter => "elvis.config", 15 | ruleset => elvis_config}]}]}]. 16 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.log 3 | *.plt 4 | *pem 5 | .DS_Store 6 | all.coverdata 7 | bin 8 | /deps/cowboy 9 | /deps/cowlib 10 | /deps/hexer_mk 11 | /deps/inaka_mk 12 | /deps/mixer 13 | /deps/ranch 14 | /deps/trails 15 | ebin 16 | erl_crash.dump 17 | log 18 | logs 19 | .erlang.mk.packages.v2 20 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Cowboy Swagger OpenAPI 3.0.0 Example 2 | 3 | To try this example, you need GNU `rebar3` , `git` and `Erlang` in your PATH. 4 | 5 | ``` 6 | rebar3 release 7 | ``` 8 | 9 | To start the example server in a interactive way do this: 10 | ``` 11 | _build/default/rel/example/bin/example console 12 | ``` 13 | 14 | Now, if we point our favorite web browser at [http://localhost:8080/api-docs](http://localhost:8080/api-docs), 15 | we should see the swagger API doc. 16 | -------------------------------------------------------------------------------- /example/config/sys.config: -------------------------------------------------------------------------------- 1 | [{example, [{http_port, 8080}]}, 2 | {cowboy_swagger, 3 | [{static_files, "priv/swagger"}, 4 | {global_spec, #{openapi => "3.0.0", info => #{title => "Example API"}}}]}]. 5 | -------------------------------------------------------------------------------- /example/config/vm.args: -------------------------------------------------------------------------------- 1 | -name example@127.0.0.1 2 | -setcookie bogus 3 | -------------------------------------------------------------------------------- /example/priv/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inaka/cowboy_swagger/24334d88d76770f32ee0eed2218c94e7e2eb8720/example/priv/favicon.ico -------------------------------------------------------------------------------- /example/priv/index.html: -------------------------------------------------------------------------------- 1 |

This is a Cowboy Swagger example!

2 |

Swagger Documentation!

3 | -------------------------------------------------------------------------------- /example/rebar.config: -------------------------------------------------------------------------------- 1 | %% == Compiler and Profiles == 2 | 3 | {erl_opts, 4 | [warn_unused_import, warn_export_vars, warnings_as_errors, verbose, report, debug_info]}. 5 | 6 | {minimum_otp_vsn, "23"}. 7 | 8 | {alias, [{test, [compile, format, hank, lint, xref, dialyzer]}]}. 9 | 10 | %% == Dependencies and plugins == 11 | 12 | {deps, [{mixer, "1.2.0", {pkg, inaka_mixer}}, cowboy_swagger]}. 13 | 14 | {project_plugins, 15 | [{rebar3_hank, "~> 1.4.0"}, {rebar3_format, "~> 1.3.0"}, {rebar3_lint, "~> 3.0.1"}]}. 16 | 17 | %% == Format == 18 | 19 | {format, [{files, ["*.config", "src/*"]}]}. 20 | 21 | %% == Hank == 22 | 23 | {hank, [{ignore, ["_build/**", "_checkouts"]}]}. 24 | 25 | %% == Dialyzer + XRef == 26 | 27 | {dialyzer, 28 | [{warnings, [no_return, underspecs, unmatched_returns, error_handling, unknown]}]}. 29 | 30 | {xref_checks, 31 | [undefined_function_calls, deprecated_function_calls, deprecated_functions]}. 32 | 33 | {xref_extra_paths, ["test/**"]}. 34 | 35 | %% == Release == 36 | 37 | {relx, 38 | [{include_src, false}, 39 | {extended_start_script, true}, 40 | {release, {example, "0.1"}, [example, sasl]}, 41 | {sys_config, "./config/sys.config"}, 42 | {vm_args, "./config/vm.args"}, 43 | {overlay, [{copy, "./_checkouts/cowboy_swagger/priv/swagger", "priv/swagger"}]}]}. 44 | 45 | %% The above should be changed to the following when cowboy_swagger is 46 | %% used as a normal dependency (instead of _checkouts): 47 | %% {copy, "./_build/default/lib/cowboy_swagger/priv/swagger", "priv/swagger"} 48 | -------------------------------------------------------------------------------- /example/rebar.config.script: -------------------------------------------------------------------------------- 1 | %% To avoid specifying a version we add cowboy_swagger in the 2 | %% _checkouts directory. 3 | os:cmd("mkdir _checkouts; ln -s $PWD/.. _checkouts/cowboy_swagger"). 4 | 5 | %% Return the original rebar3 configuration. 6 | CONFIG. 7 | -------------------------------------------------------------------------------- /example/rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.8.0">>},1}, 3 | {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.9.1">>},2}, 4 | {<<"jsx">>,{pkg,<<"jsx">>,<<"2.9.0">>},1}, 5 | {<<"mixer">>,{pkg,<<"inaka_mixer">>,<<"1.2.0">>},0}, 6 | {<<"ranch">>,{pkg,<<"ranch">>,<<"2.0.0">>},1}, 7 | {<<"trails">>,{pkg,<<"trails">>,<<"2.3.0">>},1}]}. 8 | [ 9 | {pkg_hash,[ 10 | {<<"cowboy">>, <<"F3DC62E35797ECD9AC1B50DB74611193C29815401E53BAC9A5C0577BD7BC667D">>}, 11 | {<<"cowlib">>, <<"61A6C7C50CF07FDD24B2F45B89500BB93B6686579B069A89F88CB211E1125C78">>}, 12 | {<<"jsx">>, <<"D2F6E5F069C00266CAD52FB15D87C428579EA4D7D73A33669E12679E203329DD">>}, 13 | {<<"mixer">>, <<"FC11970AE637FA3081F608A96BD67CA4442C7D7BF9C482E6DD26E6C4B517213D">>}, 14 | {<<"ranch">>, <<"FBF3D79661C071543256F9051CAF19D65DAA6DF1CF6824D8F37A49B19A66F703">>}, 15 | {<<"trails">>, <<"B09703F056705F4943E14FFF077B98C711A6F48FAD40F4FF0B350794074AD69C">>}]}, 16 | {pkg_hash_ext,[ 17 | {<<"cowboy">>, <<"4643E4FBA74AC96D4D152C75803DE6FAD0B3FA5DF354C71AFDD6CBEEB15FAC8A">>}, 18 | {<<"cowlib">>, <<"E4175DC240A70D996156160891E1C62238EDE1729E45740BDD38064DAD476170">>}, 19 | {<<"jsx">>, <<"8EE1DB1CABAFDD578A2776A6AAAE87C2A8CE54B47B59E9EC7DAB5D7EB71CD8DC">>}, 20 | {<<"mixer">>, <<"FB2332717158BBA05CEE8A76CCA76C3E0BE0EAD8EC23EE2415EA9AE65D726A48">>}, 21 | {<<"ranch">>, <<"C20A4840C7D6623C19812D3A7C828B2F1BD153EF0F124CB69C54FE51D8A42AE0">>}, 22 | {<<"trails">>, <<"40804001EB80417AA9D02400F39B7216956C3F251539A8A6096A69B3FAC0EA07">>}]} 23 | ]. 24 | -------------------------------------------------------------------------------- /example/src/example.app.src: -------------------------------------------------------------------------------- 1 | {application, 2 | example, 3 | [{description, "Cowboy Swagger OpenAPI 3.0.0 Basic Example."}, 4 | {vsn, "0.1"}, 5 | {applications, [kernel, stdlib, jsx, cowboy, trails, cowboy_swagger]}, 6 | {modules, []}, 7 | {mod, {example, []}}, 8 | {registered, []}, 9 | {start_phases, [{start_trails_http, []}]}, 10 | {build_tools, ["rebar3"]}]}. 11 | -------------------------------------------------------------------------------- /example/src/example.erl: -------------------------------------------------------------------------------- 1 | -module(example). 2 | 3 | -export([start/0]). 4 | -export([start/2]). 5 | -export([stop/0]). 6 | -export([stop/1]). 7 | -export([start_phase/3]). 8 | 9 | -hank([unnecessary_function_arguments]). 10 | 11 | %% application 12 | %% @doc Starts the application 13 | start() -> 14 | application:ensure_all_started(example). 15 | 16 | %% @doc Stops the application 17 | stop() -> 18 | application:stop(example). 19 | 20 | %% behaviour 21 | %% @private 22 | start(_StartType, _StartArgs) -> 23 | example_sup:start_link(). 24 | 25 | %% @private 26 | stop(_State) -> 27 | ok = cowboy:stop_listener(example_http). 28 | 29 | -spec start_phase(atom(), application:start_type(), []) -> ok. 30 | start_phase(start_trails_http, _StartType, []) -> 31 | {ok, Port} = application:get_env(example, http_port), 32 | Trails = 33 | trails:trails([example_echo_handler, 34 | example_description_handler, 35 | cowboy_swagger_handler]), 36 | trails:store(Trails), 37 | Dispatch = trails:single_host_compile(Trails), 38 | RanchOptions = [{port, Port}], 39 | CowboyOptions = 40 | #{env => #{dispatch => Dispatch}, 41 | compress => true, 42 | timeout => 12000}, 43 | 44 | {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), 45 | ok. 46 | -------------------------------------------------------------------------------- /example/src/example_default.erl: -------------------------------------------------------------------------------- 1 | -module(example_default). 2 | 3 | -behaviour(cowboy_rest). 4 | 5 | -export([init/2, rest_init/2, content_types_accepted/2, content_types_provided/2, 6 | forbidden/2, resource_exists/2]). 7 | 8 | %% cowboy 9 | init(Req, _Opts) -> 10 | {cowboy_rest, Req, #{}}. 11 | 12 | rest_init(Req, _Opts) -> 13 | {ok, Req, #{}}. 14 | 15 | content_types_accepted(Req, State) -> 16 | {[{'*', handle_put}], Req, State}. 17 | 18 | content_types_provided(Req, State) -> 19 | {[{<<"text/plain">>, handle_get}], Req, State}. 20 | 21 | forbidden(Req, State) -> 22 | {false, Req, State}. 23 | 24 | resource_exists(Req, State) -> 25 | {true, Req, State}. 26 | -------------------------------------------------------------------------------- /example/src/example_description_handler.erl: -------------------------------------------------------------------------------- 1 | -module(example_description_handler). 2 | 3 | -behaviour(cowboy_rest). 4 | 5 | -include_lib("mixer/include/mixer.hrl"). 6 | 7 | -mixin([{example_default, 8 | [init/2, 9 | rest_init/2, 10 | content_types_accepted/2, 11 | content_types_provided/2, 12 | resource_exists/2]}]). 13 | 14 | -export([allowed_methods/2, handle_get/2]). 15 | 16 | %trails 17 | -behaviour(trails_handler). 18 | 19 | -export([trails/0]). 20 | 21 | trails() -> 22 | Metadata = 23 | #{get => 24 | #{tags => ["example"], 25 | description => "Retrives trails's server description", 26 | responses => 27 | #{<<"200">> => 28 | #{description => <<"Retrives trails's server description 200 OK">>, 29 | content => #{'text/plain' => #{schema => #{type => string}}}}}}}, 30 | [trails:trail("/description", example_description_handler, [], Metadata)]. 31 | 32 | %% cowboy 33 | allowed_methods(Req, State) -> 34 | {[<<"GET">>], Req, State}. 35 | 36 | %% internal 37 | handle_get(Req, State) -> 38 | Body = trails:all(), 39 | {io_lib:format("~p~n", [Body]), Req, State}. 40 | -------------------------------------------------------------------------------- /example/src/example_echo_handler.erl: -------------------------------------------------------------------------------- 1 | -module(example_echo_handler). 2 | 3 | -behaviour(cowboy_rest). 4 | 5 | -include_lib("mixer/include/mixer.hrl"). 6 | 7 | -mixin([{example_default, 8 | [init/2, 9 | rest_init/2, 10 | content_types_accepted/2, 11 | content_types_provided/2, 12 | resource_exists/2]}]). 13 | 14 | -export([allowed_methods/2, handle_put/2, handle_get/2]). 15 | 16 | %trails 17 | -behaviour(trails_handler). 18 | 19 | -export([trails/0]). 20 | 21 | trails() -> 22 | Metadata = 23 | #{get => 24 | #{tags => ["echo"], 25 | description => "Gets echo var from the server", 26 | responses => 27 | #{<<"200">> => 28 | #{description => <<"Gets echo var from the server 200 OK">>, 29 | content => #{'text/plain' => #{schema => #{type => string}}}}}}, 30 | put => 31 | #{tags => ["echo"], 32 | description => "Sets echo var in the server", 33 | parameters => 34 | [#{name => <<"echo-kebab_case">>, 35 | description => <<"Echo message">>, 36 | in => <<"path">>, 37 | required => false, 38 | schema => #{type => string, example => <<"Hello, World!">>}}]}}, 39 | [trails:trail("/message/[:echo-kebab_case]", example_echo_handler, [], Metadata)]. 40 | 41 | %% cowboy 42 | allowed_methods(Req, State) -> 43 | {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. 44 | 45 | %% internal 46 | handle_get(Req, State) -> 47 | Echo = application:get_env(example, echo, ""), 48 | Body = [<<"You Get an echo!">>, Echo], 49 | {Body, Req, State}. 50 | 51 | handle_put(Req, State) -> 52 | Echo = cowboy_req:binding('echo-kebab_case', Req, ""), 53 | application:set_env(example, echo, Echo), 54 | Body = [<<"You put an echo! ">>, Echo], 55 | Req1 = cowboy_req:set_resp_body(Body, Req), 56 | {true, Req1, State}. 57 | -------------------------------------------------------------------------------- /example/src/example_sup.erl: -------------------------------------------------------------------------------- 1 | -module(example_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0]). 6 | -export([init/1]). 7 | 8 | %% admin api 9 | start_link() -> 10 | supervisor:start_link({local, ?MODULE}, ?MODULE, {}). 11 | 12 | %% behaviour callbacks 13 | init({}) -> 14 | {ok, {{one_for_one, 5, 10}, []}}. 15 | -------------------------------------------------------------------------------- /priv/swagger/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /priv/swagger/NOTICE: -------------------------------------------------------------------------------- 1 | swagger-ui 2 | Copyright 2020-2021 SmartBear Software Inc. 3 | -------------------------------------------------------------------------------- /priv/swagger/README.md: -------------------------------------------------------------------------------- 1 | # Swagger UI Dist 2 | [![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist) 3 | 4 | # API 5 | 6 | This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module. 7 | Use `swagger-ui` instead, if you'd like to have npm install dependencies for you. 8 | 9 | `SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported: 10 | ```javascript 11 | import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist" 12 | ``` 13 | 14 | To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method: 15 | 16 | ```javascript 17 | const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath() 18 | 19 | // then instantiate server that serves files from the swaggerUiAssetPath 20 | ``` 21 | 22 | For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository. 23 | -------------------------------------------------------------------------------- /priv/swagger/absolute-path.js: -------------------------------------------------------------------------------- 1 | /* 2 | * getAbsoluteFSPath 3 | * @return {string} When run in NodeJS env, returns the absolute path to the current directory 4 | * When run outside of NodeJS, will return an error message 5 | */ 6 | const getAbsoluteFSPath = function () { 7 | // detect whether we are running in a browser or nodejs 8 | if (typeof module !== "undefined" && module.exports) { 9 | return require("path").resolve(__dirname) 10 | } 11 | throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment'); 12 | } 13 | 14 | module.exports = getAbsoluteFSPath 15 | -------------------------------------------------------------------------------- /priv/swagger/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inaka/cowboy_swagger/24334d88d76770f32ee0eed2218c94e7e2eb8720/priv/swagger/favicon-16x16.png -------------------------------------------------------------------------------- /priv/swagger/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inaka/cowboy_swagger/24334d88d76770f32ee0eed2218c94e7e2eb8720/priv/swagger/favicon-32x32.png -------------------------------------------------------------------------------- /priv/swagger/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /priv/swagger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /priv/swagger/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") 3 | module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") 4 | } catch(e) { 5 | // swallow the error if there's a problem loading the assets. 6 | // allows this module to support providing the assets for browserish contexts, 7 | // without exploding in a Node context. 8 | // 9 | // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388 10 | // for more information. 11 | } 12 | 13 | // `absolutePath` and `getAbsoluteFSPath` are both here because at one point, 14 | // we documented having one and actually implemented the other. 15 | // They were both retained so we don't break anyone's code. 16 | module.exports.absolutePath = require("./absolute-path.js") 17 | module.exports.getAbsoluteFSPath = require("./absolute-path.js") 18 | -------------------------------------------------------------------------------- /priv/swagger/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI: OAuth2 Redirect 5 | 6 | 7 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /priv/swagger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-ui-dist", 3 | "version": "5.17.14", 4 | "main": "index.js", 5 | "repository": "git@github.com:swagger-api/swagger-ui.git", 6 | "contributors": [ 7 | "(in alphabetical order)", 8 | "Anna Bodnia ", 9 | "Buu Nguyen ", 10 | "Josh Ponelat ", 11 | "Kyle Shockey ", 12 | "Robert Barnwell ", 13 | "Sahar Jafari " 14 | ], 15 | "license": "Apache-2.0", 16 | "dependencies": {}, 17 | "devDependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /priv/swagger/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: window.location.origin + "/api-docs/swagger.json", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% == Compiler and Profiles == 2 | 3 | {erl_opts, 4 | [warn_unused_import, warn_export_vars, warnings_as_errors, verbose, report, debug_info]}. 5 | 6 | {minimum_otp_vsn, "23"}. 7 | 8 | {profiles, 9 | [{test, 10 | [{deps, [{mixer, "1.2.0", {pkg, inaka_mixer}}, {shotgun, "1.0.1"}]}, 11 | {ct_opts, [{sys_config, ["./test/test.config"]}, {verbose, true}]}, 12 | {cover_enabled, true}, 13 | {cover_opts, [verbose]}, 14 | {dialyzer, 15 | [{warnings, [no_return, unmatched_returns, error_handling, underspecs, unknown]}, 16 | {plt_extra_apps, [cowboy, trails, ranch, jsx, common_test, shotgun]}]}]}]}. 17 | 18 | {alias, [{test, [compile, format, hank, lint, xref, dialyzer, ct, cover, ex_doc]}]}. 19 | 20 | %% == Dependencies and plugins == 21 | 22 | {deps, [{jsx, "3.1.0"}, {cowboy, "2.10.0"}, {ranch, "2.1.0"}, {trails, "2.3.1"}]}. 23 | 24 | {project_plugins, 25 | [{rebar3_hank, "~> 1.4.0"}, 26 | {rebar3_hex, "~> 7.0.7"}, 27 | {rebar3_format, "~> 1.3.0"}, 28 | {rebar3_lint, "~> 3.1.0"}, 29 | {rebar3_ex_doc, "~> 0.2.20"}]}. 30 | 31 | %% == Documentation == 32 | 33 | {ex_doc, 34 | [{source_url, <<"https://github.com/inaka/cowboy_swagger">>}, 35 | {extras, [<<"README.md">>, <<"LICENSE">>]}, 36 | {main, <<"README.md">>}, 37 | {prefix_ref_vsn_with_v, false}]}. 38 | 39 | {hex, [{doc, #{provider => ex_doc}}]}. 40 | 41 | %% == Format == 42 | 43 | {format, [{files, ["*.config", "src/*", "test/*"]}]}. 44 | 45 | %% == Dialyzer + XRef == 46 | 47 | {dialyzer, 48 | [{warnings, [no_return, unmatched_returns, error_handling, underspecs, unknown]}, 49 | {plt_extra_apps, [cowboy, trails, ranch, jsx]}]}. 50 | 51 | {xref_checks, 52 | [undefined_function_calls, deprecated_function_calls, deprecated_functions]}. 53 | 54 | {xref_extra_paths, ["test/**"]}. 55 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.10.0">>},0}, 3 | {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.12.1">>},1}, 4 | {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}, 5 | {<<"ranch">>,{pkg,<<"ranch">>,<<"2.1.0">>},0}, 6 | {<<"trails">>,{pkg,<<"trails">>,<<"2.3.1">>},0}]}. 7 | [ 8 | {pkg_hash,[ 9 | {<<"cowboy">>, <<"FF9FFEFF91DAE4AE270DD975642997AFE2A1179D94B1887863E43F681A203E26">>}, 10 | {<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>}, 11 | {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, 12 | {<<"ranch">>, <<"2261F9ED9574DCFCC444106B9F6DA155E6E540B2F82BA3D42B339B93673B72A3">>}, 13 | {<<"trails">>, <<"BB940CD0D5187A8FB0BB05F3DB575B5A1CAC20A980EDACE092CA2033AACD0BAB">>}]}, 14 | {pkg_hash_ext,[ 15 | {<<"cowboy">>, <<"3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B">>}, 16 | {<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>}, 17 | {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, 18 | {<<"ranch">>, <<"244EE3FA2A6175270D8E1FC59024FD9DBC76294A321057DE8F803B1479E76916">>}, 19 | {<<"trails">>, <<"1006E38E09BA095AE77DD9A50BB3E8EBDB8DC31ECBC5BF276C3D848C15D7079B">>}]} 20 | ]. 21 | -------------------------------------------------------------------------------- /src/cowboy_swagger.app.src: -------------------------------------------------------------------------------- 1 | {application, 2 | cowboy_swagger, 3 | [{description, "Swagger for Cowboy Erlang projects"}, 4 | {vsn, git}, 5 | {applications, [kernel, stdlib, ranch, cowboy, trails, jsx]}, 6 | {modules, []}, 7 | {registered, []}, 8 | {licenses, ["Apache 2.0"]}, 9 | {links, 10 | [{"GitHub", "https://github.com/inaka/cowboy-swagger"}, 11 | {"Blog", 12 | "https://web.archive.org/web/20160621233046/http://inaka.net/blog/2015/08/19/cowboy-swagger/"}, 13 | {"Example", "https://github.com/inaka/cowboy-swagger/tree/master/example"}]}, 14 | {build_tools, ["rebar3"]}]}. 15 | -------------------------------------------------------------------------------- /src/cowboy_swagger.erl: -------------------------------------------------------------------------------- 1 | %%% @doc cowboy-swagger main interface. 2 | -module(cowboy_swagger). 3 | 4 | %% API 5 | -export([to_json/1, add_definition/1, add_definition/2, add_definition_array/2, 6 | schema/1]). 7 | %% Utilities 8 | -export([enc_json/1, dec_json/1, normalize_json/1]). 9 | -export([swagger_paths/1, validate_metadata/1]). 10 | -export([filter_cowboy_swagger_handler/1]). 11 | -export([get_existing_definitions/2, get_global_spec/0, get_global_spec/1, 12 | set_global_spec/1]). 13 | 14 | % is_visible is used as a maps:filter/2 predicate, which requires a /2 arity function 15 | -hank([{unnecessary_function_arguments, [{is_visible, 2}]}]). 16 | 17 | -elvis([{elvis_style, no_throw, disable}]). 18 | 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | %% Types. 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | 23 | -opaque parameter_obj() :: 24 | #{name => binary(), 25 | in => binary(), 26 | description => binary(), 27 | required => boolean(), 28 | type => binary(), 29 | schema => binary()}. 30 | 31 | -export_type([parameter_obj/0]). 32 | 33 | -opaque response_obj() :: #{description => binary()}. 34 | 35 | -type responses_definitions() :: #{binary() => response_obj()}. 36 | 37 | -export_type([response_obj/0, responses_definitions/0]). 38 | 39 | -type parameter_definition_name() :: binary(). 40 | -type property_desc() :: 41 | #{type => binary(), 42 | description => binary(), 43 | example => binary(), 44 | items => property_desc()}. 45 | -type property_obj() :: #{binary() => property_desc()}. 46 | -type parameters_definitions() :: 47 | #{parameter_definition_name() => 48 | #{type => binary(), 49 | properties => property_obj(), 50 | _ => _}}. 51 | -type parameters_definition_array() :: 52 | #{parameter_definition_name() => 53 | #{type => binary(), items => #{type => binary(), properties => property_obj()}}}. 54 | 55 | -export_type([parameter_definition_name/0, property_obj/0, parameters_definitions/0, 56 | parameters_definition_array/0]). 57 | 58 | %% Swagger map spec 59 | -opaque swagger_map() :: 60 | #{description => binary(), 61 | summary => binary(), 62 | parameters => [parameter_obj()], 63 | tags => [binary()], 64 | consumes => [binary()], 65 | produces => [binary()], 66 | responses => responses_definitions()}. 67 | 68 | -type metadata() :: trails:metadata(swagger_map()). 69 | 70 | -export_type([swagger_map/0, metadata/0]). 71 | 72 | -type swagger_version() :: swagger_2_0 | openapi_3_0_0. 73 | 74 | -export_type([swagger_version/0]). 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | %% Public API. 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | 80 | %% @doc Returns the swagger json specification from given `trails'. 81 | %% This function basically takes the metadata from each `t:trails:trail()' 82 | %% (which must be compliant with Swagger specification) and builds the 83 | %% required `swagger.json'. 84 | -spec to_json([trails:trail()]) -> jsx:json_text(). 85 | to_json(Trails) -> 86 | Default = #{info => #{title => <<"API-DOCS">>}}, 87 | GlobalSpec = get_global_spec(Default), 88 | SanitizeTrails = filter_cowboy_swagger_handler(Trails), 89 | SwaggerSpec = create_swagger_spec(GlobalSpec, SanitizeTrails), 90 | enc_json(SwaggerSpec). 91 | 92 | -spec add_definition_array(Name :: parameter_definition_name(), 93 | Properties :: property_obj()) -> 94 | ok. 95 | add_definition_array(Name, Properties) -> 96 | DefinitionArray = build_definition_array(Name, Properties), 97 | add_definition(DefinitionArray). 98 | 99 | -spec add_definition(Name :: parameter_definition_name(), Properties :: property_obj()) -> 100 | ok. 101 | add_definition(Name, Properties) -> 102 | Definition = build_definition(Name, Properties), 103 | add_definition(Definition). 104 | 105 | -spec add_definition(Definition :: 106 | parameters_definitions() | parameters_definition_array()) -> 107 | ok. 108 | add_definition(Definition) -> 109 | CurrentSpec = get_global_spec(), 110 | NormDefinition = normalize_json(Definition), 111 | Type = definition_type(NormDefinition), 112 | NewDefinitions = 113 | maps:merge(get_existing_definitions(CurrentSpec, Type), normalize_json(NormDefinition)), 114 | NewSpec = prepare_new_global_spec(CurrentSpec, NewDefinitions, Type), 115 | set_global_spec(NewSpec). 116 | 117 | definition_type(Definition) -> 118 | case maps:values(Definition) of 119 | [#{<<"in">> := In}] when In =:= <<"query">>; In =:= <<"path">>; In =:= <<"header">> -> 120 | <<"parameters">>; 121 | _ -> 122 | <<"schemas">> 123 | end. 124 | 125 | -spec schema(DefinitionName :: parameter_definition_name()) -> 126 | #{<<_:32>> => <<_:64, _:_*8>>}. 127 | schema(DefinitionName) -> 128 | case swagger_version() of 129 | swagger_2_0 -> 130 | #{<<"$ref">> => <<"#/definitions/", DefinitionName/binary>>}; 131 | openapi_3_0_0 -> 132 | #{<<"$ref">> => <<"#/components/schemas/", DefinitionName/binary>>} 133 | end. 134 | 135 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 136 | %% Utilities. 137 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 138 | 139 | %% @private 140 | -spec enc_json(jsx:json_term()) -> jsx:json_text(). 141 | enc_json(Json) -> 142 | jsx:encode(Json, [uescape]). 143 | 144 | %% @private 145 | -spec dec_json(iodata()) -> jsx:json_term(). 146 | dec_json(Data) -> 147 | try 148 | jsx:decode(Data, [return_maps]) 149 | catch 150 | _:{error, _} -> 151 | throw(bad_json) 152 | end. 153 | 154 | %% We assume the jsx representation of JSON as Erlang terms: 155 | %% true/false/null: 'true' | 'false' | 'null' 156 | %% number: integer() | float() 157 | %% string: binary() | atom() 158 | %% array: [ JSON ] 159 | %% object: #{ Label => JSON, ... } | [{ Label, JSON }] | [{}] 160 | %% date string: {{Year, Month, Day}, {Hour, Min, Sec}} 161 | %% where 162 | %% Label: binary() | atom() | integer() 163 | %% 164 | %% We also detect lists of printable characters (plain Erlang strings) and 165 | %% convert them into binaries. This use is deprecated and should be 166 | %% removed (for example, a json array [64] becomes <<"@">>). 167 | %% 168 | %% When normalizing, we make all strings and labels be binaries, 169 | %% and all objects be maps, not proplists. 170 | 171 | %% @private 172 | -spec normalize_json(jsx:json_term()) -> jsx:json_term(). 173 | normalize_json(Json) when is_map(Json) -> 174 | normalize_json_proplist(maps:to_list(Json)); 175 | normalize_json([]) -> 176 | []; % empty array 177 | normalize_json([{}]) -> 178 | #{}; % special case in jsx for empty map as list 179 | normalize_json([{_K, _V} | _] = Json) -> 180 | normalize_json_proplist(Json); % map as proplist 181 | normalize_json(Json) when is_list(Json) -> 182 | case io_lib:printable_list(Json) of 183 | true -> 184 | unicode:characters_to_binary(Json); 185 | false -> 186 | normalize_json_list(Json) 187 | end; 188 | normalize_json(true) -> 189 | true; 190 | normalize_json(false) -> 191 | false; 192 | normalize_json(null) -> 193 | null; 194 | normalize_json(Json) when is_atom(Json) -> 195 | erlang:atom_to_binary(Json, utf8); 196 | normalize_json(Json) -> 197 | Json. 198 | 199 | normalize_json_key(K) when is_atom(K) -> 200 | erlang:atom_to_binary(K, utf8); 201 | normalize_json_key(K) when is_integer(K) -> 202 | erlang:integer_to_binary(K); 203 | normalize_json_key(K) -> 204 | K. 205 | 206 | normalize_json_proplist(Proplist) -> 207 | F = fun({K, V}, Acc) -> maps:put(normalize_json_key(K), normalize_json(V), Acc) end, 208 | lists:foldl(F, #{}, Proplist). 209 | 210 | normalize_json_list(List) -> 211 | F = fun(V, Acc) -> [normalize_json(V) | Acc] end, 212 | lists:foldr(F, [], List). 213 | 214 | %% @private 215 | -spec swagger_paths([trails:trail()]) -> map(). 216 | swagger_paths(Trails) -> 217 | swagger_paths(Trails, undefined). 218 | 219 | -spec swagger_paths([trails:trail()], binary() | string() | undefined) -> map(). 220 | swagger_paths(Trails, BasePath) -> 221 | Paths = translate_swagger_paths(Trails, #{}), 222 | refactor_base_path(Paths, BasePath). 223 | 224 | %% @private 225 | -spec validate_metadata(trails:metadata(_)) -> metadata(). 226 | validate_metadata(Metadata) -> 227 | validate_swagger_map(Metadata). 228 | 229 | %% @private 230 | -spec filter_cowboy_swagger_handler([trails:trail()]) -> [trails:trail()]. 231 | filter_cowboy_swagger_handler(Trails) -> 232 | %% Keeps only trails with at least one non-hidden method. 233 | %% (All the cowboy_swagger_handler methdods are marked as hidden.) 234 | F = fun(Trail) -> 235 | MD = get_metadata(Trail), 236 | maps:size( 237 | maps:filter(fun is_visible/2, MD)) 238 | /= 0 239 | end, 240 | lists:filter(F, Trails). 241 | 242 | -spec get_existing_definitions(CurrentSpec :: jsx:json_term(), 243 | Type :: atom() | binary()) -> 244 | Definition :: 245 | parameters_definitions() | parameters_definition_array(). 246 | get_existing_definitions(CurrentSpec, Type) when is_atom(Type) -> 247 | get_existing_definitions(CurrentSpec, atom_to_binary(Type, utf8)); 248 | get_existing_definitions(CurrentSpec, Type) when is_binary(Type) -> 249 | case swagger_version() of 250 | swagger_2_0 -> 251 | maps:get(<<"definitions">>, CurrentSpec, #{}); 252 | openapi_3_0_0 -> 253 | case CurrentSpec of 254 | #{<<"components">> := #{Type := Def}} -> 255 | Def; 256 | _Other -> 257 | #{} 258 | end 259 | end. 260 | 261 | -spec get_global_spec() -> jsx:json_term(). 262 | get_global_spec() -> 263 | get_global_spec(#{}). 264 | 265 | -spec get_global_spec(jsx:json_term()) -> jsx:json_term(). 266 | get_global_spec(Default) -> 267 | normalize_json(application:get_env(cowboy_swagger, global_spec, Default)). 268 | 269 | -spec set_global_spec(jsx:json_term()) -> ok. 270 | set_global_spec(NewSpec) -> 271 | application:set_env(cowboy_swagger, global_spec, normalize_json(NewSpec)). 272 | 273 | -spec get_metadata(trails:trail()) -> jsx:json_term(). 274 | get_metadata(Trail) -> 275 | normalize_json(trails:metadata(Trail)). 276 | 277 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 278 | %% Private API. 279 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 280 | 281 | %% @private 282 | -spec swagger_version() -> swagger_version(). 283 | swagger_version() -> 284 | case get_global_spec() of 285 | #{<<"openapi">> := <<"3.0.0">>} -> 286 | openapi_3_0_0; 287 | #{<<"swagger">> := <<"2.0">>} -> 288 | swagger_2_0; 289 | _Other -> 290 | swagger_2_0 291 | end. 292 | 293 | %% @private 294 | is_visible(_Key, Metadata) when is_map(Metadata) -> 295 | %% Note that `"hidden"` is not a standard flag in OpenAPI 296 | not maps:get(<<"hidden">>, Metadata, false); 297 | is_visible(_Key, _Metadata) -> 298 | false. 299 | 300 | %% @private 301 | translate_swagger_paths([], Acc) -> 302 | Acc; 303 | translate_swagger_paths([Trail | T], Acc) -> 304 | Path = normalize_path(trails:path_match(Trail)), 305 | Metadata = validate_metadata(get_metadata(Trail)), 306 | translate_swagger_paths(T, maps:put(Path, Metadata, Acc)). 307 | 308 | %% @private 309 | refactor_base_path(PathMap, undefined) -> 310 | PathMap; 311 | refactor_base_path(PathMap, BasePath) when is_list(BasePath) -> 312 | refactor_base_path(PathMap, list_to_binary(BasePath)); 313 | refactor_base_path(PathMap, BasePath) -> 314 | Fun = fun(Path, NextPathMap) -> 315 | maps:put(remove_base_path(Path, BasePath), maps:get(Path, PathMap), NextPathMap) 316 | end, 317 | lists:foldl(Fun, #{}, maps:keys(PathMap)). 318 | 319 | %% /base_path/api -> /api 320 | %% @private 321 | -spec remove_base_path(binary(), binary()) -> binary(). 322 | remove_base_path(Path, BasePath) -> 323 | BasePathLength = erlang:size(BasePath), 324 | MatchLength = BasePathLength + 1, 325 | case binary:match(Path, <>) of 326 | {0, MatchLength} -> 327 | binary:part(Path, BasePathLength, erlang:size(Path) - BasePathLength); 328 | _ -> 329 | Path 330 | end. 331 | 332 | %% @private 333 | normalize_path(Path) -> 334 | re:replace( 335 | re:replace(Path, "\\:\\w[\\w-]*", "\\{&\\}", [global]), 336 | "\\[|\\]|\\:", 337 | "", 338 | [{return, binary}, global]). 339 | 340 | %% @private 341 | create_swagger_spec(#{<<"swagger">> := _Version} = GlobalSpec, SanitizeTrails) -> 342 | BasePath = maps:get(<<"basePath">>, GlobalSpec, undefined), 343 | SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), 344 | GlobalSpec#{<<"paths">> => SwaggerPaths}; 345 | create_swagger_spec(#{<<"openapi">> := _Version} = GlobalSpec, SanitizeTrails) -> 346 | BasePath = deconstruct_openapi_url(GlobalSpec), 347 | SwaggerPaths = swagger_paths(SanitizeTrails, BasePath), 348 | GlobalSpec#{<<"paths">> => SwaggerPaths}; 349 | create_swagger_spec(GlobalSpec, SanitizeTrails) -> 350 | create_swagger_spec(GlobalSpec#{<<"openapi">> => <<"3.0.0">>}, SanitizeTrails). 351 | 352 | %% @private 353 | deconstruct_openapi_url(GlobalSpec) -> 354 | [Server | _] = maps:get(<<"servers">>, GlobalSpec, [#{}]), 355 | Url = maps:get(<<"url">>, Server, <<"">>), 356 | maps:get(path, uri_string:parse(Url)). 357 | 358 | %% @private 359 | validate_swagger_map(Map) when is_map(Map) -> 360 | %% Note that although per-path entries are usually methods such as 361 | %% `"get": {...}`, there may also be entries whose values are not maps, 362 | %% such as path-global `"parameters": [...]'. 363 | F = fun (_K, V) when is_map(V) -> 364 | Params = validate_swagger_map_params(maps:get(<<"parameters">>, V, [])), 365 | Responses = validate_swagger_map_responses(maps:get(<<"responses">>, V, #{})), 366 | V#{<<"parameters">> => Params, <<"responses">> => Responses}; 367 | (_K, V) -> 368 | V 369 | end, 370 | maps:map(F, Map); 371 | validate_swagger_map(Other) -> 372 | Other. 373 | 374 | %% @private 375 | validate_swagger_map_params(Params) -> 376 | ValidateParams = 377 | fun(E) -> 378 | case maps:get(<<"name">>, E, undefined) of 379 | undefined -> 380 | maps:is_key(<<"$ref">>, E); 381 | _ -> 382 | {true, E#{<<"in">> => maps:get(<<"in">>, E, <<"path">>)}} 383 | end 384 | end, 385 | lists:filtermap(ValidateParams, Params). 386 | 387 | %% @private 388 | validate_swagger_map_responses(Responses) -> 389 | F = fun(_K, V) -> V#{<<"description">> => maps:get(<<"description">>, V, <<"">>)} end, 390 | maps:map(F, Responses). 391 | 392 | %% @private 393 | -spec build_definition(Name :: parameter_definition_name(), 394 | Properties :: property_obj()) -> 395 | parameters_definitions(). 396 | build_definition(Name, Properties) when is_atom(Name) -> 397 | build_definition(erlang:atom_to_binary(Name, utf8), Properties); 398 | build_definition(Name, Properties) when is_binary(Name) -> 399 | #{Name => #{<<"type">> => <<"object">>, <<"properties">> => Properties}}. 400 | 401 | %% @private 402 | -spec build_definition_array(Name :: parameter_definition_name(), 403 | Properties :: property_obj()) -> 404 | parameters_definition_array(). 405 | build_definition_array(Name, Properties) when is_atom(Name) -> 406 | build_definition_array(erlang:atom_to_binary(Name, utf8), Properties); 407 | build_definition_array(Name, Properties) when is_binary(Name) -> 408 | #{Name => 409 | #{<<"type">> => <<"array">>, 410 | <<"items">> => #{<<"type">> => <<"object">>, <<"properties">> => Properties}}}. 411 | 412 | %% @private 413 | -spec prepare_new_global_spec(CurrentSpec :: jsx:json_term(), 414 | Definitions :: 415 | parameters_definitions() | parameters_definition_array(), 416 | Type :: binary()) -> 417 | NewSpec :: jsx:json_term(). 418 | prepare_new_global_spec(CurrentSpec, Definitions, Type) -> 419 | case swagger_version() of 420 | swagger_2_0 -> 421 | CurrentSpec#{<<"definitions">> => Definitions}; 422 | openapi_3_0_0 -> 423 | Components = maps:get(<<"components">>, CurrentSpec, #{}), 424 | CurrentSpec#{<<"components">> => Components#{Type => Definitions}} 425 | end. 426 | -------------------------------------------------------------------------------- /src/cowboy_swagger_handler.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Cowboy Swagger Handler. This handler exposes a GET operation 2 | %%% to enable `swagger.json' to be retrieved from embedded 3 | %%% Swagger-UI (located in `priv/swagger' folder). 4 | -module(cowboy_swagger_handler). 5 | 6 | %% Trails 7 | -behaviour(trails_handler). 8 | 9 | -export([trails/0, trails/1]). 10 | 11 | -type route_match() :: '_' | iodata(). 12 | 13 | -export_type([route_match/0]). 14 | 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | %%% Trails 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | 19 | %% @private 20 | %% @doc Implements `trails_handler:trails/0' callback. This function returns 21 | %% trails routes for both: static content (Swagger-UI) and this handler 22 | %% that returns the `swagger.json'. 23 | -spec trails() -> trails:trails(). 24 | trails() -> 25 | trails(#{}). 26 | 27 | -spec trails(cowboy_swagger_json_handler:options()) -> trails:trails(). 28 | trails(Options) -> 29 | StaticFiles = 30 | case application:get_env(cowboy_swagger, static_files) of 31 | {ok, Val} -> 32 | Val; 33 | _ -> 34 | filename:join(cowboy_swagger_priv(), "swagger") 35 | end, 36 | Redirect = 37 | trails:trail("/api-docs", 38 | cowboy_swagger_redirect_handler, 39 | {file, StaticFiles ++ "/index.html"}, 40 | #{get => #{hidden => true}}), 41 | Static = 42 | trails:trail("/api-docs/[...]", 43 | cowboy_static, 44 | {dir, StaticFiles, [{mimetypes, cow_mimetypes, all}]}, 45 | #{get => #{hidden => true}}), 46 | MD = #{get => #{hidden => true}}, 47 | Handler = 48 | trails:trail("/api-docs/swagger.json", cowboy_swagger_json_handler, Options, MD), 49 | [Redirect, Handler, Static]. 50 | 51 | %% @private 52 | -spec cowboy_swagger_priv() -> string(). 53 | cowboy_swagger_priv() -> 54 | case code:priv_dir(cowboy_swagger) of 55 | {error, bad_name} -> 56 | case code:which(cowboy_swagger_handler) of 57 | cover_compiled -> 58 | "../../priv"; % required for tests to work 59 | BeamPath -> 60 | filename:join([filename:dirname(BeamPath), "..", "priv"]) 61 | end; 62 | Path -> 63 | Path 64 | end. 65 | -------------------------------------------------------------------------------- /src/cowboy_swagger_json_handler.erl: -------------------------------------------------------------------------------- 1 | %% @private 2 | %%% @doc Cowboy Swagger Handler. This handler exposes a GET operation 3 | %%% to enable `swagger.json' to be retrieved from embedded 4 | %%% Swagger-UI (located in `priv/swagger' folder). 5 | -module(cowboy_swagger_json_handler). 6 | 7 | %% Cowboy callbacks 8 | -export([init/2, content_types_provided/2]). 9 | 10 | -behaviour(cowboy_rest). 11 | 12 | %% Handlers 13 | -export([handle_get/2]). 14 | 15 | -type options() :: 16 | #{server => ranch:ref(), 17 | host => cowboy_swagger_handler:route_match(), 18 | _ => _}. 19 | 20 | -export_type([options/0, state/0]). 21 | 22 | -type state() :: options(). 23 | 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | %%% Cowboy Callbacks 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | 28 | -spec init(cowboy_req:req(), options()) -> {cowboy_rest, cowboy_req:req(), state()}. 29 | init(Req, Opts) -> 30 | State = Opts, 31 | {cowboy_rest, Req, State}. 32 | 33 | -spec content_types_provided(cowboy_req:req(), state()) -> 34 | {[{binary(), atom()}], cowboy_req:req(), state()}. 35 | content_types_provided(Req, State) -> 36 | {[{<<"application/json">>, handle_get}], Req, State}. 37 | 38 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 39 | %%% Handlers 40 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 41 | 42 | -spec handle_get(cowboy_req:req(), state()) -> {iodata(), cowboy_req:req(), state()}. 43 | handle_get(Req, State) -> 44 | Server = maps:get(server, State, '_'), 45 | HostMatch = maps:get(host, State, '_'), 46 | Trails = trails:all(Server, HostMatch), 47 | {cowboy_swagger:to_json(Trails), Req, State}. 48 | -------------------------------------------------------------------------------- /src/cowboy_swagger_redirect_handler.erl: -------------------------------------------------------------------------------- 1 | %% @private 2 | -module(cowboy_swagger_redirect_handler). 3 | 4 | -behaviour(cowboy_rest). 5 | 6 | %% Cowboy callbacks 7 | -export([init/2]). 8 | %% Handlers 9 | -export([resource_exists/2, previously_existed/2, moved_permanently/2]). 10 | 11 | -type state() :: #{}. 12 | 13 | -export_type([state/0]). 14 | 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | %%% Cowboy Callbacks 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | 19 | -spec init(cowboy_req:req(), state()) -> {cowboy_rest, cowboy_req:req(), state()}. 20 | init(Req, State) -> 21 | {cowboy_rest, Req, State}. 22 | 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | %%% Handlers 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | -spec resource_exists(Req :: cowboy_req:req(), State :: state()) -> 27 | {boolean(), cowboy_req:req(), state()}. 28 | resource_exists(Req, State) -> 29 | {false, Req, State}. 30 | 31 | -spec previously_existed(Req :: cowboy_req:req(), State :: state()) -> 32 | {boolean(), cowboy_req:req(), state()}. 33 | previously_existed(Req, State) -> 34 | {true, Req, State}. 35 | 36 | -spec moved_permanently(Req :: cowboy_req:req(), State :: state()) -> 37 | {{true, iodata()}, cowboy_req:req(), state()}. 38 | moved_permanently(Req, State) -> 39 | {{true, "/api-docs/index.html"}, Req, State}. 40 | -------------------------------------------------------------------------------- /test/cover.spec: -------------------------------------------------------------------------------- 1 | %% Specific modules to include in cover. 2 | {incl_mods, 3 | [cowboy_swagger, 4 | cowboy_swagger_handler, 5 | cowboy_swagger_redirect_handler, 6 | cowboy_swagger_json_handler]}. 7 | -------------------------------------------------------------------------------- /test/cowboy_swagger_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(cowboy_swagger_SUITE). 2 | 3 | -include_lib("mixer/include/mixer.hrl"). 4 | 5 | -mixin([{cowboy_swagger_test_utils, [init_per_suite/1, end_per_suite/1]}]). 6 | 7 | -export([all/0]). 8 | -export([to_json_test/1, add_definition_test/1, add_definition_array_test/1, 9 | schema_test/1, parameters_ref_test/1]). 10 | 11 | -hank([unnecessary_function_arguments]). 12 | 13 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 14 | %% Common test 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | 17 | -spec all() -> [atom()]. 18 | all() -> 19 | cowboy_swagger_test_utils:all(?MODULE). 20 | 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | %% Test Cases 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | 25 | -spec to_json_test(cowboy_swagger_test_utils:config()) -> {atom(), string()}. 26 | to_json_test(_Config) -> 27 | set_openapi_url("/basepath"), 28 | Trails = test_trails(), 29 | SwaggerJson = cowboy_swagger:to_json(Trails), 30 | Result = jsx:decode(SwaggerJson, [return_maps]), 31 | #{<<"info">> := #{<<"title">> := <<"Example API">>}, 32 | <<"openapi">> := <<"3.0.0">>, 33 | <<"servers">> := [#{<<"url">> := <<"/basepath">>}], 34 | <<"paths">> := 35 | #{<<"/a">> := 36 | #{<<"get">> := 37 | #{<<"description">> := <<"bla bla bla">>, 38 | <<"parameters">> := [], 39 | <<"responses">> := 40 | #{<<"200">> := 41 | #{<<"content">> := 42 | #{<<"application/json">> := 43 | #{<<"schema">> := 44 | #{<<"title">> := <<"bla">>, 45 | <<"type">> := <<"string">>}}}, 46 | <<"description">> := <<"200 OK">>}}}}, 47 | <<"/a/{b}/{c}">> := 48 | #{<<"delete">> := 49 | #{<<"description">> := <<"bla bla bla">>, 50 | <<"parameters">> := 51 | [#{<<"description">> := <<"bla">>, 52 | <<"in">> := <<"path">>, 53 | <<"name">> := <<"b">>, 54 | <<"required">> := false, 55 | <<"schema">> := #{<<"type">> := <<"string">>}}], 56 | <<"responses">> := #{}}, 57 | <<"get">> := 58 | #{<<"description">> := <<"bla bla bla">>, 59 | <<"parameters">> := 60 | [#{<<"description">> := <<"bla">>, 61 | <<"in">> := <<"path">>, 62 | <<"name">> := <<"b">>, 63 | <<"required">> := false, 64 | <<"schema">> := #{<<"type">> := <<"string">>}}, 65 | #{<<"description">> := <<"bla">>, 66 | <<"in">> := <<"path">>, 67 | <<"name">> := <<"c">>, 68 | <<"required">> := false, 69 | <<"schema">> := 70 | #{<<"example">> := <<"c">>, <<"type">> := <<"string">>}}], 71 | <<"responses">> := 72 | #{<<"200">> := 73 | #{<<"content">> := 74 | #{<<"application/json">> := 75 | #{<<"schema">> := 76 | #{<<"title">> := <<"bla">>, 77 | <<"type">> := <<"string">>}}}, 78 | <<"description">> := <<"200 OK">>}}}, 79 | <<"post">> := 80 | #{<<"description">> := <<"bla bla bla">>, 81 | <<"parameters">> := 82 | [#{<<"description">> := <<"bla">>, 83 | <<"in">> := <<"body">>, 84 | <<"name">> := <<"Request Body">>, 85 | <<"required">> := true, 86 | <<"schema">> := #{<<"type">> := <<"string">>}}], 87 | <<"responses">> := #{<<"200">> := #{<<"description">> := <<"bla">>}}}}, 88 | <<"/a/{b}/{c}/{d}">> := 89 | #{<<"delete">> := 90 | #{<<"description">> := <<"bla bla bla">>, 91 | <<"parameters">> := 92 | [#{<<"description">> := <<"bla">>, 93 | <<"in">> := <<"path">>, 94 | <<"name">> := <<"b">>, 95 | <<"required">> := false, 96 | <<"schema">> := #{<<"type">> := <<"string">>}}], 97 | <<"responses">> := #{}}, 98 | <<"get">> := 99 | #{<<"description">> := <<"bla bla bla">>, 100 | <<"parameters">> := 101 | [#{<<"description">> := <<"bla">>, 102 | <<"in">> := <<"path">>, 103 | <<"name">> := <<"b">>, 104 | <<"required">> := false, 105 | <<"schema">> := #{<<"type">> := <<"string">>}}, 106 | #{<<"description">> := <<"bla">>, 107 | <<"in">> := <<"path">>, 108 | <<"name">> := <<"c">>, 109 | <<"required">> := false, 110 | <<"schema">> := 111 | #{<<"example">> := <<"c">>, <<"type">> := <<"string">>}}], 112 | <<"responses">> := 113 | #{<<"200">> := 114 | #{<<"content">> := 115 | #{<<"application/json">> := 116 | #{<<"schema">> := 117 | #{<<"title">> := <<"bla">>, 118 | <<"type">> := <<"string">>}}}, 119 | <<"description">> := <<"200 OK">>}}}, 120 | <<"post">> := 121 | #{<<"description">> := <<"bla bla bla">>, 122 | <<"parameters">> := 123 | [#{<<"description">> := <<"bla">>, 124 | <<"in">> := <<"body">>, 125 | <<"name">> := <<"Request Body">>, 126 | <<"required">> := true, 127 | <<"schema">> := #{<<"type">> := <<"string">>}}], 128 | <<"responses">> := #{<<"200">> := #{<<"description">> := <<"bla">>}}}}}} = 129 | Result, 130 | #{<<"paths">> := Paths} = Result, 131 | 3 = maps:size(Paths), 132 | {comment, ""}. 133 | 134 | -spec add_definition_test(Config :: cowboy_swagger_test_utils:config()) -> 135 | {comment, string()}. 136 | add_definition_test(_Config) -> 137 | set_swagger_version(swagger_2_0), 138 | perform_add_definition_test(), 139 | perform_add_completed_definition_test(), 140 | 141 | set_swagger_version(openapi_3_0_0), 142 | perform_add_definition_test(), 143 | perform_add_completed_definition_test(), 144 | 145 | {comment, ""}. 146 | 147 | -spec add_definition_array_test(Config :: cowboy_swagger_test_utils:config()) -> 148 | {comment, string()}. 149 | add_definition_array_test(_Config) -> 150 | set_swagger_version(swagger_2_0), 151 | perform_add_definition_array_test(), 152 | 153 | set_swagger_version(openapi_3_0_0), 154 | perform_add_definition_array_test(), 155 | 156 | {comment, ""}. 157 | 158 | -spec schema_test(Config :: cowboy_swagger_test_utils:config()) -> {comment, string()}. 159 | schema_test(_Config) -> 160 | set_swagger_version(swagger_2_0), 161 | #{<<"$ref">> := <<"#/definitions/Pet">>} = cowboy_swagger:schema(<<"Pet">>), 162 | 163 | set_swagger_version(openapi_3_0_0), 164 | #{<<"$ref">> := <<"#/components/schemas/Pet">>} = cowboy_swagger:schema(<<"Pet">>), 165 | 166 | {comment, ""}. 167 | 168 | -spec parameters_ref_test(Config :: cowboy_swagger_test_utils:config()) -> 169 | {comment, string()}. 170 | parameters_ref_test(_Config) -> 171 | set_swagger_version(openapi_3_0_0), 172 | cowboy_swagger:add_definition(#{<<"page">> => 173 | #{description => <<"results per page (max 100)">>, 174 | example => 1, 175 | in => query, 176 | name => per_page, 177 | schema => 178 | #{example => 1, 179 | maximum => 100, 180 | minimum => 1, 181 | type => integer}}}), 182 | {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), 183 | JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, parameters), 184 | true = maps:is_key(<<"page">>, JsonDefinitions), 185 | {comment, ""}. 186 | 187 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 188 | %% Internal functions 189 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 190 | 191 | perform_add_completed_definition_test() -> 192 | %% 193 | %% Given 194 | %% 195 | ct:comment("Add first definition"), 196 | Name1 = <<"CostumerDefinition">>, 197 | Properties1 = test_properties_one(), 198 | Definition1 = #{Name1 => #{type => <<"object">>, properties => Properties1}}, 199 | 200 | ct:comment("Add second definition"), 201 | Name2 = <<"CarDefinition">>, 202 | Properties2 = test_properties_two(), 203 | Definition2 = #{Name1 => #{type => <<"object">>, properties => Properties2}}, 204 | %% 205 | %% When 206 | %% 207 | ok = cowboy_swagger:add_definition(Definition1), 208 | ok = cowboy_swagger:add_definition(Definition2), 209 | 210 | %% 211 | %% Then 212 | %% 213 | {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), 214 | JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), 215 | true = maps:is_key(Name1, JsonDefinitions), 216 | true = maps:is_key(Name2, JsonDefinitions), 217 | ok. 218 | 219 | %% @private 220 | perform_add_definition_test() -> 221 | %% 222 | %% Given 223 | %% 224 | ct:comment("Add first definition"), 225 | Name1 = <<"CostumerDefinition">>, 226 | Properties1 = test_properties_one(), 227 | 228 | ct:comment("Add second definition"), 229 | Name2 = <<"CarDefinition">>, 230 | Properties2 = test_properties_two(), 231 | 232 | %% 233 | %% When 234 | %% 235 | ok = cowboy_swagger:add_definition(Name1, Properties1), 236 | ok = cowboy_swagger:add_definition(Name2, Properties2), 237 | 238 | %% 239 | %% Then 240 | %% 241 | {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), 242 | JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), 243 | true = maps:is_key(Name1, JsonDefinitions), 244 | true = maps:is_key(Name2, JsonDefinitions), 245 | ok. 246 | 247 | %% @private 248 | perform_add_definition_array_test() -> 249 | %% 250 | %% Given 251 | %% 252 | ct:comment("Add first definition"), 253 | Name1 = <<"CostumerDefinition">>, 254 | Properties1 = test_properties_one(), 255 | 256 | ct:comment("Add second definition"), 257 | Name2 = <<"CarDefinition">>, 258 | Properties2 = test_properties_two(), 259 | 260 | %% 261 | %% When 262 | %% 263 | ok = cowboy_swagger:add_definition_array(Name1, Properties1), 264 | ok = cowboy_swagger:add_definition_array(Name2, Properties2), 265 | 266 | %% 267 | %% Then 268 | %% 269 | {ok, SwaggerSpec1} = application:get_env(cowboy_swagger, global_spec), 270 | JsonDefinitions = cowboy_swagger:get_existing_definitions(SwaggerSpec1, schemas), 271 | true = maps:is_key(<<"items">>, maps:get(Name1, JsonDefinitions)), 272 | true = maps:is_key(<<"items">>, maps:get(Name2, JsonDefinitions)), 273 | <<"array">> = maps:get(<<"type">>, maps:get(Name1, JsonDefinitions)), 274 | <<"array">> = maps:get(<<"type">>, maps:get(Name2, JsonDefinitions)), 275 | ok. 276 | 277 | %% @private 278 | test_trails() -> 279 | Metadata = 280 | #{get => 281 | #{description => <<"bla bla bla">>, 282 | parameters => 283 | [#{name => "b", 284 | in => "path", 285 | description => "bla", 286 | schema => #{type => string}, 287 | required => false}, 288 | #{name => "c", 289 | in => "path", 290 | description => "bla", 291 | schema => #{type => string, example => <<"c">>}, 292 | required => false}], 293 | responses => 294 | #{<<"200">> => 295 | #{description => <<"200 OK">>, 296 | content => 297 | #{'application/json' => 298 | #{schema => #{type => string, title => <<"bla">>}}}}}}, 299 | delete => 300 | #{description => <<"bla bla bla">>, 301 | parameters => 302 | [#{name => <<"b">>, 303 | in => <<"path">>, 304 | description => <<"bla">>, 305 | required => false, 306 | schema => #{type => string}}]}, 307 | post => 308 | #{description => <<"bla bla bla">>, 309 | parameters => 310 | [#{name => <<"Request Body">>, 311 | in => <<"body">>, 312 | description => <<"bla">>, 313 | required => true, 314 | schema => #{type => string}}], 315 | responses => #{<<"200">> => #{description => "bla"}}}}, 316 | Metadata1 = 317 | #{get => 318 | #{description => <<"bla bla bla">>, 319 | responses => 320 | #{<<"200">> => 321 | #{description => <<"200 OK">>, 322 | content => 323 | #{'application/json' => 324 | #{schema => #{type => string, title => <<"bla">>}}}}}}}, 325 | [trails:trail("/a/[:b/[:c/[:d]]]", handler1, [], Metadata), 326 | trails:trail("/a/:b/[:c]", handler2, [], Metadata), 327 | trails:trail("/a", handler3, [], Metadata1) 328 | | cowboy_swagger_handler:trails()]. 329 | 330 | %% @private 331 | test_properties_one() -> 332 | #{<<"first_name">> => 333 | #{type => <<"string">>, 334 | description => <<"User first name">>, 335 | example => <<"Pepito">>}, 336 | <<"last_name">> => 337 | #{type => <<"string">>, 338 | description => <<"User last name">>, 339 | example => <<"Perez">>}}. 340 | 341 | %% @private 342 | test_properties_two() -> 343 | #{<<"brand">> => #{type => <<"string">>, description => <<"Car brand">>}, 344 | <<"year">> => 345 | #{type => <<"string">>, 346 | description => <<"Production time">>, 347 | example => <<"1995">>}}. 348 | 349 | %% @private 350 | set_swagger_version(swagger_2_0) -> 351 | Spec0 = maps:remove(<<"openapi">>, cowboy_swagger:get_global_spec()), 352 | cowboy_swagger:set_global_spec(Spec0#{swagger => "2.0"}); 353 | set_swagger_version(openapi_3_0_0) -> 354 | Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), 355 | cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0"}). 356 | 357 | set_openapi_url(Url) -> 358 | Spec0 = maps:remove(<<"swagger">>, cowboy_swagger:get_global_spec()), 359 | cowboy_swagger:set_global_spec(Spec0#{openapi => "3.0.0", servers => [#{url => Url}]}). 360 | -------------------------------------------------------------------------------- /test/cowboy_swagger_handler_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(cowboy_swagger_handler_SUITE). 2 | 3 | %% CT 4 | -export([all/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2, 5 | end_per_testcase/2]). 6 | %% Test cases 7 | -export([handler_test/1, multiple_hosts_test/1]). 8 | 9 | -hank([unnecessary_function_arguments]). 10 | 11 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 12 | %% Common test 13 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 14 | 15 | -spec all() -> [atom()]. 16 | all() -> 17 | cowboy_swagger_test_utils:all(?MODULE). 18 | 19 | -spec init_per_suite(cowboy_swagger_test_utils:config()) -> 20 | cowboy_swagger_test_utils:config(). 21 | init_per_suite(Config) -> 22 | {ok, _} = shotgun:start(), 23 | Config. 24 | 25 | -spec end_per_suite(cowboy_swagger_test_utils:config()) -> 26 | cowboy_swagger_test_utils:config(). 27 | end_per_suite(Config) -> 28 | _ = shotgun:stop(), 29 | Config. 30 | 31 | -spec init_per_testcase(TestCase :: atom(), 32 | Config :: cowboy_swagger_test_utils:config()) -> 33 | cowboy_swagger_test_utils:config(). 34 | init_per_testcase(handler_test, Config) -> 35 | {ok, _} = example:start(), 36 | Config; 37 | init_per_testcase(multiple_hosts_test, Config) -> 38 | {ok, _} = multiple_hosts_servers_example:start(), 39 | Config. 40 | 41 | -spec end_per_testcase(TestCase :: atom(), 42 | Config :: cowboy_swagger_test_utils:config()) -> 43 | cowboy_swagger_test_utils:config(). 44 | end_per_testcase(handler_test, Config) -> 45 | _ = example:stop(), 46 | ok = cleanup(), 47 | Config; 48 | end_per_testcase(multiple_hosts_test, Config) -> 49 | _ = multiple_hosts_servers_example:stop(), 50 | ok = cleanup(), 51 | Config. 52 | 53 | %% @private 54 | -spec cleanup() -> ok. 55 | cleanup() -> 56 | _ = application:stop(cowboy_swagger), 57 | _ = application:stop(trails), 58 | ok. 59 | 60 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | %% Test Cases 62 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 63 | 64 | -spec handler_test(cowboy_swagger_test_utils:config()) -> {atom(), string()}. 65 | handler_test(_Config) -> 66 | %% Expected result 67 | Trails = 68 | trails:trails([example_echo_handler, 69 | example_description_handler, 70 | cowboy_swagger_handler]), 71 | SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), 72 | ExpectedPaths = 73 | cowboy_swagger:dec_json( 74 | cowboy_swagger:enc_json( 75 | cowboy_swagger:swagger_paths(SanitizeTrails))), 76 | 77 | %% GET swagger.json spec 78 | ct:comment("GET /api-docs/swagger.json should return 200 OK"), 79 | #{status_code := 200, body := Body0} = 80 | cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json"), 81 | #{<<"openapi">> := <<"3.0.0">>, 82 | <<"info">> := #{<<"title">> := <<"Example API">>}, 83 | <<"paths">> := ExpectedPaths} = 84 | cowboy_swagger:dec_json(Body0), 85 | 86 | %% GET index.html 87 | ct:comment("GET /api-docs should return 301 MOVED PERMANENTLY to " 88 | ++ "/api-docs/index.html"), 89 | #{status_code := 301, headers := Headers} = 90 | cowboy_swagger_test_utils:api_call(get, "/api-docs"), 91 | Location = {<<"location">>, <<"/api-docs/index.html">>}, 92 | Location = lists:keyfind(<<"location">>, 1, Headers), 93 | 94 | %% GET swagger-ui.js - test /api-docs/[...] trail 95 | ct:comment("GET /api-docs/swagger-ui-js should return 200 OK"), 96 | #{status_code := 200, body := SwaggerUIBody} = 97 | cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger-ui.js"), 98 | {ok, SwaggerUIBodySrc} = file:read_file("../../../../priv/swagger/swagger-ui.js"), 99 | SwaggerUIBody = SwaggerUIBodySrc, 100 | 101 | %% GET unknown-file.ext - test /api-docs/[...] trail 102 | ct:comment("GET /api-docs/unknown-file.ext should return 404 NOT FOUND"), 103 | #{status_code := 404} = 104 | cowboy_swagger_test_utils:api_call(get, "/api-docs/unknown-file.ext"), 105 | {comment, ""}. 106 | 107 | -spec multiple_hosts_test(_Config :: cowboy_swagger_test_utils:config()) -> 108 | {atom(), string()}. 109 | multiple_hosts_test(_Config) -> 110 | %% api1 - host1 111 | Trails11 = trails:trails(example_echo_handler), 112 | ExpectedPaths11 = get_expected_paths(Trails11), 113 | %% GET swagger.json spec from localhost:8383 114 | ct:comment("GET /api-docs/swagger.json should return 200 OK"), 115 | #{status_code := 200, body := Body11} = 116 | cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "localhost", 8383), 117 | #{<<"openapi">> := <<"3.0.0">>, 118 | <<"info">> := #{<<"title">> := <<"Example API">>}, 119 | <<"paths">> := ExpectedPaths11} = 120 | cowboy_swagger:dec_json(Body11), 121 | %% api1 - host2 122 | Trails12 = trails:trails(host1_handler), 123 | ExpectedPaths12 = get_expected_paths(Trails12), 124 | %% GET swagger.json spec from 127.0.0.1:8383 125 | ct:comment("GET /api-docs/swagger.json should return 200 OK"), 126 | #{status_code := 200, body := Body12} = 127 | cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "127.0.0.1", 8383), 128 | #{<<"openapi">> := <<"3.0.0">>, 129 | <<"info">> := #{<<"title">> := <<"Example API">>}, 130 | <<"paths">> := ExpectedPaths12} = 131 | cowboy_swagger:dec_json(Body12), 132 | %% api2 - host1 133 | Trails21 = trails:trails([host1_handler, example_echo_handler]), 134 | ExpectedPaths21 = get_expected_paths(Trails21), 135 | %% GET swagger.json spec from localhost:8282 136 | ct:comment("GET /api-docs/swagger.json should return 200 OK"), 137 | #{status_code := 200, body := Body21} = 138 | cowboy_swagger_test_utils:api_call(get, "/api-docs/swagger.json", "localhost", 8282), 139 | #{<<"openapi">> := <<"3.0.0">>, 140 | <<"info">> := #{<<"title">> := <<"Example API">>}, 141 | <<"paths">> := ExpectedPaths21} = 142 | cowboy_swagger:dec_json(Body21), 143 | {comment, ""}. 144 | 145 | %% @private 146 | -spec get_expected_paths(Trails :: trails:trails()) -> jsx:json_term(). 147 | get_expected_paths(Trails) -> 148 | SanitizeTrails = cowboy_swagger:filter_cowboy_swagger_handler(Trails), 149 | cowboy_swagger:dec_json( 150 | cowboy_swagger:enc_json( 151 | cowboy_swagger:swagger_paths(SanitizeTrails))). 152 | -------------------------------------------------------------------------------- /test/cowboy_swagger_test_utils.erl: -------------------------------------------------------------------------------- 1 | -module(cowboy_swagger_test_utils). 2 | 3 | -export([all/1, init_per_suite/1, end_per_suite/1]). 4 | -export([api_call/2, api_call/4]). 5 | 6 | -type response() :: 7 | #{status_code => integer(), 8 | headers => [tuple()], 9 | body => binary()}. 10 | -type config() :: proplists:proplist(). 11 | -type shotgun_http_verb() :: delete | get | head | options | patch | post | put. 12 | 13 | -export_type([shotgun_http_verb/0]). 14 | -export_type([config/0, response/0]). 15 | 16 | -spec all(atom()) -> [atom()]. 17 | all(Module) -> 18 | ExcludedFuns = [module_info, init_per_suite, end_per_suite, group, all], 19 | Exports = apply(Module, module_info, [exports]), 20 | [F || {F, 1} <- Exports, not lists:member(F, ExcludedFuns)]. 21 | 22 | -spec init_per_suite(config()) -> config(). 23 | init_per_suite(Config) -> 24 | Config. 25 | 26 | -spec end_per_suite(config()) -> config(). 27 | end_per_suite(Config) -> 28 | Config. 29 | 30 | -spec api_call(shotgun_http_verb(), [byte()]) -> response(). 31 | api_call(Method, Uri) -> 32 | api_call(Method, Uri, "localhost", 8080). 33 | 34 | -spec api_call(shotgun_http_verb(), string(), string(), integer()) -> response(). 35 | api_call(Method, Uri, HostMatch, Port) -> 36 | {ok, Pid} = shotgun:open(HostMatch, Port), 37 | try 38 | Headers = #{}, 39 | Body = [], 40 | {ok, Response} = shotgun:request(Pid, Method, Uri, Headers, Body, #{}), 41 | Response 42 | after 43 | shotgun:close(Pid) 44 | end. 45 | -------------------------------------------------------------------------------- /test/example.app: -------------------------------------------------------------------------------- 1 | {application, 2 | example, 3 | [{description, "Cowboy Swagger Basic Example."}, 4 | {vsn, "0.1"}, 5 | {applications, [kernel, stdlib, cowboy, trails, cowboy_swagger]}, 6 | {modules, []}, 7 | {mod, {example, []}}, 8 | {start_phases, [{start_trails_http, []}]}]}. 9 | -------------------------------------------------------------------------------- /test/example.erl: -------------------------------------------------------------------------------- 1 | -module(example). 2 | 3 | -export([start/0]). 4 | -export([start/2]). 5 | -export([stop/0]). 6 | -export([stop/1]). 7 | -export([start_phase/3]). 8 | 9 | -hank([unnecessary_function_arguments]). 10 | 11 | %% application 12 | %% @doc Starts the application 13 | -spec start() -> {ok, [atom()]}. 14 | start() -> 15 | application:ensure_all_started(example). 16 | 17 | %% @doc Stops the application 18 | -spec stop() -> ok. 19 | stop() -> 20 | application:stop(example). 21 | 22 | %% behaviour 23 | %% @private 24 | -spec start(normal, [any()]) -> {ok, pid()}. 25 | start(_StartType, _StartArgs) -> 26 | example_sup:start_link(). 27 | 28 | %% @private 29 | -spec stop(_) -> ok. 30 | stop(_State) -> 31 | ok = cowboy:stop_listener(example_http). 32 | 33 | -spec start_phase(atom(), application:start_type(), []) -> ok. 34 | start_phase(start_trails_http, _StartType, []) -> 35 | {ok, Port} = application:get_env(example, http_port), 36 | Trails = 37 | trails:trails([example_echo_handler, 38 | example_description_handler, 39 | cowboy_swagger_handler]), 40 | trails:store(Trails), 41 | 42 | Dispatch = trails:single_host_compile(Trails), 43 | RanchOptions = [{port, Port}], 44 | CowboyOptions = 45 | #{env => #{dispatch => Dispatch}, 46 | compress => true, 47 | timeout => 12000}, 48 | 49 | {ok, _} = cowboy:start_clear(example_http, RanchOptions, CowboyOptions), 50 | ok. 51 | -------------------------------------------------------------------------------- /test/example_default.erl: -------------------------------------------------------------------------------- 1 | -module(example_default). 2 | 3 | -export([init/3, rest_init/2, content_types_accepted/2, content_types_provided/2, 4 | forbidden/2, resource_exists/2]). 5 | 6 | -hank([unnecessary_function_arguments]). 7 | 8 | %% cowboy 9 | -spec init(any(), any(), any()) -> {upgrade, protocol, cowboy_rest}. 10 | init(_Transport, _Req, _Opts) -> 11 | {upgrade, protocol, cowboy_rest}. 12 | 13 | -spec rest_init(Req, any()) -> {ok, Req, #{}}. 14 | rest_init(Req, _Opts) -> 15 | {ok, Req, #{}}. 16 | 17 | -spec content_types_accepted(Req, State) -> {[{<<_:80>>, handle_put}, ...], Req, State}. 18 | content_types_accepted(Req, State) -> 19 | {[{<<"text/plain">>, handle_put}], Req, State}. 20 | 21 | -spec content_types_provided(Req, State) -> {[{<<_:80>>, handle_get}, ...], Req, State}. 22 | content_types_provided(Req, State) -> 23 | {[{<<"text/plain">>, handle_get}], Req, State}. 24 | 25 | -spec forbidden(Req, State) -> {false, Req, State}. 26 | forbidden(Req, State) -> 27 | {false, Req, State}. 28 | 29 | -spec resource_exists(Req, State) -> {true, Req, State}. 30 | resource_exists(Req, State) -> 31 | {true, Req, State}. 32 | -------------------------------------------------------------------------------- /test/example_description_handler.erl: -------------------------------------------------------------------------------- 1 | -module(example_description_handler). 2 | 3 | -include_lib("mixer/include/mixer.hrl"). 4 | 5 | -mixin([{example_default, 6 | [init/3, 7 | rest_init/2, 8 | content_types_accepted/2, 9 | content_types_provided/2, 10 | resource_exists/2]}]). 11 | 12 | -export([allowed_methods/2, handle_get/2]). 13 | 14 | %trails 15 | -behaviour(trails_handler). 16 | 17 | -export([trails/0]). 18 | 19 | -spec trails() -> [trails:trail()]. 20 | trails() -> 21 | Metadata = 22 | #{get => 23 | #{tags => ["example"], 24 | description => "Retrives trails's server description", 25 | responses => 26 | #{<<"200">> => 27 | #{description => <<"Retrives trails's server description 200 OK">>, 28 | content => #{'text/plain' => #{schema => #{type => string}}}}}}}, 29 | [trails:trail("/description", example_description_handler, [], Metadata)]. 30 | 31 | %% cowboy 32 | -spec allowed_methods(Req, State) -> {[<<_:24>>, ...], Req, State}. 33 | allowed_methods(Req, State) -> 34 | {[<<"GET">>], Req, State}. 35 | 36 | %% internal 37 | -spec handle_get(Req, State) -> {[trails:trail()], Req, State}. 38 | handle_get(Req, State) -> 39 | Body = trails:all(), 40 | {io_lib:format("~p~n", [Body]), Req, State}. 41 | -------------------------------------------------------------------------------- /test/example_echo_handler.erl: -------------------------------------------------------------------------------- 1 | -module(example_echo_handler). 2 | 3 | -include_lib("mixer/include/mixer.hrl"). 4 | 5 | -mixin([{example_default, 6 | [init/3, 7 | rest_init/2, 8 | content_types_accepted/2, 9 | content_types_provided/2, 10 | resource_exists/2]}]). 11 | 12 | -export([allowed_methods/2, handle_put/2, handle_get/2]). 13 | 14 | %trails 15 | -behaviour(trails_handler). 16 | 17 | -export([trails/0]). 18 | 19 | -spec trails() -> [trails:trail()]. 20 | trails() -> 21 | Metadata = 22 | #{get => 23 | #{tags => ["echo"], 24 | description => "Gets echo var from the server", 25 | responses => 26 | #{<<"200">> => 27 | #{description => <<"Gets echo var from the server 200 OK">>, 28 | content => #{'text/plain' => #{schema => #{type => string}}}}}}, 29 | put => 30 | #{tags => ["echo"], 31 | description => "Sets echo var in the server", 32 | parameters => 33 | [#{name => <<"echo">>, 34 | description => <<"Echo message">>, 35 | in => <<"path">>, 36 | required => false, 37 | schema => #{type => string, example => <<"Hello, World!">>}}], 38 | responses => 39 | #{<<"200">> => 40 | #{description => <<"Gets echo var from the server 200 OK">>, 41 | content => #{'text/plain' => #{schema => #{type => string}}}}}}}, 42 | [trails:trail("/message/[:echo]", example_echo_handler, [], Metadata)]. 43 | 44 | %% cowboy 45 | -spec allowed_methods(Req, State) -> {[<<_:24, _:_*8>>, ...], Req, State}. 46 | allowed_methods(Req, State) -> 47 | {[<<"GET">>, <<"PUT">>, <<"HEAD">>], Req, State}. 48 | 49 | %% internal 50 | -spec handle_get(Req, State) -> {[binary(), ...], Req, State}. 51 | handle_get(Req, State) -> 52 | Echo = application:get_env(example, echo, ""), 53 | Body = [<<"You Get an echo!">>, Echo], 54 | {Body, Req, State}. 55 | 56 | -spec handle_put(cowboy_req:req(), State) -> {true, cowboy_req:req(), State}. 57 | handle_put(Req, State) -> 58 | {Echo, Req1} = cowboy_req:binding(echo, Req, ""), 59 | application:set_env(example, echo, Echo), 60 | Body = [<<"You put an echo! ">>, Echo], 61 | Req2 = cowboy_req:set_resp_body(Body, Req1), 62 | {true, Req2, State}. 63 | -------------------------------------------------------------------------------- /test/example_sup.erl: -------------------------------------------------------------------------------- 1 | -module(example_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0]). 6 | -export([init/1]). 7 | 8 | %% admin api 9 | -spec start_link() -> {ok, pid()}. 10 | start_link() -> 11 | supervisor:start_link({local, ?MODULE}, ?MODULE, {}). 12 | 13 | %% behaviour callbacks 14 | -spec init({}) -> {ok, {{one_for_one, 5, 10}, []}}. 15 | init({}) -> 16 | {ok, {{one_for_one, 5, 10}, []}}. 17 | -------------------------------------------------------------------------------- /test/host1_handler.erl: -------------------------------------------------------------------------------- 1 | -module(host1_handler). 2 | 3 | -include_lib("mixer/include/mixer.hrl"). 4 | 5 | -mixin([{example_default, 6 | [init/3, 7 | rest_init/2, 8 | content_types_accepted/2, 9 | content_types_provided/2, 10 | resource_exists/2]}]). 11 | 12 | -export([allowed_methods/2, handle_get/2]). 13 | 14 | %trails 15 | -behaviour(trails_handler). 16 | 17 | -export([trails/0]). 18 | 19 | -spec trails() -> [trails:trail()]. 20 | trails() -> 21 | Metadata = 22 | #{get => 23 | #{tags => ["whoami"], 24 | description => "Get hostname", 25 | responses => 26 | #{<<"200">> => 27 | #{description => <<"Get hostname 200 OK">>, 28 | content => #{'text/plain' => #{schema => #{type => string}}}}}}}, 29 | [trails:trail("/whoami", host1_handler, #{}, Metadata)]. 30 | 31 | %% cowboy 32 | -spec allowed_methods(Req, State) -> {[<<_:24>>, ...], Req, State}. 33 | allowed_methods(Req, State) -> 34 | {[<<"GET">>], Req, State}. 35 | 36 | %% internal 37 | -spec handle_get(cowboy_req:req(), State) -> {<<_:40, _:_*8>>, cowboy_req:req(), State}. 38 | handle_get(Req, State) -> 39 | Host = cowboy_req:host(Req), 40 | Body = <<"I am ", Host/binary>>, 41 | {Body, Req, State}. 42 | -------------------------------------------------------------------------------- /test/multiple_hosts_servers_example.app: -------------------------------------------------------------------------------- 1 | {application, 2 | multiple_hosts_servers_example, 3 | [{description, "Cowboy Swagger Complex Example."}, 4 | {vsn, "0.1"}, 5 | {applications, [kernel, stdlib, sasl, cowboy, trails, cowboy_swagger]}, 6 | {modules, []}, 7 | {mod, {multiple_hosts_servers_example, []}}, 8 | {start_phases, [{start_multiple_hosts_servers_example_http, []}]}]}. 9 | -------------------------------------------------------------------------------- /test/multiple_hosts_servers_example.erl: -------------------------------------------------------------------------------- 1 | -module(multiple_hosts_servers_example). 2 | 3 | -export([start/0]). 4 | -export([start/2]). 5 | -export([stop/0]). 6 | -export([stop/1]). 7 | -export([start_phase/3]). 8 | 9 | -hank([unnecessary_function_arguments]). 10 | 11 | %% application 12 | %% @doc Starts the application 13 | -spec start() -> {ok, [atom()]}. 14 | start() -> 15 | application:ensure_all_started(multiple_hosts_servers_example). 16 | 17 | %% @doc Stops the application 18 | -spec stop() -> ok. 19 | stop() -> 20 | application:stop(multiple_hosts_servers_example). 21 | 22 | %% behaviour 23 | %% @private 24 | -spec start(normal, [any()]) -> {ok, pid()}. 25 | start(_StartType, _StartArgs) -> 26 | _ = application:stop(lager), 27 | ok = application:stop(sasl), 28 | {ok, _} = application:ensure_all_started(sasl), 29 | {ok, self()}. 30 | 31 | %% @private 32 | -spec stop(_) -> ok. 33 | stop(_State) -> 34 | ok = cowboy:stop_listener(multiple_hosts_servers_http). 35 | 36 | -spec start_phase(atom(), application:start_type(), []) -> ok. 37 | start_phase(start_multiple_hosts_servers_example_http, _StartType, []) -> 38 | %% Host1 39 | {ok, #{hosts := [HostMatch11, HostMatch12], port := Port1}} = 40 | application:get_env(multiple_hosts_servers_example, api1), 41 | {ok, #{hosts := ['_'], port := Port2}} = 42 | application:get_env(multiple_hosts_servers_example, api2), 43 | 44 | Trails11 = 45 | trails:trails(example_echo_handler) 46 | ++ cowboy_swagger_handler:trails(#{server => api1, host => HostMatch11}), 47 | Trails12 = 48 | trails:trails(host1_handler) 49 | ++ cowboy_swagger_handler:trails(#{server => api1, host => HostMatch12}), 50 | Routes1 = [{HostMatch11, Trails11}, {HostMatch12, Trails12}], 51 | 52 | trails:store(api1, Routes1), 53 | Dispatch1 = trails:compile(Routes1), 54 | {ok, _} = start_cowboy(api1, Dispatch1, Port1), 55 | 56 | Trails21 = 57 | trails:trails([host1_handler, example_echo_handler]) 58 | ++ cowboy_swagger_handler:trails(#{server => api2}), 59 | 60 | trails:store(api2, Trails21), 61 | Dispatch2 = trails:single_host_compile(Trails21), 62 | {ok, _} = start_cowboy(api2, Dispatch2, Port2), 63 | ok. 64 | 65 | %% @private 66 | start_cowboy(Server, Dispatch, Port) -> 67 | RanchOptions = [{port, Port}], 68 | CowboyOptions = 69 | #{env => #{dispatch => Dispatch}, 70 | compress => true, 71 | timeout => 12000}, 72 | cowboy:start_clear(Server, RanchOptions, CowboyOptions). 73 | -------------------------------------------------------------------------------- /test/test.config: -------------------------------------------------------------------------------- 1 | [{example, [{http_port, 8080}]}, 2 | {multiple_hosts_servers_example, 3 | [{api1, #{hosts => ["localhost", "127.0.0.1"], port => 8383}}, 4 | {api2, #{hosts => ['_'], port => 8282}}]}, 5 | {cowboy_swagger, 6 | [{global_spec, #{openapi => "3.0.0", info => #{title => "Example API"}}}]}]. 7 | --------------------------------------------------------------------------------