├── .gitignore
├── LICENSE
├── README.md
├── config
└── config.exs
├── doc
├── Mix.Tasks.Swagger.html
├── README.html
├── SwaggerDoc.html
├── dist
│ ├── app.css
│ ├── app.js
│ └── sidebar_items.js
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── index.html
└── overview.html
├── examples
└── hello_user
│ ├── .gitignore
│ ├── README.md
│ ├── brunch-config.js
│ ├── config
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ └── test.exs
│ ├── lib
│ ├── hello_user.ex
│ └── hello_user
│ │ ├── endpoint.ex
│ │ └── repo.ex
│ ├── mix.exs
│ ├── mix.lock
│ ├── package.json
│ ├── priv
│ └── repo
│ │ ├── migrations
│ │ └── 20150904154139_create_user.exs
│ │ └── seeds.exs
│ ├── test
│ ├── controllers
│ │ ├── page_controller_test.exs
│ │ └── user_controller_test.exs
│ ├── models
│ │ └── user_test.exs
│ ├── support
│ │ ├── channel_case.ex
│ │ ├── conn_case.ex
│ │ └── model_case.ex
│ ├── test_helper.exs
│ └── views
│ │ ├── error_view_test.exs
│ │ ├── layout_view_test.exs
│ │ └── page_view_test.exs
│ └── web
│ ├── channels
│ └── user_socket.ex
│ ├── controllers
│ ├── controller_helper.ex
│ ├── page_controller.ex
│ └── user_controller.ex
│ ├── models
│ └── user.ex
│ ├── router.ex
│ ├── static
│ ├── assets
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ └── phoenix.png
│ │ └── robots.txt
│ ├── css
│ │ └── app.css
│ └── js
│ │ ├── app.js
│ │ └── socket.js
│ ├── templates
│ ├── layout
│ │ └── app.html.eex
│ └── page
│ │ └── index.html.eex
│ ├── views
│ ├── error_view.ex
│ ├── layout_view.ex
│ ├── page_view.ex
│ └── user_view.ex
│ └── web.ex
├── lib
├── mix
│ └── tasks
│ │ └── swagger.ex
└── swaggerdoc.ex
├── mix.exs
├── mix.lock
└── test
├── mix
└── tasks
│ └── swagger_test.exs
├── swaggerdoc_test.exs
└── test_helper.exs
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /deps
3 | erl_crash.dump
4 | *.ez
5 | .DS_Store
6 | swagger/
7 | /examples/hello_user/_build
8 | /examples/hello_user/deps
9 | /examples/hello_user/node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | © 2015 Lexmark International Technology S.A. All rights reserved.
2 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 |
4 | Mozilla Public License
5 | Version 2.0
6 |
7 | 1. Definitions
8 |
9 | 1.1. “Contributor”
10 | means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.
11 |
12 | 1.2. “Contributor Version”
13 | means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.
14 |
15 | 1.3. “Contribution”
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. “Covered Software”
19 | means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.
20 |
21 | 1.5. “Incompatible With Secondary Licenses”
22 | means
23 |
24 | that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or
25 |
26 | that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.
27 |
28 | 1.6. “Executable Form”
29 | means any form of the work other than Source Code Form.
30 |
31 | 1.7. “Larger Work”
32 | means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.
33 |
34 | 1.8. “License”
35 | means this document.
36 |
37 | 1.9. “Licensable”
38 | means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.
39 |
40 | 1.10. “Modifications”
41 | means any of the following:
42 |
43 | any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or
44 |
45 | any new file in Source Code Form that contains any Covered Software.
46 |
47 | 1.11. “Patent Claims” of a Contributor
48 | means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.
49 |
50 | 1.12. “Secondary License”
51 | means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.
52 |
53 | 1.13. “Source Code Form”
54 | means the form of the work preferred for making modifications.
55 |
56 | 1.14. “You” (or “Your”)
57 | means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
58 |
59 | 2. License Grants and Conditions
60 |
61 | 2.1. Grants
62 |
63 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
64 |
65 | under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and
66 |
67 | under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.
68 |
69 | 2.2. Effective Date
70 |
71 | The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.
72 |
73 | 2.3. Limitations on Grant Scope
74 |
75 | The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:
76 |
77 | for any code that a Contributor has removed from Covered Software; or
78 |
79 | for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or
80 |
81 | under Patent Claims infringed by Covered Software in the absence of its Contributions.
82 |
83 | This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).
84 |
85 | 2.4. Subsequent Licenses
86 |
87 | No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).
88 |
89 | 2.5. Representation
90 |
91 | Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.
92 |
93 | 2.6. Fair Use
94 |
95 | This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.
96 |
97 | 2.7. Conditions
98 |
99 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.
100 |
101 | 3. Responsibilities
102 |
103 | 3.1. Distribution of Source Form
104 |
105 | All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.
106 |
107 | 3.2. Distribution of Executable Form
108 |
109 | If You distribute Covered Software in Executable Form then:
110 |
111 | such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and
112 |
113 | You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.
114 |
115 | 3.3. Distribution of a Larger Work
116 |
117 | You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).
118 |
119 | 3.4. Notices
120 |
121 | You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.
122 |
123 | 3.5. Application of Additional Terms
124 |
125 | You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.
126 |
127 | 4. Inability to Comply Due to Statute or Regulation
128 |
129 | If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.
130 |
131 | 5. Termination
132 |
133 | 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.
134 |
135 | 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
136 |
137 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.
138 |
139 | 6. Disclaimer of Warranty
140 |
141 | Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.
142 |
143 | 7. Limitation of Liability
144 |
145 | Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
146 |
147 | 8. Litigation
148 |
149 | Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.
150 |
151 | 9. Miscellaneous
152 |
153 | This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.
154 |
155 | 10. Versions of the License
156 |
157 | 10.1. New Versions
158 |
159 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.
160 |
161 | 10.2. Effect of New Versions
162 |
163 | You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.
164 |
165 | 10.3. Modified Versions
166 |
167 | If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).
168 |
169 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
170 |
171 | If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.
172 |
173 | Exhibit A - Source Code Form License Notice
174 |
175 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
176 |
177 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
178 |
179 | You may add additional accurate notices of copyright ownership.
180 |
181 | Exhibit B - “Incompatible With Secondary Licenses” Notice
182 |
183 | This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwaggerDoc
2 |
3 | The SwaggerDoc module provides a convenience task for generating [Swagger](http://swagger.io/) API documentation for Phoenix and Ecto-based projects. This task has been created for Phoenix and Ecto 1.0 and greater.
4 |
5 | [](https://semaphoreci.com/perceptive/swaggerdoc)
6 |
7 | ## Getting Started
8 |
9 | To use swaggerdoc with your projects, edit your mix.exs file and add it as a dependency:
10 |
11 | ```elixir
12 | defp deps do
13 | [{:swaggerdoc, "~> 0.0.1"}]
14 | end
15 | ```
16 |
17 | To execute the Mix task, simply type `mix swagger`:
18 |
19 | ```elixir
20 | hello_user$ mix swagger
21 | Generating Swagger documentation...
22 | Adding Ecto definitions...
23 | Adding Phoenix Routes...
24 | Writing JSON to file...
25 | Finished generating Swagger documentation!
26 | ```
27 |
28 | To view the generated Swagger in [swagger-ui](https://github.com/swagger-api/swagger-ui):
29 |
30 | * In a temp folder, execute a git clone of https://github.com/swagger-api/swagger-ui.git
31 | * In the browser of your choice, open the file *temp folder*/swagger-ui/dist/index.html
32 | * In the JSON API input box at the top of the page, paste in the link to the JSON
33 | * Hit the 'Explore' button
34 |
35 | For a complete example, please see the [examples](https://github.com/OpenAperture/swaggerdoc/tree/master/examples) section.
36 |
37 | ## Config
38 |
39 | SwaggerDoc's version and project information is configured in your config.exs files. The options are specified under the :swaggerdoc application.
40 |
41 | ```iex
42 | config :swaggerdoc,
43 | swagger_version: "2.0"
44 | ```
45 |
46 | ### Task Config
47 | The following task-specific options are available:
48 |
49 | * :output_path
50 | * Description: Specifies the file path where the JSON file will be created.
51 | * Type: string
52 | * Default Value: System.cwd!() <> "/swagger"
53 | * :output_file
54 | * Description: Specifies the file name (within the output_path) of the Swagger JSON
55 | * Type: string
56 | * Default Value: "api.json"
57 | * :pipe_through,
58 | * Description: if pipe_through is defined only this is used.
59 | * Type: list
60 | * Default Value: nil
61 | * Example: pipe_through: [:api]
62 |
63 | ### Swagger Config
64 | The following Swagger-specific options are available:
65 |
66 | * :swagger_version
67 | * Description: Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be "2.0".
68 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "swagger"
69 | * Type: string
70 | * Default Value: "2.0"
71 | * :host
72 | * Description: The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used (including the port). The host does not support path templating.
73 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "host"
74 | * Type: string
75 | * Default Value: ""
76 | * :base_path
77 | * Description: The base path on which the API is served, which is relative to the host. If it is not included, the API is served directly under the host. The value MUST start with a leading slash (/). The basePath does not support path templating.
78 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "basePath"
79 | * Type: string
80 | * Default Value: ""
81 | * :schemes
82 | * Description: The transfer protocol of the API. Values MUST be from the list: "http", "https", "ws", "wss". If the schemes is not included, the default scheme to be used is the one used to access the Swagger definition itself.
83 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "schemes"
84 | * Type: [string]
85 | * Default Value: ["http"]
86 | * :consumes
87 | * Description: A list of MIME types the APIs can consume. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types.
88 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "consumes"
89 | * Type: [string]
90 | * Default Value: []
91 | * :produces
92 | * Description: A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types.
93 | * Swagger Location: [Swagger Object](http://swagger.io/specification/#swaggerObject), "produces"
94 | * Type: [string]
95 | * Default Value: []
96 | * :project_version
97 | * Description: Provides the version of the application API (not to be confused with the specification version).
98 | * Swagger Location: [Info Object](http://swagger.io/specification/#infoObject), "version"
99 | * Type: string
100 | * Default Value: ""
101 | * :project_name
102 | * Description: The title of the application.
103 | * Swagger Location: [Info Object](http://swagger.io/specification/#infoObject), "title"
104 | * Type: string
105 | * Default Value: ""
106 | * :project_desc
107 | * Description: A short description of the application. GFM syntax can be used for rich text representation.
108 | * Swagger Location: [Info Object](http://swagger.io/specification/#infoObject), "description"
109 | * Type: string
110 | * Default Value: ""
111 | * :project_terms
112 | * Description: The Terms of Service for the API.
113 | * Swagger Location: [Info Object](http://swagger.io/specification/#infoObject), "termsOfService"
114 | * Type: string
115 | * Default Value: ""
116 | * :project_contact_name
117 | * Description: The identifying name of the contact person/organization.
118 | * Swagger Location: [Contact Object](http://swagger.io/specification/#contactObject), "name"
119 | * Type: string
120 | * Default Value: ""
121 | * :project_contact_email
122 | * Description: The email address of the contact person/organization. MUST be in the format of an email address.
123 | * Swagger Location: [Contact Object](http://swagger.io/specification/#contactObject), "email"
124 | * Type: string
125 | * Default Value: ""
126 | * :project_contact_url
127 | * Description: The URL pointing to the contact information. MUST be in the format of a URL.
128 | * Swagger Location: [Contact Object](http://swagger.io/specification/#contactObject), "url"
129 | * Type: string
130 | * Default Value: ""
131 | * :project_license_name
132 | * Description: The license name used for the API.
133 | * Swagger Location: [License Object](http://swagger.io/specification/#licenseObject), "url"
134 | * Type: string
135 | * Default Value: ""
136 | * :project_license_url
137 | * Description: A URL to the license used for the API. MUST be in the format of a URL.
138 | * Swagger Location: [License Object](http://swagger.io/specification/#licenseObject), "url"
139 | * Type: string
140 | * Default Value: ""
141 |
142 | Here's an example from the sample [HelloUser's config.exs](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/config/config.exs):
143 | ```iex
144 | config :swaggerdoc,
145 | swagger_version: "2.0",
146 | project_version: "1.0.0",
147 | project_name: "Hello User",
148 | project_desc: "The REST API for the Hello User",
149 | project_terms: "https://www.mozilla.org/en-US/MPL/2.0/",
150 | project_contact_name: "OpenAperture",
151 | project_contact_email: "openaperture@lexmark.com",
152 | project_contact_url: "http://openaperture.io",
153 | project_license_name: "Mozilla Public License, v. 2.0",
154 | project_license_url: "https://www.mozilla.org/en-US/MPL/2.0/",
155 | host: "openaperture.io",
156 | base_path: "/",
157 | schemes: ["https"],
158 | consumes: ["application/json"],
159 | produces: ["application/json"]
160 | ```
161 |
162 | ## Default Behavior
163 | The mix task is designed to scan for Ecto-specific Models and Phoenix-specific routes to attempt to generate an accurrate Swagger API object.
164 |
165 | ### Converting Ecto Models into Swagger Definitions
166 | Each [Ecto Model](https://github.com/elixir-lang/ecto/blob/v1.0.0/lib/ecto/model.ex) that is identified (prescence of the [__schema__ method](https://github.com/elixir-lang/ecto/blob/v1.0.0/lib/ecto/schema.ex#L229-L230)) is converted into a [Definitions Object](http://swagger.io/specification/#definitionsObject).
167 |
168 | The conversion uses schema fields and updates them into schemas unde the [Definitions Object](http://swagger.io/specification/#definitionsObject). The following values are used to convert an [Ecto schema type](https://github.com/elixir-lang/ecto/blob/v1.0.0/lib/ecto/schema.ex#L107-L145) into a [Swagger property type](http://swagger.io/specification/#dataTypeType):
169 |
170 | * :id -> %{"type" => "integer", "format" => "int64"}
171 | * :binary_id -> %{"type" => "string", "format" => "binary"}
172 | * :integer -> %{"type" => "integer", "format" => "int64"}
173 | * :float -> %{"type" => "number", "format" => "float"}
174 | * :boolean -> %{"type" => "boolean"}
175 | * :string -> %{"type" => "string"}
176 | * :binary -> %{"type" => "string", "format" => "binary"}
177 | * :Ecto.DateTime -> %{"type" => "string", "format" => "date-time"}
178 | * :Ecto.Date -> %{"type" => "string", "format" => "date"}
179 | * :Ecto.Time -> %{"type" => "string", "format" => "date-time"}
180 | * :uuid -> %{"type" => "string"}
181 | * _ -> %{"type" => "string"}
182 |
183 | Looking at the [HelloUser.User model](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/web/models/user.ex#L4-L11):
184 |
185 | ```elixir
186 | schema "users" do
187 | field :name, :string
188 | field :email, :string
189 | field :bio, :string
190 | field :number_of_pets, :integer
191 |
192 | timestamps
193 | end
194 | ```
195 |
196 | The JSON output will look like:
197 |
198 | ```javascript
199 | "definitions": {
200 | "HelloUser.User": {
201 | "properties": {
202 | "updated_at": {
203 | "type": "string",
204 | "format": "date-time"
205 | },
206 | "number_of_pets": {
207 | "type": "integer",
208 | "format": "int64"
209 | },
210 | "name": {
211 | "type": "string"
212 | },
213 | "inserted_at": {
214 | "type": "string",
215 | "format": "date-time"
216 | },
217 | "id": {
218 | "type": "integer",
219 | "format": "int64"
220 | },
221 | "email": {
222 | "type": "string"
223 | },
224 | "bio": {
225 | "type": "string"
226 | }
227 | }
228 | }
229 | }
230 | ```
231 |
232 | If changeset is defined in models, like this:
233 | ```elixir
234 | schema "users" do
235 | field :name, :string
236 | field :email, :string
237 | field :bio, :string
238 | field :number_of_pets, :integer
239 |
240 | timestamps
241 | end
242 |
243 | @required_fields ~w(name email)
244 | @optional_fields ~w(bio number_of_pets)
245 |
246 | def changeset(model, params \\ :empty) do
247 | model
248 | |> cast(params, @required_fields, @optional_fields)
249 | end
250 | ```
251 | Changeset is used for 'required' fields in schema.
252 |
253 | ### Converting Phoenix Routes into Swagger Paths
254 | The [Phoenix Routes](https://github.com/phoenixframework/phoenix/blob/v1.0.0/lib/phoenix/router/route.ex) that is found via the [Phoenix Router](https://github.com/phoenixframework/phoenix/blob/v1.0.0/lib/phoenix/router.ex) are converted into a [Swagger Paths Object](http://swagger.io/specification/#pathsObject), each route becoming a [Path Item](http://swagger.io/specification/#pathItemObject). The Phoenix template paths are converted into [Swagger path templates](http://swagger.io/specification/#pathTemplating) and each templated variable is converted into a [Path paramter](http://swagger.io/specification/#parametersDefinitionsObject). All path parameters are assumed to be required and are of type string (except for parameters named `id`, which are assumed to be integers).
255 |
256 | [Response Definitions](http://swagger.io/specification/#responsesDefinitionsObject) are generated, based on the HTTP verb associated with the operation:
257 |
258 | * All Verbs
259 | * "404" => %{"description" => "Resource not found"},
260 | * "401" => %{"description" => "Request is not authorized"},
261 | * "500" => %{"description" => "Internal Server Error"}
262 | * GET
263 | * "200" => %{"description" => "Resource Content"}
264 | * DELETE
265 | * "204" => %{"description" => "No Content"}
266 | * POST
267 | * "201" => %{"description" => "Resource created"
268 | * "400" => %{"description" => "Request contains bad values"}
269 | * PUT
270 | * "204" => %{"description" => "No Content"
271 | * "400" => %{"description" => "Request contains bad values"}
272 |
273 | ## Customized Behavior
274 | The default behavior of the task may be improved by adding action-specific functions that provide the task more detail. As the task scans for [Phoenix Routes](https://github.com/phoenixframework/phoenix/blob/v1.0.0/lib/phoenix/router/route.ex), it will check for the prescense of a function named "swaggerdoc_#{route.controller.method}". If that function is present, the Map returned will be used in place of the default Swagger implementation. The map may consist of the following elements:
275 |
276 | * :description
277 | * Short description of the API endpoint.
278 | * :response_schema
279 | * For API endpoints that are returning a value (i.e. a GET), you may want to return a specific [Schema Object](http://swagger.io/specification/#schemaObject) that represents the return value.
280 | * To specify a specific Ecto Model, add as a [$ref](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/web/controllers/user_controller.ex#L41): `"schema": %{"$ref": "#/definitions/HelloUser.User"}`. Make sure to use the fully-qualified module name.
281 | * To specify an array of Ecto Models, add as an [array of items](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/web/controllers/user_controller.ex#L10): `%{"title" => "Users", "type": "array", "items": %{"$ref": "#/definitions/HelloUser.User"}}`
282 | * To specify a custom object that doesn't have a corresponding model, [build the schema](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/web/controllers/user_controller.ex#L149-L158) directly:
283 | ```elixir
284 | %{
285 | "title" => "User.CustomFields", "type": "array", "items": %{
286 | "title" => "User.CustomField",
287 | "description" => "A Custom Field",
288 | "type" => "object",
289 | "required" => ["key","value"],
290 | "properties" => %{
291 | "key" => %{"type" => "string", "description" => "The key for the custom field"},
292 | "value" => %{"type" => "string", "description" => "The value for the custom field"},
293 | }
294 | }
295 | }
296 | ```
297 | * :parameters
298 | * An array of [Parameter Definition objects](http://swagger.io/specification/#parametersDefinitionsObject). These values may represent a combination of query, body, formdata, etc... For an example, please see the [sync_user](https://github.com/OpenAperture/swaggerdoc/blob/master/examples/hello_user/web/controllers/user_controller.ex#L109-L140):
299 | ```elixir
300 | [%{
301 | "name" => "id",
302 | "in" => "path",
303 | "description" => "Workflow identifier",
304 | "required" => true,
305 | "type" => "integer"
306 | },
307 | %{
308 | "name" => "force_sync",
309 | "in" => "body",
310 | "description" => "Force a synchronization of the user",
311 | "required" => false,
312 | "schema": %{
313 | "title" => "force_sync",
314 | "description" => "Force a synchronization of the user",
315 | "type" => "boolean"
316 | }
317 | },
318 | %{
319 | "name" => "foreign_system_id",
320 | "in" => "body",
321 | "description" => "Foreign User system identifier",
322 | "required" => true,
323 | "schema": %{
324 | "title" => "foreign_system_id",
325 | "description" => "Foreign User system identifier",
326 | "type" => "string"
327 | }
328 | }]
329 | ```
330 | * Note that `body` parameters are required to define a `schema`.
331 |
332 | ## Contributing
333 |
334 | To contribute to OpenAperture development, view our [contributing guide](http://openaperture.io/dev_resources/contributing.html)
335 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for third-
9 | # party users, it should be done in your mix.exs file.
10 |
11 | # Sample configuration:
12 | #
13 | # config :logger, :console,
14 | # level: :info,
15 | # format: "$date $time [$level] $metadata$message\n",
16 | # metadata: [:user_id]
17 |
18 | # It is also possible to import configuration files, relative to this
19 | # directory. For example, you can emulate configuration per environment
20 | # by uncommenting the line below and defining dev.exs, test.exs and such.
21 | # Configuration from the imported file will override the ones defined
22 | # here (which is why it is important to import them last).
23 | #
24 | # import_config "#{Mix.env}.exs"
25 |
--------------------------------------------------------------------------------
/doc/Mix.Tasks.Swagger.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Mix.Tasks.Swagger – swaggerdoc v0.0.1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | swaggerdoc v0.0.1 →
Overview → Mix → Tasks →
Swagger
80 |
81 |
82 |
83 | Mix.Tasks.Swagger
84 |
85 |
86 |
87 |
98 |
99 |
100 |
101 | To use swaggerdoc with your projects, edit your mix.exs file and add it as a dependency:
102 | defp deps do
103 | [{:swaggerdoc, "~> 0.0.1"}]
104 | end
105 | To execute the Mix task, simply type mix swagger
:
106 | hello_user$ mix swagger
107 | Generating Swagger documentation...
108 | Adding Ecto definitions...
109 | Adding Phoenix Routes...
110 | Writing JSON to file...
111 | Finished generating Swagger documentation!
112 | To view the generated Swagger in swagger-ui :
113 |
114 | In a temp folder, execute a git clone of https://github.com/swagger-api/swagger-ui.git
115 |
116 | In the browser of your choice, open the file temp folder /swagger-ui/dist/index.html
117 |
118 | In the JSON API input box at the top of the page, paste in the link to the JSON
119 |
120 | Hit the ‘Explore’ button
121 |
122 |
123 | For a complete example, please see the examples section.
124 |
125 |
126 |
127 |
128 |
129 | Source
130 |
131 |
132 |
133 |
134 | Summary
135 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Functions
193 |
194 |
202 |
203 | Specs:
204 |
205 |
206 | add_routes(list, %{}) :: %{}
207 |
208 |
209 |
210 |
211 | Method to add Phoenix routes to the Swagger map
212 |
213 |
214 |
215 | Source
216 |
217 |
218 |
219 |
227 |
228 | Specs:
229 |
230 |
231 | app_json :: %{}
232 |
233 |
234 |
235 |
236 | Contains the application-specific JSON that forms the base of the Swagger JSON
237 |
238 |
239 |
240 | Source
241 |
242 |
243 |
244 |
252 |
253 | Specs:
254 |
255 |
256 | build_definitions(list, %{}) :: %{}
257 |
258 |
259 |
260 |
261 | Method to build the Swagger definitions from Ecto models
262 |
263 |
264 |
265 | Source
266 |
267 |
268 |
269 |
277 |
278 | Specs:
279 |
280 |
281 | convert_property_type(term) :: %{}
282 |
283 |
284 |
285 |
290 |
291 | Source
292 |
293 |
294 |
295 |
303 |
304 | Specs:
305 |
306 |
307 | default_responses(String.t , any) :: %{}
308 |
309 |
310 |
311 |
312 | Method to build the default Swagger response map for a specific verb, if not specified by the developer
313 |
314 |
315 |
316 | Source
317 |
318 |
319 |
320 |
328 |
329 | Specs:
330 |
331 |
332 | get_router([any]) :: term
333 |
334 |
335 |
336 |
337 | Method to return the Phoenix router, based on args or configuration
338 |
339 |
340 |
341 | Source
342 |
343 |
344 |
345 |
353 |
354 | Specs:
355 |
356 |
357 | parse_default_verb(String.t ) :: %{}
358 |
359 |
360 |
361 |
362 | Method to build the default Swagger verb map, if not specified by the developer
363 |
364 |
365 |
366 | Source
367 |
368 |
369 |
370 |
378 |
379 | Specs:
380 |
381 |
382 | path_from_route(list, %{}) :: %{}
383 |
384 |
385 |
386 |
391 |
392 | Source
393 |
394 |
395 |
396 |
404 |
405 | Specs:
406 |
407 |
408 | run([any]) :: no_return
409 |
410 |
411 |
412 |
413 | Mix entrypoint method
414 |
415 |
416 |
417 | Source
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
--------------------------------------------------------------------------------
/doc/README.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | README – swaggerdoc v0.0.1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | swaggerdoc v0.0.1 →
README
79 |
80 |
81 | SwaggerDoc
82 | The SwaggerDoc module provides a convenience task for generating Swagger API documentation for Phoenix and Ecto-based projects. This task has been created for Phoenix and Ecto 1.0 and greater.
83 | Getting Started
84 | To use swaggerdoc with your projects, edit your mix.exs file and add it as a dependency:
85 | defp deps do
86 | [{:swaggerdoc, "~> 0.0.1"}]
87 | end
88 | To execute the Mix task, simply type mix swagger
:
89 | hello_user$ mix swagger
90 | Generating Swagger documentation...
91 | Adding Ecto definitions...
92 | Adding Phoenix Routes...
93 | Writing JSON to file...
94 | Finished generating Swagger documentation!
95 | To view the generated Swagger in swagger-ui :
96 |
97 | In a temp folder, execute a git clone of https://github.com/swagger-api/swagger-ui.git
98 |
99 | In the browser of your choice, open the file temp folder /swagger-ui/dist/index.html
100 |
101 | In the JSON API input box at the top of the page, paste in the link to the JSON
102 |
103 | Hit the ‘Explore’ button
104 |
105 |
106 | For a complete example, please see the examples section.
107 | Config
108 | SwaggerDoc’s version and project information is configured in your config.exs files. The options are specified under the :swaggerdoc application.
109 | config :swaggerdoc,
110 | swagger_version: "2.0"
111 | Task Config
112 | The following task-specific options are available:
113 |
114 | :output_path
115 |
116 | Description: Specifies the file path where the JSON file will be created.
117 |
118 | Type: string
119 |
120 | Default Value: System.cwd!() <> “/swagger”
121 |
122 | :output_file
123 |
124 | Description: Specifies the file name (within the output_path) of the Swagger JSON
125 |
126 | Type: string
127 |
128 | Default Value: “api.json”
129 |
130 |
131 | Swagger Config
132 | The following Swagger-specific options are available:
133 |
134 | :swagger_version
135 |
136 | Description: Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be “2.0”.
137 |
138 | Swagger Location: Swagger Object , “swagger”
139 |
140 | Type: string
141 |
142 | Default Value: “2.0”
143 |
144 | :host
145 |
146 | Description: The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used (including the port). The host does not support path templating.
147 |
148 | Swagger Location: Swagger Object , “host”
149 |
150 | Type: string
151 |
152 | Default Value: “”
153 |
154 | :base_path
155 |
156 | Description: The base path on which the API is served, which is relative to the host. If it is not included, the API is served directly under the host. The value MUST start with a leading slash (/). The basePath does not support path templating.
157 |
158 | Swagger Location: Swagger Object , “basePath”
159 |
160 | Type: string
161 |
162 | Default Value: “”
163 |
164 | :schemes
165 |
166 | Description: The transfer protocol of the API. Values MUST be from the list: “http”, “https”, “ws”, “wss”. If the schemes is not included, the default scheme to be used is the one used to access the Swagger definition itself.
167 |
168 | Swagger Location: Swagger Object , “schemes”
169 |
170 | Type: [string]
171 |
172 | Default Value: ["http"]
173 |
174 | :consumes
175 |
176 | Description: A list of MIME types the APIs can consume. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types.
177 |
178 | Swagger Location: Swagger Object , “consumes”
179 |
180 | Type: [string]
181 |
182 | Default Value: []
183 |
184 | :produces
185 |
186 | Description: A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under Mime Types.
187 |
188 | Swagger Location: Swagger Object , “produces”
189 |
190 | Type: [string]
191 |
192 | Default Value: []
193 |
194 | :project_version
195 |
196 | Description: Provides the version of the application API (not to be confused with the specification version).
197 |
198 | Swagger Location: Info Object , “version”
199 |
200 | Type: string
201 |
202 | Default Value: “”
203 |
204 | :project_name
205 |
206 | Description: The title of the application.
207 |
208 | Swagger Location: Info Object , “title”
209 |
210 | Type: string
211 |
212 | Default Value: “”
213 |
214 | :project_desc
215 |
216 | Description: A short description of the application. GFM syntax can be used for rich text representation.
217 |
218 | Swagger Location: Info Object , “description”
219 |
220 | Type: string
221 |
222 | Default Value: “”
223 |
224 | :project_terms
225 |
226 | Description: The Terms of Service for the API.
227 |
228 | Swagger Location: Info Object , “termsOfService”
229 |
230 | Type: string
231 |
232 | Default Value: “”
233 |
234 | :project_contact_name
235 |
236 | Description: The identifying name of the contact person/organization.
237 |
238 | Swagger Location: Contact Object , “name”
239 |
240 | Type: string
241 |
242 | Default Value: “”
243 |
244 | :project_contact_email
245 |
246 | Description: The email address of the contact person/organization. MUST be in the format of an email address.
247 |
248 | Swagger Location: Contact Object , “email”
249 |
250 | Type: string
251 |
252 | Default Value: “”
253 |
254 | :project_contact_url
255 |
256 | Description: The URL pointing to the contact information. MUST be in the format of a URL.
257 |
258 | Swagger Location: Contact Object , “url”
259 |
260 | Type: string
261 |
262 | Default Value: “”
263 |
264 | :project_license_name
265 |
266 | Description: The license name used for the API.
267 |
268 | Swagger Location: License Object , “url”
269 |
270 | Type: string
271 |
272 | Default Value: “”
273 |
274 | :project_license_url
275 |
276 | Description: A URL to the license used for the API. MUST be in the format of a URL.
277 |
278 | Swagger Location: License Object , “url”
279 |
280 | Type: string
281 |
282 | Default Value: “”
283 |
284 |
285 | Here’s an example from the sample HelloUser’s config.exs :
286 | config :swaggerdoc,
287 | swagger_version: "2.0",
288 | project_version: "1.0.0",
289 | project_name: "Hello User",
290 | project_desc: "The REST API for the Hello User",
291 | project_terms: "https://www.mozilla.org/en-US/MPL/2.0/",
292 | project_contact_name: "OpenAperture",
293 | project_contact_email: "openaperture@lexmark.com",
294 | project_contact_url: "http://openaperture.io",
295 | project_license_name: "Mozilla Public License, v. 2.0",
296 | project_license_url: "https://www.mozilla.org/en-US/MPL/2.0/",
297 | host: "openaperture.io",
298 | base_path: "/",
299 | schemes: ["https"],
300 | consumes: ["application/json"],
301 | produces: ["application/json"]
302 | Default Behavior
303 | The mix task is designed to scan for Ecto-specific Models and Phoenix-specific routes to attempt to generate an accurrate Swagger API object.
304 | Converting Ecto Models into Swagger Definitions
305 | Each Ecto Model that is identified (prescence of the schema method ) is converted into a Definitions Object .
306 | The conversion uses schema fields and updates them into schemas unde the Definitions Object . The following values are used to convert an Ecto schema type into a Swagger property type :
307 |
308 | :id -> %{“type” => “integer”, “format” => “int64”}
309 |
310 | :binary_id -> %{“type” => “string”, “format” => “binary”}
311 |
312 | :integer -> %{“type” => “integer”, “format” => “int64”}
313 |
314 | :float -> %{“type” => “number”, “format” => “float”}
315 |
316 | :boolean -> %{“type” => “boolean”}
317 |
318 | :string -> %{“type” => “string”}
319 |
320 | :binary -> %{“type” => “string”, “format” => “binary”}
321 |
322 | :Ecto.DateTime -> %{“type” => “string”, “format” => “date-time”}
323 |
324 | :Ecto.Date -> %{“type” => “string”, “format” => “date”}
325 |
326 | :Ecto.Time -> %{“type” => “string”, “format” => “date-time”}
327 |
328 | :uuid -> %{“type” => “string”}
329 |
330 | _ -> %{“type” => “string”}
331 |
332 |
333 | Looking at the HelloUser.User model :
334 | schema "users" do
335 | field :name, :string
336 | field :email, :string
337 | field :bio, :string
338 | field :number_of_pets, :integer
339 |
340 | timestamps
341 | end
342 | The JSON output will look like:
343 | "definitions": {
344 | "HelloUser.User": {
345 | "properties": {
346 | "updated_at": {
347 | "type": "string",
348 | "format": "date-time"
349 | },
350 | "number_of_pets": {
351 | "type": "integer",
352 | "format": "int64"
353 | },
354 | "name": {
355 | "type": "string"
356 | },
357 | "inserted_at": {
358 | "type": "string",
359 | "format": "date-time"
360 | },
361 | "id": {
362 | "type": "integer",
363 | "format": "int64"
364 | },
365 | "email": {
366 | "type": "string"
367 | },
368 | "bio": {
369 | "type": "string"
370 | }
371 | }
372 | }
373 | }
374 | Converting Phoenix Routes into Swagger Paths
375 | The Phoenix Routes that is found via the Phoenix Router are converted into a Swagger Paths Object , each route becoming a Path Item . The Phoenix template paths are converted into Swagger path templates and each templated variable is converted into a Path paramter . All path parameters are assumed to be required and are of type string (except for parameters named id
, which are assumed to be integers).
376 | Response Definitions are generated, based on the HTTP verb associated with the operation:
377 |
378 | All Verbs
379 |
380 | “404” => %{“description” => “Resource not found”},
381 |
382 | “401” => %{“description” => “Request is not authorized”},
383 |
384 | “500” => %{“description” => “Internal Server Error”}
385 |
386 | GET
387 |
388 | “200” => %{“description” => “Resource Content”}
389 |
390 | DELETE
391 |
392 | “204” => %{“description” => “No Content”}
393 |
394 | POST
395 |
396 | “201” => %{“description” => “Resource created”
397 |
398 | “400” => %{“description” => “Request contains bad values”}
399 |
400 | PUT
401 |
402 | “204” => %{“description” => “No Content”
403 |
404 | “400” => %{“description” => “Request contains bad values”}
405 |
406 |
407 | Customized Behavior
408 | The default behavior of the task may be improved by adding action-specific functions that provide the task more detail. As the task scans for Phoenix Routes , it will check for the prescense of a function named “swaggerdoc_#{route.controller.method}”. If that function is present, the Map returned will be used in place of the default Swagger implementation. The map may consist of the following elements:
409 |
410 | :description
411 |
412 | Short description of the API endpoint.
413 |
414 | :response_schema
415 |
416 | For API endpoints that are returning a value (i.e. a GET), you may want to return a specific Schema Object that represents the return value.
417 |
418 | To specify a specific Ecto Model, add as a $ref : "schema": %{"$ref": "#/definitions/HelloUser.User"}
. Make sure to use the fully-qualified module name.
419 |
420 | To specify an array of Ecto Models, add as an array of items : %{"title" => "Users", "type": "array", "items": %{"$ref": "#/definitions/HelloUser.User"}}
421 |
422 | To specify a custom object that doesn’t have a corresponding model, build the schema directly:
423 |
424 |
425 | %{
426 | "title" => "User.CustomFields", "type": "array", "items": %{
427 | "title" => "User.CustomField",
428 | "description" => "A Custom Field",
429 | "type" => "object",
430 | "required" => ["key","value"],
431 | "properties" => %{
432 | "key" => %{"type" => "string", "description" => "The key for the custom field"},
433 | "value" => %{"type" => "string", "description" => "The value for the custom field"},
434 | }
435 | }
436 | }
437 |
438 | :parameters
439 |
440 | An array of Parameter Definition objects . These values may represent a combination of query, body, formdata, etc… For an example, please see the sync_user :
441 |
442 |
443 | [%{
444 | "name" => "id",
445 | "in" => "path",
446 | "description" => "Workflow identifier",
447 | "required" => true,
448 | "type" => "integer"
449 | },
450 | %{
451 | "name" => "force_sync",
452 | "in" => "body",
453 | "description" => "Force a synchronization of the user",
454 | "required" => false,
455 | "schema": %{
456 | "title" => "force_sync",
457 | "description" => "Force a synchronization of the user",
458 | "type" => "boolean"
459 | }
460 | },
461 | %{
462 | "name" => "foreign_system_id",
463 | "in" => "body",
464 | "description" => "Foreign User system identifier",
465 | "required" => true,
466 | "schema": %{
467 | "title" => "foreign_system_id",
468 | "description" => "Foreign User system identifier",
469 | "type" => "string"
470 | }
471 | }]
472 |
473 | Note that body
parameters are required to define a schema
.
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
--------------------------------------------------------------------------------
/doc/SwaggerDoc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SwaggerDoc – swaggerdoc v0.0.1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
72 |
73 |
74 |
75 |
81 |
82 |
83 | SwaggerDoc
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | The SwaggerDoc module provides a convenience task for generating Swagger
98 | API documentation for Phoenix and Ecto-based projects.
99 |
100 |
101 |
102 |
103 |
104 | Source
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/doc/dist/sidebar_items.js:
--------------------------------------------------------------------------------
1 | sidebarNodes = {
2 | "modules": [
3 | {"id": "Mix.Tasks.Swagger",
4 | "docs": ["add_routes/2","app_json/0","build_definitions/2","convert_property_type/1","default_responses/2","get_router/1","parse_default_verb/1","path_from_route/2","run/1"]
5 | },{"id": "SwaggerDoc"
6 |
7 | }]
8 |
9 | };
10 | fillSidebarWithNodes(sidebarNodes);
11 |
--------------------------------------------------------------------------------
/doc/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/doc/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/doc/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/doc/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/doc/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/doc/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/doc/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/doc/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | swaggerdoc v0.0.1 – Documentation
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/doc/overview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Overview – swaggerdoc v0.0.1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | swaggerdoc v0.0.1 →
Overview
80 |
81 |
82 |
83 | swaggerdoc v0.0.1
84 |
85 |
86 |
87 | Modules
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Modules summary
96 |
97 |
98 | Mix.Tasks.Swagger
99 | To use swaggerdoc with your projects, edit your mix.exs file and add it as a dependency:
100 |
101 |
102 |
103 | SwaggerDoc
104 | The SwaggerDoc module provides a convenience task for generating Swagger
105 | API documentation for Phoenix and Ecto-based projects
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/examples/hello_user/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | /_build
3 | /db
4 | /deps
5 | /*.ez
6 | /swagger
7 |
8 | # Generate on crash by the VM
9 | erl_crash.dump
10 |
11 | # Static artifacts
12 | /node_modules
13 |
14 | # Since we are building assets from web/static,
15 | # we ignore priv/static. You may want to comment
16 | # this depending on your deployment strategy.
17 | /priv/static/
18 |
19 | # The config/prod.secret.exs file by default contains sensitive
20 | # data and you should not commit it into version control.
21 | #
22 | # Alternatively, you may comment the line below and commit the
23 | # secrets file as long as you replace its contents by environment
24 | # variables.
25 | /config/prod.secret.exs
26 |
--------------------------------------------------------------------------------
/examples/hello_user/README.md:
--------------------------------------------------------------------------------
1 | # HelloUser
2 |
3 | To start your Phoenix app:
4 |
5 | 1. Install dependencies with `mix deps.get`
6 | 2. Create and migrate your database with `mix ecto.create && mix ecto.migrate`
7 | 3. Start Phoenix endpoint with `mix phoenix.server`
8 |
9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
10 |
11 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
12 |
13 | ## Learn more
14 |
15 | * Official website: http://www.phoenixframework.org/
16 | * Guides: http://phoenixframework.org/docs/overview
17 | * Docs: http://hexdocs.pm/phoenix
18 | * Mailing list: http://groups.google.com/group/phoenix-talk
19 | * Source: https://github.com/phoenixframework/phoenix
20 |
--------------------------------------------------------------------------------
/examples/hello_user/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: 'js/app.js'
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // https://github.com/brunch/brunch/blob/stable/docs/config.md#files
9 | // joinTo: {
10 | // 'js/app.js': /^(web\/static\/js)/,
11 | // 'js/vendor.js': /^(web\/static\/vendor)|(deps)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // https://github.com/brunch/brunch/tree/master/docs#concatenation
16 | // order: {
17 | // before: [
18 | // 'web/static/vendor/js/jquery-2.1.1.js',
19 | // 'web/static/vendor/js/bootstrap.min.js'
20 | // ]
21 | // }
22 | },
23 | stylesheets: {
24 | joinTo: 'css/app.css'
25 | },
26 | templates: {
27 | joinTo: 'js/app.js'
28 | }
29 | },
30 |
31 | conventions: {
32 | // This option sets where we should place non-css and non-js assets in.
33 | // By default, we set this to '/web/static/assets'. Files in this directory
34 | // will be copied to `paths.public`, which is "priv/static" by default.
35 | assets: /^(web\/static\/assets)/
36 | },
37 |
38 | // Phoenix paths configuration
39 | paths: {
40 | // Dependencies and current project directories to watch
41 | watched: ["deps/phoenix/web/static",
42 | "deps/phoenix_html/web/static",
43 | "web/static", "test/static"],
44 |
45 | // Where to compile files to
46 | public: "priv/static"
47 | },
48 |
49 | // Configure your plugins
50 | plugins: {
51 | babel: {
52 | // Do not use ES6 compiler in vendor code
53 | ignore: [/web\/static\/vendor/]
54 | }
55 | },
56 |
57 | modules: {
58 | autoRequire: {
59 | 'js/app.js': ['web/static/js/app']
60 | }
61 | },
62 |
63 | npm: {
64 | enabled: true
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/examples/hello_user/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Configures the endpoint
9 | config :hello_user, HelloUser.Endpoint,
10 | url: [host: "localhost"],
11 | root: Path.dirname(__DIR__),
12 | secret_key_base: "LhQGXEISk9W1S5pJk7QgmGySTxMMWTuIxpek5NocU6KO6FiHu1RTBCM8YZRg5Yzz",
13 | render_errors: [accepts: ~w(html json)],
14 | pubsub: [name: HelloUser.PubSub,
15 | adapter: Phoenix.PubSub.PG2]
16 |
17 | # Configures Elixir's Logger
18 | config :logger, :console,
19 | format: "$time $metadata[$level] $message\n",
20 | metadata: [:request_id]
21 |
22 | config :swaggerdoc,
23 | swagger_version: "2.0",
24 | project_version: "1.0.0",
25 | project_name: "Hello User",
26 | project_desc: "The REST API for the Hello User",
27 | project_terms: "https://www.mozilla.org/en-US/MPL/2.0/",
28 | project_contact_name: "OpenAperture",
29 | project_contact_email: "openaperture@lexmark.com",
30 | project_contact_url: "http://openaperture.io",
31 | project_license_name: "Mozilla Public License, v. 2.0",
32 | project_license_url: "https://www.mozilla.org/en-US/MPL/2.0/",
33 | host: "openaperture.io",
34 | base_path: "/",
35 | schemes: ["https"],
36 | consumes: ["application/json"],
37 | produces: ["application/json"]
38 |
39 | # Import environment specific config. This must remain at the bottom
40 | # of this file so it overrides the configuration defined above.
41 | import_config "#{Mix.env}.exs"
42 |
--------------------------------------------------------------------------------
/examples/hello_user/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :hello_user, HelloUser.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | cache_static_lookup: false,
14 | check_origin: false,
15 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin"]]
16 |
17 | # Watch static and templates for browser reloading.
18 | config :hello_user, HelloUser.Endpoint,
19 | live_reload: [
20 | patterns: [
21 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
22 | ~r{web/views/.*(ex)$},
23 | ~r{web/templates/.*(eex)$}
24 | ]
25 | ]
26 |
27 | # Do not include metadata nor timestamps in development logs
28 | config :logger, :console, format: "[$level] $message\n"
29 |
30 | # Set a higher stacktrace during development.
31 | # Do not configure such in production as keeping
32 | # and calculating stacktraces is usually expensive.
33 | config :phoenix, :stacktrace_depth, 20
34 |
35 | # Configure your database
36 | config :hello_user, HelloUser.Repo,
37 | adapter: Ecto.Adapters.Postgres,
38 | username: "postgres",
39 | password: "postgres",
40 | database: "hello_user_dev",
41 | pool_size: 10
42 |
--------------------------------------------------------------------------------
/examples/hello_user/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | #
10 | # Finally, we also include the path to a manifest
11 | # containing the digested version of static files. This
12 | # manifest is generated by the mix phoenix.digest task
13 | # which you typically run after static files are built.
14 | config :hello_user, HelloUser.Endpoint,
15 | http: [port: {:system, "PORT"}],
16 | url: [host: "example.com", port: 80],
17 | cache_static_manifest: "priv/static/manifest.json"
18 |
19 | # Do not print debug messages in production
20 | config :logger, level: :info
21 |
22 | # ## SSL Support
23 | #
24 | # To get SSL working, you will need to add the `https` key
25 | # to the previous section and set your `:url` port to 443:
26 | #
27 | # config :hello_user, HelloUser.Endpoint,
28 | # ...
29 | # url: [host: "example.com", port: 443],
30 | # https: [port: 443,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
33 | #
34 | # Where those two env variables return an absolute path to
35 | # the key and cert in disk or a relative path inside priv,
36 | # for example "priv/ssl/server.key".
37 | #
38 | # We also recommend setting `force_ssl`, ensuring no data is
39 | # ever sent via http, always redirecting to https:
40 | #
41 | # config :hello_user, HelloUser.Endpoint,
42 | # force_ssl: [hsts: true]
43 | #
44 | # Check `Plug.SSL` for all available options in `force_ssl`.
45 |
46 | # ## Using releases
47 | #
48 | # If you are doing OTP releases, you need to instruct Phoenix
49 | # to start the server for all endpoints:
50 | #
51 | # config :phoenix, :serve_endpoints, true
52 | #
53 | # Alternatively, you can configure exactly which server to
54 | # start per endpoint:
55 | #
56 | # config :hello_user, HelloUser.Endpoint, server: true
57 | #
58 |
59 | # Finally import the config/prod.secret.exs
60 | # which should be versioned separately.
61 | import_config "prod.secret.exs"
62 |
--------------------------------------------------------------------------------
/examples/hello_user/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :hello_user, HelloUser.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
12 | # Set a higher stacktrace during test
13 | config :phoenix, :stacktrace_depth, 20
14 |
15 | # Configure your database
16 | config :hello_user, HelloUser.Repo,
17 | adapter: Ecto.Adapters.Postgres,
18 | username: "postgres",
19 | password: "postgres",
20 | database: "hello_user_test",
21 | pool: Ecto.Adapters.SQL.Sandbox
22 |
--------------------------------------------------------------------------------
/examples/hello_user/lib/hello_user.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser do
2 | use Application
3 |
4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec, warn: false
8 |
9 | children = [
10 | # Start the endpoint when the application starts
11 | supervisor(HelloUser.Endpoint, []),
12 | # Start the Ecto repository
13 | worker(HelloUser.Repo, []),
14 | # Here you could define other workers and supervisors as children
15 | # worker(HelloUser.Worker, [arg1, arg2, arg3]),
16 | ]
17 |
18 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
19 | # for other strategies and supported options
20 | opts = [strategy: :one_for_one, name: HelloUser.Supervisor]
21 | Supervisor.start_link(children, opts)
22 | end
23 |
24 | # Tell Phoenix to update the endpoint configuration
25 | # whenever the application is updated.
26 | def config_change(changed, _new, removed) do
27 | HelloUser.Endpoint.config_change(changed, removed)
28 | :ok
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/examples/hello_user/lib/hello_user/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :hello_user
3 |
4 | socket "/socket", HelloUser.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :hello_user, gzip: false,
12 | only: ~w(css fonts images js favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | plug Plug.Session,
34 | store: :cookie,
35 | key: "_hello_user_key",
36 | signing_salt: "f9HqDHIr"
37 |
38 | plug HelloUser.Router
39 | end
40 |
--------------------------------------------------------------------------------
/examples/hello_user/lib/hello_user/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Repo do
2 | use Ecto.Repo, otp_app: :hello_user
3 | end
4 |
--------------------------------------------------------------------------------
/examples/hello_user/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :hello_user,
6 | version: "0.0.1",
7 | elixir: "~> 1.0",
8 | elixirc_paths: elixirc_paths(Mix.env),
9 | compilers: [:phoenix] ++ Mix.compilers,
10 | build_embedded: Mix.env == :prod,
11 | start_permanent: Mix.env == :prod,
12 | deps: deps]
13 | end
14 |
15 | # Configuration for the OTP application
16 | #
17 | # Type `mix help compile.app` for more information
18 | def application do
19 | [mod: {HelloUser, []},
20 | applications: [:phoenix, :phoenix_html, :cowboy, :logger,
21 | :phoenix_ecto, :postgrex]]
22 | end
23 |
24 | # Specifies which paths to compile per environment
25 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
26 | defp elixirc_paths(_), do: ["lib", "web"]
27 |
28 | # Specifies your project dependencies
29 | #
30 | # Type `mix help deps` for examples and options
31 | defp deps do
32 | [{:phoenix, "~> 1.0.0"},
33 | {:phoenix_ecto, "~> 1.1"},
34 | {:postgrex, ">= 0.0.0"},
35 | {:phoenix_html, "~> 2.1"},
36 | {:phoenix_live_reload, "~> 1.0", only: :dev},
37 | {:cowboy, "~> 1.0"},
38 | {:swaggerdoc, "~> 0.0.1", path: "../../"}
39 | ]
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/examples/hello_user/mix.lock:
--------------------------------------------------------------------------------
1 | %{"cowboy": {:hex, :cowboy, "1.0.2"},
2 | "cowlib": {:hex, :cowlib, "1.0.1"},
3 | "decimal": {:hex, :decimal, "1.1.0"},
4 | "ecto": {:hex, :ecto, "1.0.1"},
5 | "fs": {:hex, :fs, "0.9.2"},
6 | "phoenix": {:hex, :phoenix, "1.0.1"},
7 | "phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0"},
8 | "phoenix_html": {:hex, :phoenix_html, "2.2.0"},
9 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.0"},
10 | "plug": {:hex, :plug, "1.0.0"},
11 | "poison": {:hex, :poison, "1.5.0"},
12 | "poolboy": {:hex, :poolboy, "1.5.1"},
13 | "postgrex": {:hex, :postgrex, "0.9.1"},
14 | "ranch": {:hex, :ranch, "1.1.0"}}
15 |
--------------------------------------------------------------------------------
/examples/hello_user/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {
3 | },
4 | "dependencies": {
5 | "brunch": "^1.8.5",
6 | "babel-brunch": "^5.1.1",
7 | "clean-css-brunch": ">= 1.0 < 1.8",
8 | "css-brunch": ">= 1.0 < 1.8",
9 | "javascript-brunch": ">= 1.0 < 1.8",
10 | "uglify-js-brunch": ">= 1.0 < 1.8"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/hello_user/priv/repo/migrations/20150904154139_create_user.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Repo.Migrations.CreateUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:users) do
6 | add :name, :string
7 | add :email, :string
8 | add :bio, :string
9 | add :number_of_pets, :integer
10 |
11 | timestamps
12 | end
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/examples/hello_user/priv/repo/seeds.exs:
--------------------------------------------------------------------------------
1 | # Script for populating the database. You can run it as:
2 | #
3 | # mix run priv/repo/seeds.exs
4 | #
5 | # Inside the script, you can read and write to any of your
6 | # repositories directly:
7 | #
8 | # HelloUser.Repo.insert!(%SomeModel{})
9 | #
10 | # We recommend using the bang functions (`insert!`, `update!`
11 | # and so on) as they will fail if something goes wrong.
12 |
--------------------------------------------------------------------------------
/examples/hello_user/test/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.PageControllerTest do
2 | use HelloUser.ConnCase
3 |
4 | test "GET /" do
5 | conn = get conn(), "/"
6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/examples/hello_user/test/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.UserControllerTest do
2 | use HelloUser.ConnCase
3 |
4 | alias HelloUser.User
5 | @valid_attrs %{bio: "some content", email: "some content", name: "some content", number_of_pets: 42}
6 | @invalid_attrs %{}
7 |
8 | setup do
9 | conn = conn()
10 | {:ok, conn: conn}
11 | end
12 |
13 | test "lists all entries on index", %{conn: conn} do
14 | conn = get conn, user_path(conn, :index)
15 | assert html_response(conn, 200) =~ "Listing users"
16 | end
17 |
18 | test "renders form for new resources", %{conn: conn} do
19 | conn = get conn, user_path(conn, :new)
20 | assert html_response(conn, 200) =~ "New user"
21 | end
22 |
23 | test "creates resource and redirects when data is valid", %{conn: conn} do
24 | conn = post conn, user_path(conn, :create), user: @valid_attrs
25 | assert redirected_to(conn) == user_path(conn, :index)
26 | assert Repo.get_by(User, @valid_attrs)
27 | end
28 |
29 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
30 | conn = post conn, user_path(conn, :create), user: @invalid_attrs
31 | assert html_response(conn, 200) =~ "New user"
32 | end
33 |
34 | test "shows chosen resource", %{conn: conn} do
35 | user = Repo.insert! %User{}
36 | conn = get conn, user_path(conn, :show, user)
37 | assert html_response(conn, 200) =~ "Show user"
38 | end
39 |
40 | test "renders page not found when id is nonexistent", %{conn: conn} do
41 | assert_raise Ecto.NoResultsError, fn ->
42 | get conn, user_path(conn, :show, -1)
43 | end
44 | end
45 |
46 | test "renders form for editing chosen resource", %{conn: conn} do
47 | user = Repo.insert! %User{}
48 | conn = get conn, user_path(conn, :edit, user)
49 | assert html_response(conn, 200) =~ "Edit user"
50 | end
51 |
52 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
53 | user = Repo.insert! %User{}
54 | conn = put conn, user_path(conn, :update, user), user: @valid_attrs
55 | assert redirected_to(conn) == user_path(conn, :show, user)
56 | assert Repo.get_by(User, @valid_attrs)
57 | end
58 |
59 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
60 | user = Repo.insert! %User{}
61 | conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
62 | assert html_response(conn, 200) =~ "Edit user"
63 | end
64 |
65 | test "deletes chosen resource", %{conn: conn} do
66 | user = Repo.insert! %User{}
67 | conn = delete conn, user_path(conn, :delete, user)
68 | assert redirected_to(conn) == user_path(conn, :index)
69 | refute Repo.get(User, user.id)
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/examples/hello_user/test/models/user_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.UserTest do
2 | use HelloUser.ModelCase
3 |
4 | alias HelloUser.User
5 |
6 | @valid_attrs %{bio: "some content", email: "some content", name: "some content", number_of_pets: 42}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = User.changeset(%User{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = User.changeset(%User{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/examples/hello_user/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | imports other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | alias HelloUser.Repo
24 | import Ecto.Model
25 | import Ecto.Query, only: [from: 2]
26 |
27 |
28 | # The default endpoint for testing
29 | @endpoint HelloUser.Endpoint
30 | end
31 | end
32 |
33 | setup tags do
34 | unless tags[:async] do
35 | Ecto.Adapters.SQL.restart_test_transaction(HelloUser.Repo, [])
36 | end
37 |
38 | :ok
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/examples/hello_user/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | imports other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 |
23 | alias HelloUser.Repo
24 | import Ecto.Model
25 | import Ecto.Query, only: [from: 2]
26 |
27 | import HelloUser.Router.Helpers
28 |
29 | # The default endpoint for testing
30 | @endpoint HelloUser.Endpoint
31 | end
32 | end
33 |
34 | setup tags do
35 | unless tags[:async] do
36 | Ecto.Adapters.SQL.restart_test_transaction(HelloUser.Repo, [])
37 | end
38 |
39 | :ok
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/examples/hello_user/test/support/model_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.ModelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | model tests.
5 |
6 | You may define functions here to be used as helpers in
7 | your model tests. See `errors_on/2`'s definition as reference.
8 |
9 | Finally, if the test case interacts with the database,
10 | it cannot be async. For this reason, every test runs
11 | inside a transaction which is reset at the beginning
12 | of the test unless the test case is marked as async.
13 | """
14 |
15 | use ExUnit.CaseTemplate
16 |
17 | using do
18 | quote do
19 | alias HelloUser.Repo
20 | import Ecto.Model
21 | import Ecto.Query, only: [from: 2]
22 | import HelloUser.ModelCase
23 | end
24 | end
25 |
26 | setup tags do
27 | unless tags[:async] do
28 | Ecto.Adapters.SQL.restart_test_transaction(HelloUser.Repo, [])
29 | end
30 |
31 | :ok
32 | end
33 |
34 | @doc """
35 | Helper for returning list of errors in model when passed certain data.
36 |
37 | ## Examples
38 |
39 | Given a User model that lists `:name` as a required field and validates
40 | `:password` to be safe, it would return:
41 |
42 | iex> errors_on(%User{}, password: "password")
43 | [password: "is unsafe", name: "is blank"]
44 |
45 | You could then write your assertion like:
46 |
47 | assert {:password, "is unsafe"} in errors_on(%User{}, password: "password")
48 |
49 | You can also create the changeset manually and retrieve the errors
50 | field directly:
51 |
52 | iex> changeset = User.changeset(%User{}, password: "password")
53 | iex> {:password, "is unsafe"} in changeset.errors
54 | true
55 | """
56 | def errors_on(model, data) do
57 | model.__struct__.changeset(model, data).errors
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/examples/hello_user/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
3 | Mix.Task.run "ecto.create", ["--quiet"]
4 | Mix.Task.run "ecto.migrate", ["--quiet"]
5 | Ecto.Adapters.SQL.begin_test_transaction(HelloUser.Repo)
6 |
7 |
--------------------------------------------------------------------------------
/examples/hello_user/test/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.ErrorViewTest do
2 | use HelloUser.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(HelloUser.ErrorView, "404.html", []) ==
9 | "Page not found"
10 | end
11 |
12 | test "render 500.html" do
13 | assert render_to_string(HelloUser.ErrorView, "500.html", []) ==
14 | "Server internal error"
15 | end
16 |
17 | test "render any other" do
18 | assert render_to_string(HelloUser.ErrorView, "505.html", []) ==
19 | "Server internal error"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/examples/hello_user/test/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.LayoutViewTest do
2 | use HelloUser.ConnCase, async: true
3 | end
--------------------------------------------------------------------------------
/examples/hello_user/test/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.PageViewTest do
2 | use HelloUser.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/examples/hello_user/web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "rooms:*", HelloUser.RoomChannel
6 |
7 | ## Transports
8 | transport :websocket, Phoenix.Transports.WebSocket
9 | # transport :longpoll, Phoenix.Transports.LongPoll
10 |
11 | # Socket params are passed from the client and can
12 | # be used to verify and authenticate a user. After
13 | # verification, you can put default assigns into
14 | # the socket that will be set for all channels, ie
15 | #
16 | # {:ok, assign(socket, :user_id, verified_user_id)}
17 | #
18 | # To deny connection, return `:error`.
19 | #
20 | # See `Phoenix.Token` documentation for examples in
21 | # performing token verification on connect.
22 | def connect(_params, socket) do
23 | {:ok, socket}
24 | end
25 |
26 | # Socket id's are topics that allow you to identify all sockets for a given user:
27 | #
28 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}"
29 | #
30 | # Would allow you to broadcast a "disconnect" event and terminate
31 | # all active sockets and channels for a given user:
32 | #
33 | # HelloUser.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
34 | #
35 | # Returning `nil` makes this socket anonymous.
36 | def id(_socket), do: nil
37 | end
38 |
--------------------------------------------------------------------------------
/examples/hello_user/web/controllers/controller_helper.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Controllers.ControllerHelper do
2 |
3 | @moduledoc """
4 | This module provides helper functions that are used by controllers to
5 | format incoming and outgoing data.
6 | """
7 |
8 | @doc """
9 | to_sendable prepares a List of structs or maps for transmission by converting structs
10 | to plain old maps (if a struct is passed in), and stripping out any fields
11 | in the allowed_fields list. If allowed_fields is empty, then all fields are
12 | sent.
13 | """
14 | def to_sendable(item) when is_list(item), do: to_sendable_list([], item)
15 | def to_sendable(%{__struct__: _} = struct) do
16 | to_sendable(Map.from_struct(struct))
17 | end
18 |
19 | defp to_sendable_list(sendable_items, []), do: sendable_items
20 | defp to_sendable_list(sendable_items, [item|remaining_items]) do
21 | to_sendable_list(sendable_items ++ [to_sendable(item)], remaining_items)
22 | end
23 | end
--------------------------------------------------------------------------------
/examples/hello_user/web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.PageController do
2 | use HelloUser.Web, :controller
3 |
4 | def index(conn, _params) do
5 | render conn, "index.html"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/examples/hello_user/web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.UserController do
2 | use HelloUser.Web, :controller
3 |
4 | import HelloUser.Controllers.ControllerHelper
5 |
6 | alias HelloUser.User
7 |
8 | def swaggerdoc_index, do: %{
9 | description: "Retrieve all Users",
10 | response_schema: %{"title" => "Users", "type": "array", "items": %{"$ref": "#/definitions/HelloUser.User"}},
11 | parameters: []
12 | }
13 | @spec index(Plug.Conn.t, [any]) :: Plug.Conn.t
14 | def index(conn, _params) do
15 | json conn, to_sendable(Repo.all(User))
16 | end
17 |
18 | def swaggerdoc_show, do: %{
19 | description: "Retrieve a specific User",
20 | response_schema: %{"$ref": "#/definitions/HelloUser.User"},
21 | parameters: [%{
22 | "name" => "id",
23 | "in" => "path",
24 | "description" => "User identifier",
25 | "required" => true,
26 | "type" => "integer"
27 | }]
28 | }
29 | @spec show(Plug.Conn.t, [any]) :: Plug.Conn.t
30 | def show(conn, %{"id" => id}) do
31 | json conn, to_sendable(Repo.get!(User, id))
32 | end
33 |
34 | def swaggerdoc_create, do: %{
35 | description: "Create a User" ,
36 | parameters: [%{
37 | "name" => "type",
38 | "in" => "body",
39 | "description" => "The new User",
40 | "required" => true,
41 | "schema": %{"$ref": "#/definitions/HelloUser.User"}
42 | }]
43 | }
44 | @spec create(Plug.Conn.t, [any]) :: Plug.Conn.t
45 | def create(conn, %{"user" => user_params}) do
46 | changeset = User.changeset(%User{}, user_params)
47 |
48 | case Repo.insert(changeset) do
49 | {:ok, _user} -> resp(conn, :created, "")
50 | {:error, changeset} ->
51 | conn
52 | |> put_status(:bad_request)
53 | |> json inspect(changeset.errors)
54 | end
55 | end
56 |
57 | def swaggerdoc_update, do: %{
58 | description: "Update a User" ,
59 | parameters: [%{
60 | "name" => "id",
61 | "in" => "path",
62 | "description" => "User identifier",
63 | "required" => true,
64 | "type" => "integer"
65 | },
66 | %{
67 | "name" => "type",
68 | "in" => "body",
69 | "description" => "The updated User",
70 | "required" => true,
71 | "schema": %{"$ref": "#/definitions/HelloUser.User"}
72 | }]
73 | }
74 | @spec update(Plug.Conn.t, [any]) :: Plug.Conn.t
75 | def update(conn, %{"id" => id, "user" => user_params}) do
76 | user = Repo.get!(User, id)
77 | changeset = User.changeset(user, user_params)
78 |
79 | case Repo.update(changeset) do
80 | {:ok, _user} -> resp(conn, :no_content, "")
81 | {:error, changeset} ->
82 | conn
83 | |> put_status(:bad_request)
84 | |> json inspect(changeset.errors)
85 | end
86 | end
87 |
88 | def swaggerdoc_delete, do: %{
89 | description: "Delete a User" ,
90 | parameters: [%{
91 | "name" => "id",
92 | "in" => "path",
93 | "description" => "User identifier",
94 | "required" => true,
95 | "type" => "integer"
96 | }]
97 | }
98 | @spec delete(Plug.Conn.t, [any]) :: Plug.Conn.t
99 | def delete(conn, %{"id" => id}) do
100 | user = Repo.get!(User, id)
101 |
102 | # Here we use delete! (with a bang) because we expect
103 | # it to always work (and if it does not, it will raise).
104 | Repo.delete!(user)
105 | resp(conn, :no_content, "")
106 | end
107 |
108 |
109 | def swaggerdoc_sync_user, do: %{
110 | description: "Synchronize a User to a 3rd-party system" ,
111 | parameters: [%{
112 | "name" => "id",
113 | "in" => "path",
114 | "description" => "Workflow identifier",
115 | "required" => true,
116 | "type" => "integer"
117 | },
118 | %{
119 | "name" => "force_sync",
120 | "in" => "body",
121 | "description" => "Force a synchronization of the user",
122 | "required" => false,
123 | "schema": %{
124 | "title" => "force_sync",
125 | "description" => "Force a synchronization of the user",
126 | "type" => "boolean"
127 | }
128 | },
129 | %{
130 | "name" => "foreign_system_id",
131 | "in" => "body",
132 | "description" => "Foreign User system identifier",
133 | "required" => true,
134 | "schema": %{
135 | "title" => "foreign_system_id",
136 | "description" => "Foreign User system identifier",
137 | "type" => "string"
138 | }
139 | }]
140 | }
141 | @spec sync_user(Plug.Conn.t, [any]) :: Plug.Conn.t
142 | def sync_user(conn, %{"id" => _id}) do
143 | #Take some sort of customized action, based on the params
144 | resp(conn, :no_content, "")
145 | end
146 |
147 | def swaggerdoc_get_custom_fields, do: %{
148 | description: "Retrieve all get_custom_fields associated with a User",
149 | response_schema: %{"title" => "User.CustomFields", "type": "array", "items": %{
150 | "title" => "User.CustomField",
151 | "description" => "A Custom Field",
152 | "type" => "object",
153 | "required" => ["key","value"],
154 | "properties" => %{
155 | "key" => %{"type" => "string", "description" => "The key for the custom field"},
156 | "value" => %{"type" => "string", "description" => "The value for the custom field"},
157 | }
158 | }},
159 | parameters: [%{
160 | "name" => "id",
161 | "in" => "path",
162 | "description" => "Workflow identifier",
163 | "required" => true,
164 | "type" => "integer"
165 | }]
166 | }
167 | @spec get_custom_fields(Plug.Conn.t, [any]) :: Plug.Conn.t
168 | def get_custom_fields(conn, %{"id" => _id}) do
169 | custom_fields = [%{key: "test1", value: "value1"}, %{key: "test2", value: "value2"}]
170 |
171 | json conn, custom_fields
172 | end
173 | end
174 |
--------------------------------------------------------------------------------
/examples/hello_user/web/models/user.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.User do
2 | use HelloUser.Web, :model
3 |
4 | schema "users" do
5 | field :name, :string
6 | field :email, :string
7 | field :bio, :string
8 | field :number_of_pets, :integer
9 |
10 | timestamps
11 | end
12 |
13 | @required_fields ~w(name email bio number_of_pets)
14 | @optional_fields ~w()
15 |
16 | @doc """
17 | Creates a changeset based on the `model` and `params`.
18 |
19 | If no params are provided, an invalid changeset is returned
20 | with no validation performed.
21 | """
22 | def changeset(model, params \\ :empty) do
23 | model
24 | |> cast(params, @required_fields, @optional_fields)
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/examples/hello_user/web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Router do
2 | use HelloUser.Web, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | plug :put_secure_browser_headers
10 | end
11 |
12 | pipeline :api do
13 | plug :accepts, ["json"]
14 | end
15 |
16 | scope "/", HelloUser do
17 | pipe_through :browser # Use the default browser stack
18 |
19 | get "/", PageController, :index
20 | end
21 |
22 | scope "/users", HelloUser do
23 | pipe_through :api
24 |
25 | get "/", UserController, :index
26 | post "/", UserController, :create
27 |
28 | get "/:id", UserController, :show
29 | put "/:id", UserController, :update
30 | delete "/:id", UserController, :destroy
31 |
32 | post "/:id/sync", UserController, :sync_user
33 | get "/:id/fields", UserController, :get_custom_fields
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/examples/hello_user/web/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/examples/hello_user/web/static/assets/favicon.ico
--------------------------------------------------------------------------------
/examples/hello_user/web/static/assets/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msawka/swaggerdoc/9512a1f56e9fa761cbf7caf1c08121aaf0acfb29/examples/hello_user/web/static/assets/images/phoenix.png
--------------------------------------------------------------------------------
/examples/hello_user/web/static/assets/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/examples/hello_user/web/static/js/app.js:
--------------------------------------------------------------------------------
1 | // Brunch automatically concatenates all files in your
2 | // watched paths. Those paths can be configured at
3 | // config.paths.watched in "brunch-config.js".
4 | //
5 | // However, those files will only be executed if
6 | // explicitly imported. The only exception are files
7 | // in vendor, which are never wrapped in imports and
8 | // therefore are always executed.
9 |
10 | // Import dependencies
11 | //
12 | // If you no longer want to use a dependency, remember
13 | // to also remove its path from "config.paths.watched".
14 | import "deps/phoenix_html/web/static/js/phoenix_html"
15 |
16 | // Import local files
17 | //
18 | // Local files can be imported directly using relative
19 | // paths "./socket" or full ones "web/static/js/socket".
20 |
21 | // import socket from "./socket"
22 |
--------------------------------------------------------------------------------
/examples/hello_user/web/static/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "web/static/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket
5 | // and connect at the socket path in "lib/my_app/endpoint.ex":
6 | import {Socket} from "deps/phoenix/web/static/js/phoenix"
7 |
8 | let socket = new Socket("/socket")
9 |
10 | // When you connect, you'll often need to authenticate the client.
11 | // For example, imagine you have an authentication plug, `MyAuth`,
12 | // which authenticates the session and assigns a `:current_user`.
13 | // If the current user exists you can assign the user's token in
14 | // the connection for use in the layout.
15 | //
16 | // In your "web/router.ex":
17 | //
18 | // pipeline :browser do
19 | // ...
20 | // plug MyAuth
21 | // plug :put_user_token
22 | // end
23 | //
24 | // defp put_user_token(conn, _) do
25 | // if current_user = conn.assigns[:current_user] do
26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
27 | // assign(conn, :user_token, token)
28 | // else
29 | // conn
30 | // end
31 | // end
32 | //
33 | // Now you need to pass this token to JavaScript. You can do so
34 | // inside a script tag in "web/templates/layout/app.html.eex":
35 | //
36 | //
37 | //
38 | // You will need to verify the user token in the "connect/2" function
39 | // in "web/channels/user_socket.ex":
40 | //
41 | // def connect(%{"token" => token}, socket) do
42 | // # max_age: 1209600 is equivalent to two weeks in seconds
43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
44 | // {:ok, user_id} ->
45 | // {:ok, assign(socket, :user, user_id)}
46 | // {:error, reason} ->
47 | // :error
48 | // end
49 | // end
50 | //
51 | // Finally, pass the token on connect as below. Or remove it
52 | // from connect if you don't care about authentication.
53 |
54 | socket.connect({token: window.userToken})
55 |
56 | // Now that you are connected, you can join channels with a topic:
57 | let channel = socket.channel("topic:subtopic", {})
58 | channel.join()
59 | .receive("ok", resp => { console.log("Joined succesffuly", resp) })
60 | .receive("error", resp => { console.log("Unabled to join", resp) })
61 |
62 | export default socket
63 |
--------------------------------------------------------------------------------
/examples/hello_user/web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Hello Phoenix!
11 | ">
12 |
13 |
14 |
15 |
16 |
22 |
23 |
<%= get_flash(@conn, :info) %>
24 |
<%= get_flash(@conn, :error) %>
25 |
26 | <%= @inner %>
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/hello_user/web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to Phoenix!
3 |
A productive web framework that does not compromise speed and maintainability.
4 |
5 |
6 |
37 |
--------------------------------------------------------------------------------
/examples/hello_user/web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.ErrorView do
2 | use HelloUser.Web, :view
3 |
4 | def render("404.html", _assigns) do
5 | "Page not found"
6 | end
7 |
8 | def render("500.html", _assigns) do
9 | "Server internal error"
10 | end
11 |
12 | # In case no render clause matches or no
13 | # template is found, let's render it as 500
14 | def template_not_found(_template, assigns) do
15 | render "500.html", assigns
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/examples/hello_user/web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.LayoutView do
2 | use HelloUser.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/examples/hello_user/web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.PageView do
2 | use HelloUser.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/examples/hello_user/web/views/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.UserView do
2 | use HelloUser.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/examples/hello_user/web/web.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloUser.Web do
2 | @moduledoc """
3 | A module that keeps using definitions for controllers,
4 | views and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use HelloUser.Web, :controller
9 | use HelloUser.Web, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below.
17 | """
18 |
19 | def model do
20 | quote do
21 | use Ecto.Model
22 | end
23 | end
24 |
25 | def controller do
26 | quote do
27 | use Phoenix.Controller
28 |
29 | alias HelloUser.Repo
30 | import Ecto.Model
31 | import Ecto.Query, only: [from: 1, from: 2]
32 |
33 | import HelloUser.Router.Helpers
34 | end
35 | end
36 |
37 | def view do
38 | quote do
39 | use Phoenix.View, root: "web/templates"
40 |
41 | # Import convenience functions from controllers
42 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
43 |
44 | # Use all HTML functionality (forms, tags, etc)
45 | use Phoenix.HTML
46 |
47 | import HelloUser.Router.Helpers
48 | end
49 | end
50 |
51 | def router do
52 | quote do
53 | use Phoenix.Router
54 | end
55 | end
56 |
57 | def channel do
58 | quote do
59 | use Phoenix.Channel
60 |
61 | alias HelloUser.Repo
62 | import Ecto.Model
63 | import Ecto.Query, only: [from: 1, from: 2]
64 |
65 | end
66 | end
67 |
68 | @doc """
69 | When used, dispatch to the appropriate controller/view/etc.
70 | """
71 | defmacro __using__(which) when is_atom(which) do
72 | apply(__MODULE__, which, [])
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/lib/mix/tasks/swagger.ex:
--------------------------------------------------------------------------------
1 | defmodule Mix.Tasks.Swagger do
2 | use Mix.Task
3 |
4 | @shortdoc "Generates Swagger JSON from Phoenix routes and Ecto models"
5 |
6 | @moduledoc """
7 | To use swaggerdoc with your projects, edit your mix.exs file and add it as a dependency:
8 |
9 | ```elixir
10 | defp deps do
11 | [{:swaggerdoc, "~> 0.0.1"}]
12 | end
13 | ```
14 |
15 | To execute the Mix task, simply type `mix swagger`:
16 |
17 | ```elixir
18 | hello_user$ mix swagger
19 | Generating Swagger documentation...
20 | Adding Ecto definitions...
21 | Adding Phoenix Routes...
22 | Writing JSON to file...
23 | Finished generating Swagger documentation!
24 | ```
25 |
26 | To view the generated Swagger in [swagger-ui](https://github.com/swagger-api/swagger-ui):
27 |
28 | * In a temp folder, execute a git clone of https://github.com/swagger-api/swagger-ui.git
29 | * In the browser of your choice, open the file *temp folder*/swagger-ui/dist/index.html
30 | * In the JSON API input box at the top of the page, paste in the link to the JSON
31 | * Hit the 'Explore' button
32 |
33 | For a complete example, please see the [examples](https://github.com/OpenAperture/swaggerdoc/tree/master/examples) section.
34 | """
35 |
36 | @doc """
37 | Mix entrypoint method
38 | """
39 | @spec run([any]) :: no_return
40 | def run(args) do
41 | Mix.Task.run "compile", args
42 | generate_docs(args)
43 | end
44 |
45 | defp generate_docs(args) do
46 | Mix.Task.run "compile", args
47 |
48 | try do
49 | Mix.shell.info "Generating Swagger documentation..."
50 |
51 | Mix.shell.info "Adding Ecto definitions..."
52 | swagger_json = Map.put(__MODULE__.app_json, :definitions, __MODULE__.build_definitions(:code.all_loaded, %{}))
53 |
54 | Mix.shell.info "Adding Phoenix Routes..."
55 | swagger_json = __MODULE__.add_routes(__MODULE__.get_router(args).__routes__, swagger_json)
56 |
57 | Mix.shell.info "Writing JSON to file..."
58 | output_path = Application.get_env(:swaggerdoc, :output_path, System.cwd!() <> "/swagger")
59 | File.mkdir_p!(output_path)
60 |
61 | output_file = Application.get_env(:swaggerdoc, :output_file, "api.json")
62 | File.write!("#{output_path}/#{output_file}", Poison.encode!(swagger_json))
63 | Mix.shell.info "Finished generating Swagger documentation!"
64 | catch
65 | :exit, code ->
66 | Mix.shell.error "Failed to generate Swagger documentation: Exited with code #{inspect code}"
67 | Mix.shell.error Exception.format_stacktrace(System.stacktrace)
68 | :throw, value ->
69 | Mix.shell.error "Failed to generate Swagger documentation: Throw called with #{inspect value}"
70 | Mix.shell.error Exception.format_stacktrace(System.stacktrace)
71 | what, value ->
72 | Mix.shell.error "Failed to generate Swagger documentation: Caught #{inspect what} with #{inspect value}"
73 | Mix.shell.error Exception.format_stacktrace(System.stacktrace)
74 | end
75 | end
76 |
77 | @doc """
78 | Contains the application-specific JSON that forms the base of the Swagger JSON
79 | """
80 | @spec app_json :: map
81 | def app_json, do: %{
82 | swagger: Application.get_env(:swaggerdoc, :swagger_version, "2.0"),
83 | info: %{
84 | version: Application.get_env(:swaggerdoc, :project_version, ""),
85 | title: Application.get_env(:swaggerdoc, :project_name, ""),
86 | description: Application.get_env(:swaggerdoc, :project_desc, ""),
87 | termsOfService: Application.get_env(:swaggerdoc, :project_terms, ""),
88 | contact: %{
89 | name: Application.get_env(:swaggerdoc, :project_contact_name, ""),
90 | email: Application.get_env(:swaggerdoc, :project_contact_email, ""),
91 | url: Application.get_env(:swaggerdoc, :project_contact_url, ""),
92 | },
93 | license: %{
94 | name: Application.get_env(:swaggerdoc, :project_license_name, ""),
95 | url: Application.get_env(:swaggerdoc, :project_license_url, ""),
96 | }
97 | },
98 | host: Application.get_env(:swaggerdoc, :host, ""),
99 | basePath: Application.get_env(:swaggerdoc, :base_path, ""),
100 | schemes: Application.get_env(:swaggerdoc, :schemes, ["http"]),
101 | consumes: Application.get_env(:swaggerdoc, :consumes, []),
102 | produces: Application.get_env(:swaggerdoc, :produces, []),
103 | definitions: [],
104 | paths: %{}
105 | }
106 |
107 | @doc """
108 | Method to return the Phoenix router, based on args or configuration
109 | """
110 | @spec get_router([any]) :: term
111 | def get_router(args) do
112 | cond do
113 | args != nil && length(args) > 0 -> Module.concat("Elixir", Enum.at(args, 0))
114 | Mix.Project.umbrella? -> Mix.raise "Umbrella applications require an explicit router to be given to Phoenix.routes"
115 | true -> Module.concat(Mix.Phoenix.base(), "Router")
116 | end
117 | end
118 |
119 | @doc """
120 | Method to add Phoenix routes to the Swagger map
121 | """
122 | @spec add_routes(list, map) :: map
123 | def add_routes(nil, swagger), do: swagger
124 | def add_routes([], swagger), do: swagger
125 | def add_routes([route | remaining_routes], swagger) do
126 | pipe_through = Application.get_env(:swaggerdoc, :pipe_through, nil)
127 | if pipe_through && route.pipe_through != pipe_through do
128 | add_routes(remaining_routes, swagger)
129 | else
130 | swagger_path = path_from_route(String.split(route.path, "/"), nil)
131 |
132 | path = swagger[:paths][swagger_path] || %{}
133 |
134 | func_name = "swaggerdoc_#{route.opts}"
135 | verb = if route.plug != nil && Keyword.has_key?(route.plug.__info__(:functions), String.to_atom(func_name)) do
136 | apply(route.plug, String.to_atom(func_name), [])
137 | else
138 | parse_default_verb(route.path)
139 | end
140 |
141 | verb_string = String.downcase("#{route.verb}")
142 |
143 | response_schema = verb[:response_schema]
144 | verb = verb
145 | |> Map.delete(:response_schema)
146 | |> Map.put(:responses, verb[:responses] || default_responses(verb_string, response_schema))
147 | |> Map.put(:produces, verb[:produces] || Application.get_env(:swaggerdoc, :produces, []))
148 | |> Map.put(:operationId, verb[:operationId] || "#{route.opts}")
149 | |> Map.put(:description, verb[:description] || "")
150 |
151 | path = Map.put(path, verb_string, verb)
152 | paths = Map.put(swagger[:paths], swagger_path, path)
153 | add_routes(remaining_routes, Map.put(swagger, :paths, paths))
154 | end
155 | end
156 |
157 | @doc """
158 | Method to add a specific path from the Phoenix routes to the Swagger map. Paths must enclose params with braces {var},
159 | rather than :var (http://swagger.io/specification/#pathTemplating)
160 | """
161 | @spec path_from_route(list, map) :: map
162 | def path_from_route([], swagger_path), do: swagger_path
163 | def path_from_route([path_segment | remaining_segments], swagger_path) do
164 | path_from_route(remaining_segments, cond do
165 | path_segment == nil || String.length(path_segment) == 0 -> swagger_path
166 | swagger_path == nil -> "/#{path_segment}"
167 | String.first(path_segment) == ":" -> "#{swagger_path}/{#{String.slice(path_segment, 1..String.length(path_segment))}}"
168 | true -> "#{swagger_path}/#{path_segment}"
169 | end)
170 | end
171 |
172 | @doc """
173 | Method to build the default Swagger verb map, if not specified by the developer
174 | """
175 | @spec parse_default_verb(String.t) :: map
176 | def parse_default_verb(path) do
177 | parameters = Enum.reduce String.split(path, "/"), [], fn(path_segment, parameters) ->
178 | if String.first(path_segment) == ":" do
179 |
180 | #http://swagger.io/specification/#parameterObject
181 | name = String.slice(path_segment, 1..String.length(path_segment))
182 | #assumes all params named "id" are integers
183 | type = if name == "id", do: "integer", else: "string"
184 | parameter = %{
185 | "name" => name,
186 | "in" => "path",
187 | "description" => "",
188 | "required" => true,
189 | "type" => type
190 | }
191 |
192 | parameters ++ [parameter]
193 | else
194 | parameters
195 | end
196 | end
197 |
198 | %{
199 | parameters: parameters,
200 | }
201 | end
202 |
203 | @doc """
204 | Method to build the default Swagger response map for a specific verb, if not specified by the developer
205 | """
206 | @spec default_responses(String.t, any) :: map
207 | def default_responses(verb_string, response_schema \\ nil) do
208 | %{
209 | "404" => %{"description" => "Resource not found"},
210 | "401" => %{"description" => "Request is not authorized"},
211 | "500" => %{"description" => "Internal Server Error"} }
212 | |> Map.merge(
213 | case verb_string do
214 | "get" ->
215 | %{"200" =>
216 | %{"description" => "Resource Content"}
217 | |> Map.merge(if !is_nil(response_schema), do: %{"schema" => response_schema}, else: %{})}
218 | "delete" ->
219 | %{"204" => %{"description" => "No Content"}}
220 | "post" ->
221 | %{"201" => %{"description" => "Resource created"},
222 | "400" => %{"description" => "Request contains bad values"}}
223 | "put" ->
224 | %{"204" => %{"description" => "No Content"},
225 | "400" => %{"description" => "Request contains bad values"}}
226 | _ ->
227 | %{}
228 | end
229 | )
230 | end
231 |
232 | @doc """
233 | Method to build the Swagger definitions from Ecto models
234 | """
235 | @spec build_definitions(list, map) :: map
236 | def build_definitions([], def_json), do: def_json
237 | def build_definitions([code_def | remaining_defs], def_json) do
238 | module = elem(code_def, 0)
239 | def_json = if :erlang.function_exported(module, :__schema__, 1) do
240 | properties_json = Enum.reduce module.__schema__(:types), %{}, fn(type, properties_json) ->
241 | Map.put(properties_json, "#{elem(type, 0)}", convert_property_type(elem(type, 1)))
242 | end
243 |
244 | module_json = %{"properties" => properties_json}
245 | |> Map.merge(
246 | if :erlang.function_exported(module, :changeset, 2) do
247 | module_struct = module.changeset(module.__struct__, %{})
248 | required = required_fields module_struct.errors
249 | %{"required" => required}
250 | else
251 | %{}
252 | end
253 | )
254 |
255 | Map.put(def_json, "#{inspect module}", module_json)
256 | else
257 | def_json
258 | end
259 |
260 | build_definitions(remaining_defs, def_json)
261 | end
262 |
263 | @doc """
264 | Method to convert an Ecto schema type (https://github.com/elixir-lang/ecto/blob/v1.0.0/lib/ecto/schema.ex#L107-L145)
265 | into a Swagger property type (http://swagger.io/specification/#dataTypeType)
266 | """
267 | @spec convert_property_type(term) :: map
268 | def convert_property_type(type) do
269 | case type do
270 | :id -> %{"type" => "integer", "format" => "int64"}
271 | :binary_id -> %{"type" => "string", "format" => "binary"}
272 | :integer -> %{"type" => "integer", "format" => "int64"}
273 | :float -> %{"type" => "number", "format" => "float"}
274 | :boolean -> %{"type" => "boolean"}
275 | :string -> %{"type" => "string"}
276 | :binary -> %{"type" => "string", "format" => "binary"}
277 | Ecto.DateTime -> %{"type" => "string", "format" => "date-time"}
278 | Ecto.Date -> %{"type" => "string", "format" => "date"}
279 | Ecto.Time -> %{"type" => "string", "format" => "date-time"}
280 | :uuid -> %{"type" => "string"}
281 | _ -> %{"type" => "string"}
282 | end
283 | end
284 |
285 | def required_fields([]), do: []
286 |
287 | def required_fields([head|tail]) do
288 | {key, _msg} = head
289 | [to_string(key)|required_fields(tail)]
290 | end
291 | end
292 |
--------------------------------------------------------------------------------
/lib/swaggerdoc.ex:
--------------------------------------------------------------------------------
1 | defmodule SwaggerDoc do
2 | @moduledoc """
3 | The SwaggerDoc module provides a convenience task for generating [Swagger](http://swagger.io/)
4 | API documentation for Phoenix and Ecto-based projects.
5 | """
6 | end
7 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule SwaggerDoc.Mixfile do
2 | use Mix.Project
3 |
4 | @version "0.0.1"
5 |
6 | def project do
7 | [
8 | app: :swaggerdoc,
9 | version: @version,
10 | elixir: "~> 1.0",
11 | build_embedded: Mix.env == :prod,
12 | start_permanent: Mix.env == :prod,
13 | deps: deps(),
14 | description: description(),
15 | package: package(),
16 | docs: [
17 | readme: "README.md",
18 | main: "README",
19 | source_url: "https://github.com/OpenAperture/swaggerdoc",
20 | #source_ref: "v#{@version}"
21 | ],
22 | name: "swaggerdoc",
23 | source_url: "https://github.com/OpenAperture/swaggerdoc",
24 | homepage_url: "https://openaperture.io/"
25 | ]
26 | end
27 |
28 | # Configuration for the OTP application
29 | #
30 | # Type `mix help compile.app` for more information
31 | def application do
32 | [applications: [:logger]]
33 | end
34 |
35 | # Dependencies can be Hex packages:
36 | #
37 | # {:mydep, "~> 0.3.0"}
38 | #
39 | # Or git/path repositories:
40 | #
41 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
42 | #
43 | # Type `mix help deps` for more examples and options
44 | defp deps do
45 | [
46 | {:phoenix, "~> 1.2"},
47 | {:ecto, "~> 2.0"},
48 | {:poison, "~> 2.0"},
49 | {:ex_doc, "~> 0.8.4", only: :docs},
50 | {:earmark, "~> 0.1.17", only: :docs},
51 | {:meck, "~> 0.8.3", only: :test},
52 | ]
53 | end
54 |
55 | defp description do
56 | "The SwaggerDoc module provides a convenience task for generating Swagger API documentation for Phoenix and Ecto-based projects."
57 | end
58 |
59 | defp package do
60 | [
61 | contributors: ["Matt Sawka"],
62 | licenses: ["MPL 2.0"],
63 | links: %{"GitHub" => "https://github.com/OpenAperture/swaggerdoc.git"},
64 | files: ~w(lib) ++
65 | ~w(LICENSE mix.exs README.md)
66 | ]
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"decimal": {:hex, :decimal, "1.2.0", "462960fd71af282e570f7b477f6be56bf8968e68277d4d0b641a635269bf4b0d", [:mix], []},
2 | "earmark": {:hex, :earmark, "0.1.17", "a2269e72ff85501bdb58c2de9edc0a9a17a4be2757883eed1f601b30494ed2bf", [:mix], []},
3 | "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]},
4 | "ex_doc": {:hex, :ex_doc, "0.8.4", "c74a30b09627ff22a2bb7f75d3b75dec3aedb2bd434bb3009a73a40425c2315d", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]},
5 | "meck": {:hex, :meck, "0.8.3", "4628a1334c69610c5bd558b04dc78d723d8ec5445c123856de34c77f462b5ee5", [:rebar], []},
6 | "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []},
7 | "phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
8 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
9 | "plug": {:hex, :plug, "1.2.2", "cfbda521b54c92ab8ddffb173fbaabed8d8fc94bec07cd9bb58a84c1c501b0bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
10 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
11 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}}
12 |
--------------------------------------------------------------------------------
/test/mix/tasks/swagger_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Mocks.DefaultPlug do
2 | def swaggerdoc_index do
3 | %{
4 | responses: %{}
5 | }
6 | end
7 | end
8 |
9 | defmodule Mocks.UserModel do
10 | use Ecto.Schema
11 |
12 | schema "users" do
13 | field :name, :string
14 | field :email, :string
15 | field :bio, :string
16 | field :number_of_pets, :integer
17 |
18 | timestamps()
19 | end
20 | end
21 |
22 | defmodule Mocks.UserRequiredModel do
23 | use Ecto.Schema
24 | import Ecto.Changeset
25 |
26 | schema "users" do
27 | field :name, :string
28 | field :email, :string
29 | field :bio, :string
30 | field :number_of_pets, :integer
31 |
32 | timestamps()
33 | end
34 |
35 | @required_fields ~w(name email)a
36 | @optional_fields ~w(bio number_of_pets)a
37 |
38 | def changeset(struct, params \\ %{}) do
39 | struct
40 | |> cast(params, @required_fields ++ @optional_fields)
41 | |> validate_required(@required_fields)
42 | end
43 |
44 | end
45 |
46 | defmodule Mocks.SimpleRouter do
47 | def __routes__, do: []
48 | end
49 |
50 | defmodule Mix.Tasks.Swagger.Tests do
51 | use ExUnit.Case
52 | use Plug.Test
53 |
54 | alias Mix.Tasks.Swagger
55 | alias Phoenix.Router.Route, as: PhoenixRoute
56 |
57 | def reset_swagger_env do
58 | Application.delete_env(:swaggerdoc, :swagger_version)
59 | Application.delete_env(:swaggerdoc, :project_version)
60 | Application.delete_env(:swaggerdoc, :project_name)
61 | Application.delete_env(:swaggerdoc, :project_desc)
62 | Application.delete_env(:swaggerdoc, :project_terms)
63 | Application.delete_env(:swaggerdoc, :project_contact_name)
64 | Application.delete_env(:swaggerdoc, :project_contact_email)
65 | Application.delete_env(:swaggerdoc, :project_contact_url)
66 | Application.delete_env(:swaggerdoc, :project_license_name)
67 | Application.delete_env(:swaggerdoc, :project_license_url)
68 | Application.delete_env(:swaggerdoc, :host)
69 | Application.delete_env(:swaggerdoc, :base_path)
70 | Application.delete_env(:swaggerdoc, :schemes)
71 | Application.delete_env(:swaggerdoc, :consumes)
72 | Application.delete_env(:swaggerdoc, :produces)
73 | Application.delete_env(:swaggerdoc, :pipe_through)
74 | end
75 |
76 | setup do
77 | reset_swagger_env()
78 | :ok
79 | end
80 |
81 | setup_all do
82 | on_exit fn ->
83 | reset_swagger_env()
84 | end
85 | :ok
86 | end
87 |
88 | #==============================
89 | # app_json tests
90 |
91 | test "app_json - default values" do
92 | default_json = Swagger.app_json
93 | assert default_json != nil
94 | assert default_json[:swagger] == "2.0"
95 | assert default_json[:info][:version] == ""
96 | assert default_json[:info][:title] == ""
97 | assert default_json[:info][:description] == ""
98 | assert default_json[:info][:termsOfService] == ""
99 | assert default_json[:info][:contact][:name] == ""
100 | assert default_json[:info][:contact][:email] == ""
101 | assert default_json[:info][:contact][:url] == ""
102 | assert default_json[:info][:license][:name] == ""
103 | assert default_json[:info][:license][:url] == ""
104 | assert default_json[:host] == ""
105 | assert default_json[:basePath] == ""
106 | assert default_json[:schemes] == ["http"]
107 | assert default_json[:consumes] == []
108 | assert default_json[:produces] == []
109 | assert default_json[:definitions] == []
110 | assert default_json[:paths] == %{}
111 | end
112 |
113 | test "app_json - override values" do
114 | Application.put_env(:swaggerdoc, :swagger_version, "3.0")
115 | Application.put_env(:swaggerdoc, :project_version, "123")
116 | Application.put_env(:swaggerdoc, :project_name, "test name")
117 | Application.put_env(:swaggerdoc, :project_desc, "testing")
118 | Application.put_env(:swaggerdoc, :project_terms, "testing terms")
119 | Application.put_env(:swaggerdoc, :project_contact_name, "last, first")
120 | Application.put_env(:swaggerdoc, :project_contact_email, "first.last@somewhere.com")
121 | Application.put_env(:swaggerdoc, :project_contact_url, "http://somewhere")
122 | Application.put_env(:swaggerdoc, :project_license_name, "license")
123 | Application.put_env(:swaggerdoc, :project_license_url, "http://somewhere/license")
124 | Application.put_env(:swaggerdoc, :host, "hostname")
125 | Application.put_env(:swaggerdoc, :base_path, "/")
126 | Application.put_env(:swaggerdoc, :schemes, ["http", "https"])
127 | Application.put_env(:swaggerdoc, :consumes, ["application/json"])
128 | Application.put_env(:swaggerdoc, :produces, ["application/json"])
129 |
130 | default_json = Swagger.app_json
131 | assert default_json != nil
132 | assert default_json[:swagger] == "3.0"
133 | assert default_json[:info][:version] == "123"
134 | assert default_json[:info][:title] == "test name"
135 | assert default_json[:info][:description] == "testing"
136 | assert default_json[:info][:termsOfService] == "testing terms"
137 | assert default_json[:info][:contact][:name] == "last, first"
138 | assert default_json[:info][:contact][:email] == "first.last@somewhere.com"
139 | assert default_json[:info][:contact][:url] == "http://somewhere"
140 | assert default_json[:info][:license][:name] == "license"
141 | assert default_json[:info][:license][:url] == "http://somewhere/license"
142 | assert default_json[:host] == "hostname"
143 | assert default_json[:basePath] == "/"
144 | assert default_json[:schemes] == ["http", "https"]
145 | assert default_json[:consumes] == ["application/json"]
146 | assert default_json[:produces] == ["application/json"]
147 | assert default_json[:definitions] == []
148 | assert default_json[:paths] == %{}
149 | end
150 |
151 | #==============================
152 | # get_router tests
153 |
154 | test "get_router - nil args" do
155 | assert Swagger.get_router(nil) == Swaggerdoc.Router
156 | end
157 |
158 | test "get_router - empty args" do
159 | assert Swagger.get_router([]) == Swaggerdoc.Router
160 | end
161 |
162 | #==============================
163 | # add_routes tests
164 |
165 | test "add_routes - nil routes" do
166 | assert Swagger.add_routes(nil, %{}) == %{}
167 | end
168 |
169 | test "add_routes - empty routes" do
170 | assert Swagger.add_routes([], %{}) == %{}
171 | end
172 |
173 | test "add_routes - route" do
174 | route = %PhoenixRoute{
175 | path: "/test",
176 | opts: :index,
177 | verb: "GET"
178 | }
179 | assert Swagger.add_routes([route], %{paths: %{}}) == %{
180 | paths: %{"/test" =>
181 | %{"get" => %{
182 | description: "",
183 | operationId: "index",
184 | parameters: [],
185 | produces: [],
186 | responses: %{
187 | "200" => %{"description" => "Resource Content"},
188 | "401" => %{"description" => "Request is not authorized"}, "404" => %{"description" => "Resource not found"},
189 | "500" => %{"description" => "Internal Server Error"}
190 | }
191 | }}
192 | }}
193 | end
194 |
195 | test "add_routes - route with default template param" do
196 | route = %PhoenixRoute{
197 | path: "/testing/:id",
198 | opts: :index,
199 | verb: "GET"
200 | }
201 | assert Swagger.add_routes([route], %{paths: %{}}) == %{
202 | paths: %{"/testing/{id}" =>
203 | %{"get" => %{
204 | description: "",
205 | operationId: "index",
206 | parameters: [%{"description" => "", "in" => "path", "name" => "id", "required" => true, "type" => "integer"}],
207 | produces: [],
208 | responses: %{
209 | "200" => %{"description" => "Resource Content"},
210 | "401" => %{"description" => "Request is not authorized"}, "404" => %{"description" => "Resource not found"},
211 | "500" => %{"description" => "Internal Server Error"}
212 | }
213 | }}
214 | }}
215 | end
216 |
217 | test "add_routes - route with select pipe_through" do
218 | Application.put_env(:swaggerdoc, :pipe_through, [:api])
219 | route = [%PhoenixRoute{
220 | path: "/testing/:id",
221 | opts: :index,
222 | verb: "GET"
223 | },%PhoenixRoute{
224 | path: "/api/v1/testing/:id",
225 | opts: :index,
226 | verb: "GET",
227 | pipe_through: [:api],
228 | }]
229 | assert Swagger.add_routes(route, %{paths: %{}}) == %{
230 | paths: %{"/api/v1/testing/{id}" =>
231 | %{"get" => %{
232 | description: "",
233 | operationId: "index",
234 | parameters: [%{"description" => "", "in" => "path", "name" => "id", "required" => true, "type" => "integer"}],
235 | produces: [],
236 | responses: %{
237 | "200" => %{"description" => "Resource Content"},
238 | "401" => %{"description" => "Request is not authorized"}, "404" => %{"description" => "Resource not found"},
239 | "500" => %{"description" => "Internal Server Error"}
240 | }
241 | }}
242 | }}
243 | Application.delete_env(:swaggerdoc, :pipe_through)
244 | end
245 |
246 | test "add_routes - route from custom plug" do
247 | route = %PhoenixRoute{
248 | path: "/test",
249 | opts: :index,
250 | verb: "GET",
251 | plug: Mocks.DefaultPlug
252 | }
253 | assert Swagger.add_routes([route], %{paths: %{}}) == %{
254 | paths: %{"/test" =>
255 | %{"get" => %{
256 | description: "",
257 | operationId: "index",
258 | produces: [],
259 | responses: %{}
260 | }}
261 | }}
262 | end
263 |
264 | #==============================
265 | # path_from_route tests
266 |
267 | test "path_from_route - empty path segments" do
268 | assert Swagger.path_from_route([], nil) == nil
269 | end
270 |
271 | test "path_from_route - no templates" do
272 | assert Swagger.path_from_route(["testing"], nil) == "/testing"
273 | end
274 |
275 | test "path_from_route - templates" do
276 | assert Swagger.path_from_route(["testing", ":id"], nil) == "/testing/{id}"
277 | end
278 |
279 | #==============================
280 | # parse_default_verb tests
281 |
282 | test "parse_default_verb - no templates" do
283 | assert Swagger.parse_default_verb("/testing") == %{parameters: []}
284 | end
285 |
286 | test "parse_default_verb - id segment" do
287 | assert Swagger.parse_default_verb("/testing/:id") == %{parameters: [%{"description" => "", "in" => "path", "name" => "id", "required" => true, "type" => "integer"}]}
288 | end
289 |
290 | test "parse_default_verb - name segment" do
291 | assert Swagger.parse_default_verb("/testing/:name") == %{parameters: [%{"description" => "", "in" => "path", "name" => "name", "required" => true, "type" => "string"}]}
292 | end
293 |
294 | #==============================
295 | # default_responses tests
296 |
297 | test "default_responses - unknown verb" do
298 | assert Swagger.default_responses("junk") == %{
299 | "404" => %{"description" => "Resource not found"},
300 | "401" => %{"description" => "Request is not authorized"},
301 | "500" => %{"description" => "Internal Server Error"}
302 | }
303 | end
304 |
305 | test "default_responses - get without schema" do
306 | assert Swagger.default_responses("get") == %{
307 | "404" => %{"description" => "Resource not found"},
308 | "401" => %{"description" => "Request is not authorized"},
309 | "500" => %{"description" => "Internal Server Error"},
310 | "200" => %{"description" => "Resource Content"}
311 | }
312 | end
313 |
314 | test "default_responses - get with schema" do
315 | assert Swagger.default_responses("get", %{}) == %{
316 | "404" => %{"description" => "Resource not found"},
317 | "401" => %{"description" => "Request is not authorized"},
318 | "500" => %{"description" => "Internal Server Error"},
319 | "200" => %{"description" => "Resource Content", "schema" => %{}}
320 | }
321 | end
322 |
323 | test "default_responses - delete" do
324 | assert Swagger.default_responses("delete") == %{
325 | "404" => %{"description" => "Resource not found"},
326 | "401" => %{"description" => "Request is not authorized"},
327 | "500" => %{"description" => "Internal Server Error"},
328 | "204" => %{"description" => "No Content"}
329 | }
330 | end
331 |
332 | test "default_responses - post" do
333 | assert Swagger.default_responses("post") == %{
334 | "404" => %{"description" => "Resource not found"},
335 | "401" => %{"description" => "Request is not authorized"},
336 | "500" => %{"description" => "Internal Server Error"},
337 | "201" => %{"description" => "Resource created"},
338 | "400" => %{"description" => "Request contains bad values"}
339 | }
340 | end
341 |
342 | test "default_responses - put" do
343 | assert Swagger.default_responses("put") == %{
344 | "404" => %{"description" => "Resource not found"},
345 | "401" => %{"description" => "Request is not authorized"},
346 | "500" => %{"description" => "Internal Server Error"},
347 | "204" => %{"description" => "No Content"},
348 | "400" => %{"description" => "Request contains bad values"}
349 | }
350 | end
351 |
352 | #==============================
353 | # build_definitions tests
354 |
355 | test "build_definitions - no models" do
356 | assert Swagger.build_definitions([], %{}) == %{}
357 | end
358 |
359 | test "build_definitions - modules but no models" do
360 | assert Swagger.build_definitions([{Mocks.DefaultPlug, ""}], %{}) == %{}
361 | end
362 |
363 | #==============================
364 | # required_fields tests
365 |
366 | test "required_fields - parse errors from struct, if errors is empty" do
367 | assert Swagger.required_fields([]) == []
368 | end
369 |
370 | test "required_fields - parse errors from struct" do
371 | assert Swagger.required_fields([name: "can't be blank", email: "can't be blank"]) == ["name", "email"]
372 | end
373 |
374 | test "build_definitions - model" do
375 | assert Swagger.build_definitions([{Mocks.UserModel, ""}], %{}) == %{
376 | "Mocks.UserModel" => %{
377 | "properties" => %{
378 | "bio" => %{"type" => "string"},
379 | "email" => %{"type" => "string"},
380 | "id" => %{"format" => "int64", "type" => "integer"},
381 | "inserted_at" => %{"format" => "date-time", "type" => "string"},
382 | "name" => %{"type" => "string"},
383 | "number_of_pets" => %{"format" => "int64", "type" => "integer"},
384 | "updated_at" => %{"format" => "date-time", "type" => "string"}}
385 | }
386 | }
387 | end
388 |
389 | test "build_definitions - model support changeset (required_fields)" do
390 | assert Swagger.build_definitions([{Mocks.UserRequiredModel, ""}], %{}) == %{
391 | "Mocks.UserRequiredModel" => %{
392 | "properties" => %{
393 | "bio" => %{"type" => "string"},
394 | "email" => %{"type" => "string"},
395 | "id" => %{"format" => "int64", "type" => "integer"},
396 | "inserted_at" => %{"format" => "date-time", "type" => "string"},
397 | "name" => %{"type" => "string"},
398 | "number_of_pets" => %{"format" => "int64", "type" => "integer"},
399 | "updated_at" => %{"format" => "date-time", "type" => "string"}
400 | },
401 | "required" => ["name", "email"]
402 | }
403 | }
404 | end
405 |
406 | #==============================
407 | # convert_property_type tests
408 |
409 | test "convert_property_type - :id" do
410 | assert Swagger.convert_property_type(:id) == %{"type" => "integer", "format" => "int64"}
411 | end
412 |
413 | test "convert_property_type - :binary_id" do
414 | assert Swagger.convert_property_type(:binary_id) == %{"type" => "string", "format" => "binary"}
415 | end
416 |
417 | test "convert_property_type - :integer" do
418 | assert Swagger.convert_property_type(:integer) == %{"type" => "integer", "format" => "int64"}
419 | end
420 |
421 | test "convert_property_type - :float" do
422 | assert Swagger.convert_property_type(:float) == %{"type" => "number", "format" => "float"}
423 | end
424 |
425 | test "convert_property_type - :boolean" do
426 | assert Swagger.convert_property_type(:boolean) == %{"type" => "boolean"}
427 | end
428 |
429 | test "convert_property_type - :string" do
430 | assert Swagger.convert_property_type(:string) == %{"type" => "string"}
431 | end
432 |
433 | test "convert_property_type - :Ecto.DateTime " do
434 | assert Swagger.convert_property_type(Ecto.DateTime ) == %{"type" => "string", "format" => "date-time"}
435 | end
436 |
437 | test "convert_property_type - :Ecto.Date" do
438 | assert Swagger.convert_property_type(Ecto.Date) ==%{"type" => "string", "format" => "date"}
439 | end
440 |
441 | test "convert_property_type - :Ecto.Time" do
442 | assert Swagger.convert_property_type(Ecto.Time) == %{"type" => "string", "format" => "date-time"}
443 | end
444 |
445 | test "convert_property_type - :uuid" do
446 | assert Swagger.convert_property_type(:uuid) == %{"type" => "string"}
447 | end
448 |
449 | test "convert_property_type - :unknown" do
450 | assert Swagger.convert_property_type(:unknown) == %{"type" => "string"}
451 | end
452 |
453 | #==============================
454 | # run tests
455 |
456 | test "run" do
457 | :meck.new(Swagger, [:passthrough])
458 | :meck.expect(Swagger, :get_router, fn _ -> Mocks.SimpleRouter end)
459 | :meck.expect(Swagger, :add_routes, fn _,_ -> %{} end)
460 |
461 | assert Swagger.run(nil) == :ok
462 | after
463 | :meck.unload
464 | end
465 |
466 | test "run raise exception" do
467 | :meck.new(Swagger, [:passthrough])
468 | :meck.expect(Swagger, :get_router, fn _ -> Mocks.SimpleRouter end)
469 | :meck.expect(Swagger, :add_routes, fn _,_ -> raise "bad news bears" end)
470 |
471 | assert Swagger.run(nil) == :ok
472 | after
473 | :meck.unload
474 | end
475 | end
476 |
--------------------------------------------------------------------------------
/test/swaggerdoc_test.exs:
--------------------------------------------------------------------------------
1 | defmodule SwaggerDocTest do
2 | use ExUnit.Case
3 |
4 | test "the truth" do
5 | assert 1 + 1 == 2
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------