├── .gitignore
├── Appraisals
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── certs
└── gem-public_cert.pem
├── lib
├── swagger
│ ├── docs.rb
│ └── docs
│ │ ├── api_declaration_file.rb
│ │ ├── api_declaration_file_metadata.rb
│ │ ├── config.rb
│ │ ├── dsl.rb
│ │ ├── generator.rb
│ │ ├── impotent_methods.rb
│ │ ├── methods.rb
│ │ ├── task.rb
│ │ └── version.rb
└── tasks
│ └── swagger.rake
├── spec
├── fixtures
│ └── controllers
│ │ ├── application_controller.rb
│ │ ├── custom_resource_path_controller.rb
│ │ ├── ignored_controller.rb
│ │ ├── multiple_routes_controller.rb
│ │ ├── nested_controller.rb
│ │ └── sample_controller.rb
├── lib
│ └── swagger
│ │ └── docs
│ │ ├── api_declaration_file_metadata_spec.rb
│ │ ├── api_declaration_file_spec.rb
│ │ ├── config_spec.rb
│ │ ├── dsl_spec.rb
│ │ ├── generator_spec.rb
│ │ └── methods.rb
└── spec_helper.rb
└── swagger-docs.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | public
19 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rails-3" do
2 | gem "rails", "~> 3.2.16"
3 | end
4 |
5 | appraise "rails-4" do
6 | gem "rails", "~> 4.0.0"
7 | end
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.2.9
2 |
3 | Fix gem dependencies to support Rails 5 #141
4 |
5 | ## 0.2.8
6 |
7 | Accept success status (#134) - thanks to @dcarneiro
8 |
9 | ## 0.2.7
10 |
11 | - Fix issue "NoMethodError: undefined method `<' for false:FalseClass" (#133) - thanks to @heaven
12 |
13 | ## 0.2.6
14 |
15 | - swagger_controller DSL can accept a resource_path which will be used over a generated path #126 @sb8244
16 |
17 | ## 0.2.5
18 |
19 | - Enabled option to set 'items' inside swagger_api method #99 @krakatoa
20 |
21 | ## 0.2.4
22 |
23 | - Parent controller option for register_apis config. #123 @mskubenich
24 |
25 | ## 0.2.3
26 |
27 | - Added property_list to SwaggerModelDSL #108 @dr-impossible
28 |
29 | ## 0.2.2
30 |
31 | - Support multiple route methods #128 @frodrigo
32 |
33 | ## 0.2.1
34 |
35 | - Add support for Authorizations (OAuth 2.0) - Thanks to @RKushnir #97
36 |
37 | ## 0.2.0
38 |
39 | - Additional logging for generation failures (suggested in #81)
40 | - Added api_file_name to config #88
41 | - Add support for multiple base api controllers. #93
42 | - Change success status to ok #89
43 | - Address issue with missing slashes - remove trailing slash from base paths and add slash before api paths #117
44 |
45 | ## 0.1.9
46 |
47 | - Adding support for multiple engines #65
48 | - Add ability for swagger_api to accept parameters (e.g. consumes, produces)
49 | - Update dependencies #64
50 | - Address issue with routing verbs where some verbs do not have a route.verb.source attribute only route.verb #58
51 | - Add ability to set custom attributes (like info block) on api-docs file #67
52 | - Ensure API endpoint/nickname (e.g. "Api::V1::Some#update") is only written out once per resource file. Addresses PATCH, POST duplication issue #70
53 | - Add "consumes" dsl method #74
54 | - Expose API version on transform_path for easier “No Server Integrations” #79
55 |
56 | ## 0.1.8
57 |
58 | - Fix issue with gem build open-ended dependency warnings in gemspec
59 | - Fix issue where param_list doesn't output parameter description #57
60 |
61 | ## 0.1.7
62 |
63 | - Make camelizing of model properties configurable. #55
64 |
65 | ## 0.1.6
66 |
67 | - Document notes DSL
68 | - Get rid of unnecessary ternary operator in dsl.rb #54
69 | - Fix development dependencies gems requirements #53
70 | - Add support for the `notes` property #52
71 | - Config's base_api_controller is configurable #51
72 |
73 | ## 0.1.5
74 | - Delay processing docs DSL to allow changing the context of the controllers #47 @ldnunes
75 |
76 | ## 0.1.4
77 | - An undocumentated action in a documented controller should not raise errors #43 @ldnunes
78 | - Allow reopening of docs definition for the swagger_api DSL command #44 @ldnunes
79 | - Refactor write_docs to split the documentation generation from file writing #45 @ldnunes
80 |
81 | ## 0.1.3
82 | - Fix issue where empty path throws error
83 |
84 | ## 0.1.2
85 | - Add suport for Swagger models
86 | - Use ActionControlller::Base instead of ApplicationController. fixes #27
87 | - Status codes for response
88 | - Path generation fixes #26 @stevschmid
89 | - Ignore path filtering when no params are set
90 | - Add param_list helper for generating enums/lists
91 | - Improve structure of generator class - break up large methods
92 | - Fix the destination path of the resource files #30
93 |
94 | ## 0.1.1
95 | - Add support for Rails engines (@fotinakis)
96 | - Filter out path parameters if the parameter is not in the path (@stevschmid)
97 |
98 | ## 0.1.0
99 |
100 | - Add CHANGELOG.md
101 | - Add `api_extension_type` option (support for other route .formats)
102 | - Rails Appraisals
103 | - Add configuration options table to README documentation
104 | - Guidance on inheritance and asset pre-compilation
105 | - Custom response message error text can now be set
106 | - Ability to override base controller with `base_api_controller` method
107 | - Default configuration for Generator
108 | - Fix typo in README.md
109 |
110 | ##0.0.3
111 |
112 | - Documentation
113 |
114 | ## 0.0.2
115 |
116 | - Add `base_path` option
117 |
118 | ## 0.0.1
119 |
120 | - Initial release
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in swagger-docs.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Rich Hollis
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Swagger::Docs
2 |
3 | Generates swagger-ui json files for rails apps with APIs. You add the swagger DSL to your controller classes and then run one rake task to generate the json files.
4 |
5 | [][gem]
6 | [][gemnasium]
7 |
8 | [gem]: https://rubygems.org/gems/swagger-docs
9 | [travis]: http://travis-ci.org/richhollis/swagger-docs
10 | [gemnasium]: https://gemnasium.com/richhollis/swagger-docs
11 | [coveralls]: https://coveralls.io/r/richhollis/swagger-docs
12 |
13 | ## Swagger Version Specification Support
14 |
15 | This project supports elements of the v1.2 swagger specification. It *does not* support the v2 specification. If you are looking for support for the newer specification the please see the [swagger-blocks](https://github.com/fotinakis/swagger-blocks/) project. I don't currently have any plans to add support for v2.0 at this time due to time constraints, but I'm open to accepting a PR on this. Contact me if you are interested in helping with that effort - thanks!
16 |
17 | ## Example usage
18 |
19 | Here is an extract of the DSL from a user controller API class:
20 |
21 | ```ruby
22 | swagger_controller :users, "User Management"
23 |
24 | swagger_api :index do
25 | summary "Fetches all User items"
26 | notes "This lists all the active users"
27 | param :query, :page, :integer, :optional, "Page number"
28 | response :unauthorized
29 | response :not_acceptable
30 | response :requested_range_not_satisfiable
31 | end
32 | ```
33 |
34 | ## Installation
35 |
36 | Add this line to your application's Gemfile:
37 |
38 | gem 'swagger-docs'
39 |
40 | And then execute:
41 |
42 | $ bundle
43 |
44 | Or install it yourself as:
45 |
46 | $ gem install swagger-docs
47 |
48 | ## Usage
49 |
50 | ### Create Initializer
51 |
52 | Create an initializer in config/initializers (e.g. swagger_docs.rb) and define your APIs:
53 |
54 | ```ruby
55 | Swagger::Docs::Config.register_apis({
56 | "1.0" => {
57 | # the extension used for the API
58 | :api_extension_type => :json,
59 | # the output location where your .json files are written to
60 | :api_file_path => "public/api/v1/",
61 | # the URL base path to your API
62 | :base_path => "http://api.somedomain.com",
63 | # if you want to delete all .json files at each generation
64 | :clean_directory => false,
65 | # Ability to setup base controller for each api version. Api::V1::SomeController for example.
66 | :parent_controller => Api::V1::SomeController,
67 | # add custom attributes to api-docs
68 | :attributes => {
69 | :info => {
70 | "title" => "Swagger Sample App",
71 | "description" => "This is a sample description.",
72 | "termsOfServiceUrl" => "http://helloreverb.com/terms/",
73 | "contact" => "apiteam@wordnik.com",
74 | "license" => "Apache 2.0",
75 | "licenseUrl" => "http://www.apache.org/licenses/LICENSE-2.0.html"
76 | }
77 | }
78 | }
79 | })
80 | ```
81 |
82 | #### Configuration options
83 |
84 | The following table shows all the current configuration options and their defaults. The default will be used if you don't supply your own value.
85 |
86 |
87 |
88 |
89 | Option |
90 | Description |
91 | Default |
92 |
93 |
94 |
95 |
96 |
97 | api_extension_type |
98 | The extension, if necessary, used for your API - e.g. :json or :xml |
99 | nil |
100 |
101 |
102 |
103 | api_file_path |
104 | The output file path where generated swagger-docs files are written to. |
105 | public/ |
106 |
107 |
108 |
109 | base_path |
110 | The URI base path for your API - e.g. api.somedomain.com |
111 | / |
112 |
113 |
114 |
115 | base_api_controller / base_api_controllers |
116 | The base controller class your project uses; it or its subclasses will be where you call swagger_controller and swagger_api. An array of base controller classes may be provided. |
117 | ActionController::Base |
118 |
119 |
120 |
121 | clean_directory |
122 | When generating swagger-docs files this option specifies if the api_file_path should be cleaned first. This means that all files will be deleted in the output directory first before any files are generated. |
123 | false |
124 |
125 |
126 |
127 | formatting |
128 | Specifies which formatting method to apply to the JSON that is written. Available options: :none, :pretty |
129 | :pretty |
130 |
131 |
132 |
133 | camelize_model_properties |
134 | Camelizes property names of models. For example, a property name called first_name would be converted to firstName. |
135 | true |
136 |
137 |
138 |
139 | parent_controller |
140 | Assign a different controller to use for the configuration |
141 | |
142 |
143 |
144 |
145 |
146 |
147 |
148 | ### Documenting a controller
149 |
150 | ```ruby
151 | class Api::V1::UsersController < ApplicationController
152 |
153 | swagger_controller :users, "User Management"
154 |
155 | swagger_api :index do
156 | summary "Fetches all User items"
157 | notes "This lists all the active users"
158 | param :query, :page, :integer, :optional, "Page number"
159 | param :path, :nested_id, :integer, :optional, "Team Id"
160 | response :unauthorized
161 | response :not_acceptable, "The request you made is not acceptable"
162 | response :requested_range_not_satisfiable
163 | end
164 |
165 | swagger_api :show do
166 | summary "Fetches a single User item"
167 | param :path, :id, :integer, :optional, "User Id"
168 | response :ok, "Success", :User
169 | response :unauthorized
170 | response :not_acceptable
171 | response :not_found
172 | end
173 |
174 | swagger_api :create do
175 | summary "Creates a new User"
176 | param :form, :first_name, :string, :required, "First name"
177 | param :form, :last_name, :string, :required, "Last name"
178 | param :form, :email, :string, :required, "Email address"
179 | param_list :form, :role, :string, :required, "Role", [ "admin", "superadmin", "user" ]
180 | response :unauthorized
181 | response :not_acceptable
182 | end
183 |
184 | swagger_api :update do
185 | summary "Updates an existing User"
186 | param :path, :id, :integer, :required, "User Id"
187 | param :form, :first_name, :string, :optional, "First name"
188 | param :form, :last_name, :string, :optional, "Last name"
189 | param :form, :email, :string, :optional, "Email address"
190 | param :form, :tag, :Tag, :required, "Tag object"
191 | response :unauthorized
192 | response :not_found
193 | response :not_acceptable
194 | end
195 |
196 | swagger_api :destroy do
197 | summary "Deletes an existing User item"
198 | param :path, :id, :integer, :optional, "User Id"
199 | response :unauthorized
200 | response :not_found
201 | end
202 |
203 | # Support for Swagger complex types:
204 | # https://github.com/wordnik/swagger-core/wiki/Datatypes#wiki-complex-types
205 | swagger_model :Tag do
206 | description "A Tag object."
207 | property :id, :integer, :required, "User Id"
208 | property :name, :string, :optional, "Name"
209 | property_list :type, :string, :optional, "Tag Type", ["info", "warning", "error"]
210 | end
211 | end
212 | ```
213 |
214 | #### Support for Enums (PR #108)
215 |
216 | ```
217 | property_list :type, :string, :optional, "Type", ["info", "warning", "error"]
218 | ```
219 |
220 | #### Custom resource paths`(PR #126)
221 |
222 | ```ruby
223 | class Api::V1::UsersController < ApplicationController
224 |
225 | swagger_controller :users, "User Management", resource_path: "/some/where"
226 | ```
227 |
228 | ### DRYing up common documentation
229 |
230 | Suppose you have a header or a parameter that must be present on several controllers and methods. Instead of duplicating it on all the controllers you can do this on your API base controller:
231 |
232 | ```ruby
233 | class Api::BaseController < ActionController::Base
234 | class << self
235 | Swagger::Docs::Generator::set_real_methods
236 |
237 | def inherited(subclass)
238 | super
239 | subclass.class_eval do
240 | setup_basic_api_documentation
241 | end
242 | end
243 |
244 | private
245 | def setup_basic_api_documentation
246 | [:index, :show, :create, :update, :delete].each do |api_action|
247 | swagger_api api_action do
248 | param :header, 'Authentication-Token', :string, :required, 'Authentication token'
249 | end
250 | end
251 | end
252 | end
253 | end
254 | ```
255 |
256 | And then use it as a superclass to all you API controllers. All the subclassed controllers will have the same documentation applied to them.
257 |
258 | #### Alternate method
259 |
260 | Using a block for the swagger_api definition:
261 |
262 | ```ruby
263 | class Api::V1::UserController < Api::V1::BaseController
264 |
265 | swagger_controller :user, "Users"
266 |
267 | def self.add_common_params(api)
268 | api.param :form, "user[first_name]", :string, :optional, "Notes"
269 | api.param :form, "user[last_name]", :string, :optional, "Name"
270 | api.param :form, "user[email]", :string, :optional, "Email"
271 | end
272 |
273 | swagger_api :create do |api|
274 | summary "Create a new User item"
275 | Api::V1::UserController::add_common_params(api)
276 | response :unauthorized
277 | response :not_acceptable
278 | response :unprocessable_entity
279 | end
280 |
281 | swagger_api :update do |api|
282 | summary "Update an existing User item"
283 | Api::V1::UserController::add_common_params(api)
284 | response :unauthorized
285 | response :not_acceptable
286 | response :unprocessable_entity
287 | end
288 | end
289 | ```
290 |
291 | ### DSL Methods
292 |
293 |
294 |
295 |
296 | Method |
297 | Description |
298 |
299 |
300 |
301 |
302 |
303 | summary |
304 | The summary of the API |
305 |
306 |
307 |
308 | notes (optional) |
309 | The associated notes for the API |
310 |
311 |
312 |
313 | param |
314 | Standard API Parameter |
315 |
316 |
317 |
318 | param_list |
319 | Standard API Enum/List parameter. |
320 |
321 |
322 |
323 | response |
324 | Takes a symbol or status code and passes it to `Rack::Utils.status_code`. The current list of status codes can be seen here: https://github.com/rack/rack/blob/master/lib/rack/utils.rb. An optional message can be added. |
325 |
326 |
327 |
328 |
329 |
330 | ### Run rake task to generate docs
331 |
332 | ```
333 | rake swagger:docs
334 | ```
335 |
336 | Swagger-ui JSON files should now be present in your api_file_path (e.g. ./public/api/v1)
337 |
338 | #### Additional logging for generation failures
339 |
340 | Errors aren't displayed by default. To see all error messages use the ```SD_LOG_LEVEL``` environment variable when running the rake task:
341 |
342 | ```
343 | SD_LOG_LEVEL=1 rake swagger:docs
344 | ```
345 |
346 | Currently only constantize errors are shown.
347 |
348 | Errors are written to ```$stderr```. Error logging methods can be found in ```Config``` and can be overridden for custom behaviour.
349 |
350 | Thanks to **[@tomtt](https://github.com/tomtt/)** who originally suggested this idea in #81
351 |
352 | ### Sample
353 |
354 | A sample Rails application where you can run the above rake command and view the output in swagger-ui can be found here:
355 |
356 | https://github.com/richhollis/swagger-docs-sample
357 |
358 | 
359 |
360 |
361 | ### Advanced Customization
362 |
363 | #### Inheriting from a custom Api controller
364 |
365 | By default swagger-docs is applied to controllers inheriting from ApplicationController.
366 | If this is not the case for your application, use this snippet in your initializer
367 | _before_ calling Swagger::Docs::Config#register_apis(...).
368 |
369 | ```ruby
370 | class Swagger::Docs::Config
371 | def self.base_api_controller; Api::ApiController end
372 | end
373 | ```
374 |
375 | #### Custom route discovery for supporting Rails Engines
376 |
377 | By default, swagger-docs finds controllers by traversing routes in `Rails.application`.
378 | To override this, you can customize the `base_application` config in an initializer:
379 |
380 | ```ruby
381 | class Swagger::Docs::Config
382 | def self.base_application; Api::Engine end
383 | end
384 | ```
385 |
386 | If you want swagger to find controllers in `Rails.application` and/or multiple
387 | engines you can override `base_application` to return an array.
388 |
389 | ```ruby
390 | class Swagger::Docs::Config
391 | def self.base_application; [Rails.application, Api::Engine, SomeOther::Engine] end
392 | end
393 | ```
394 |
395 | Or, if you prefer you can override `base_applications` for this purpose. The plural
396 | `base_applications` takes precedence over `base_application` and MUST return an
397 | array.
398 |
399 | ```ruby
400 | class Swagger::Docs::Config
401 | def self.base_applications; [Rails.application, Api::Engine, SomeOther::Engine] end
402 | end
403 | ```
404 |
405 | #### Transforming the `path` variable
406 |
407 | Swagger allows a distinction between the API documentation server and the hosted API
408 | server through the `path` variable (see [Swagger: No server Integrations](https://github.com/wordnik/swagger-core/wiki/No-server-Integrations)). To override the default swagger-docs behavior, you can provide a `transform_path`
409 | class method in your initializer:
410 |
411 | ```ruby
412 | class Swagger::Docs::Config
413 | def self.transform_path(path, api_version)
414 | "http://example.com/api-docs/#{api_version}/#{path}"
415 | end
416 | end
417 | ```
418 |
419 | The transformation will be applied to all API `path` values in the generated `api-docs.json` file.
420 |
421 | #### Precompile
422 |
423 | It is best-practice *not* to keep documentation in version control. An easy way
424 | to integrate swagger-docs into a conventional deployment setup (e.g. capistrano,
425 | chef, or opsworks) is to piggyback on the 'assets:precompile' rake task. And don't forget
426 | to add your api documentation directory to .gitignore in this case.
427 |
428 | ```ruby
429 | #Rakefile or lib/task/precompile_overrides.rake
430 | namespace :assets do
431 | task :precompile do
432 | Rake::Task['assets:precompile'].invoke
433 | Rake::Task['swagger:docs'].invoke
434 | end
435 | end
436 | ```
437 |
438 | ### Output files
439 |
440 | api-docs.json output:
441 |
442 |
443 | ```json
444 | {
445 | "apiVersion": "1.0",
446 | "swaggerVersion": "1.2",
447 | "basePath": "/api/v1",
448 | "apis": [
449 | {
450 | "path": "/users.{format}",
451 | "description": "User Management"
452 | }
453 | ]
454 | }
455 | ```
456 |
457 | users.json output:
458 |
459 | ```json
460 | {
461 | "apiVersion": "1.0",
462 | "swaggerVersion": "1.2",
463 | "basePath": "http://api.somedomain.com/api/v1/",
464 | "resourcePath": "/users",
465 | "apis": [
466 | {
467 | "path": "/users",
468 | "operations": [
469 | {
470 | "summary": "Fetches all User items",
471 | "parameters": [
472 | {
473 | "paramType": "query",
474 | "name": "page",
475 | "type": "integer",
476 | "description": "Page number",
477 | "required": false
478 | }
479 | ],
480 | "responseMessages": [
481 | {
482 | "code": 401,
483 | "message": "Unauthorized"
484 | },
485 | {
486 | "code": 406,
487 | "message": "The request you made is not acceptable"
488 | },
489 | {
490 | "code": 416,
491 | "message": "Requested Range Not Satisfiable"
492 | }
493 | ],
494 | "method": "get",
495 | "nickname": "Api::V1::Users#index"
496 | }
497 | ]
498 | },
499 | {
500 | "path": "nested/{nested_id}/sample",
501 | "operations": [
502 | {
503 | "summary": "Fetches all User items",
504 | "parameters": [
505 | {
506 | "paramType": "query",
507 | "name": "page",
508 | "type": "integer",
509 | "description": "Page number",
510 | "required": false
511 | },
512 | {
513 | "paramType": "path",
514 | "name": "nested_id",
515 | "type": "integer",
516 | "description": "Team Id",
517 | "required": false
518 | }
519 | ],
520 | "responseMessages": [
521 | {
522 | "code": 401,
523 | "message": "Unauthorized"
524 | },
525 | {
526 | "code": 406,
527 | "message": "The request you made is not acceptable"
528 | },
529 | {
530 | "code": 416,
531 | "message": "Requested Range Not Satisfiable"
532 | }
533 | ],
534 | "method": "get",
535 | "nickname": "Api::V1::Users#index"
536 | }
537 | ]
538 | },
539 | {
540 | "path": "/users",
541 | "operations": [
542 | {
543 | "summary": "Creates a new User",
544 | "parameters": [
545 | {
546 | "paramType": "form",
547 | "name": "first_name",
548 | "type": "string",
549 | "description": "First name",
550 | "required": true
551 | },
552 | {
553 | "paramType": "form",
554 | "name": "last_name",
555 | "type": "string",
556 | "description": "Last name",
557 | "required": true
558 | },
559 | {
560 | "paramType": "form",
561 | "name": "email",
562 | "type": "string",
563 | "description": "Email address",
564 | "required": true
565 | }
566 | ],
567 | "responseMessages": [
568 | {
569 | "code": 401,
570 | "message": "Unauthorized"
571 | },
572 | {
573 | "code": 406,
574 | "message": "Not Acceptable"
575 | }
576 | ],
577 | "method": "post",
578 | "nickname": "Api::V1::Users#create"
579 | }
580 | ]
581 | },
582 | {
583 | "path": "/users/{id}",
584 | "operations": [
585 | {
586 | "summary": "Fetches a single User item",
587 | "parameters": [
588 | {
589 | "paramType": "path",
590 | "name": "id",
591 | "type": "integer",
592 | "description": "User Id",
593 | "required": false
594 | }
595 | ],
596 | "responseMessages": [
597 | {
598 | "code": 401,
599 | "message": "Unauthorized"
600 | },
601 | {
602 | "code": 404,
603 | "message": "Not Found"
604 | },
605 | {
606 | "code": 406,
607 | "message": "Not Acceptable"
608 | }
609 | ],
610 | "method": "get",
611 | "nickname": "Api::V1::Users#show"
612 | }
613 | ]
614 | },
615 | {
616 | "path": "/users/{id}",
617 | "operations": [
618 | {
619 | "summary": "Updates an existing User",
620 | "parameters": [
621 | {
622 | "paramType": "path",
623 | "name": "id",
624 | "type": "integer",
625 | "description": "User Id",
626 | "required": true
627 | },
628 | {
629 | "paramType": "form",
630 | "name": "first_name",
631 | "type": "string",
632 | "description": "First name",
633 | "required": false
634 | },
635 | {
636 | "paramType": "form",
637 | "name": "last_name",
638 | "type": "string",
639 | "description": "Last name",
640 | "required": false
641 | },
642 | {
643 | "paramType": "form",
644 | "name": "email",
645 | "type": "string",
646 | "description": "Email address",
647 | "required": false
648 | },
649 | {
650 | "paramType": "form",
651 | "name": "tag",
652 | "type": "Tag",
653 | "description": "Tag object",
654 | "required": true
655 | }
656 | ],
657 | "responseMessages": [
658 | {
659 | "code": 401,
660 | "message": "Unauthorized"
661 | },
662 | {
663 | "code": 404,
664 | "message": "Not Found"
665 | },
666 | {
667 | "code": 406,
668 | "message": "Not Acceptable"
669 | }
670 | ],
671 | "method": "put",
672 | "nickname": "Api::V1::Users#update"
673 | }
674 | ]
675 | },
676 | {
677 | "path": "/users/{id}",
678 | "operations": [
679 | {
680 | "summary": "Deletes an existing User item",
681 | "parameters": [
682 | {
683 | "paramType": "path",
684 | "name": "id",
685 | "type": "integer",
686 | "description": "User Id",
687 | "required": false
688 | }
689 | ],
690 | "responseMessages": [
691 | {
692 | "code": 401,
693 | "message": "Unauthorized"
694 | },
695 | {
696 | "code": 404,
697 | "message": "Not Found"
698 | }
699 | ],
700 | "method": "delete",
701 | "nickname": "Api::V1::Users#destroy"
702 | }
703 | ]
704 | }
705 | ],
706 | "models": {
707 | "Tag": {
708 | "id": "Tag",
709 | "required": [
710 | "id"
711 | ],
712 | "properties": {
713 | "id": {
714 | "type": "integer",
715 | "description": "User Id"
716 | },
717 | "name": {
718 | "type": "string",
719 | "description": "Name",
720 | "foo": "test"
721 | }
722 | },
723 | "description": "A Tag object."
724 | }
725 | }
726 | }
727 | ```
728 |
729 | ## Thanks to our contributors
730 |
731 | Thanks to jdar, fotinakis, stevschmid, ldnunes, aaronrenner and all of our contributors for making swagger-docs even better.
732 |
733 | ## Related Projects
734 |
735 | **[@fotinakis](https://github.com/fotinakis/)** has created Swagger::Blocks - a DSL for pure Ruby code blocks: [swagger-blocks](https://github.com/fotinakis/swagger-blocks/)
736 |
737 | A [cors rack middleware for testing swagger apis](https://gist.github.com/richhollis/b98a8b0599860145ad86) designed to be used in Rails development environments.
738 |
739 |
740 | ## More About Me
741 |
742 | [Rich Hollis](http://richhollis.co.uk)
743 |
744 | ## Contributing
745 |
746 | When raising a Pull Request please ensure that you have provided good test coverage for the request you are making.
747 |
748 | 1. Fork it
749 | 2. Create your feature branch (`git checkout -b my-new-feature`)
750 | 3. Commit your changes (`git commit -am 'Add some feature'`)
751 | 4. Push to the branch (`git push origin my-new-feature`)
752 | 5. Create new Pull Request
753 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require 'appraisal'
3 | require "bundler/setup"
4 | require "bundler/gem_tasks"
5 |
6 | require 'rspec/core/rake_task'
7 |
8 | RSpec::Core::RakeTask.new(:spec)
9 | task :default => :spec
10 | task :test => :spec
11 |
--------------------------------------------------------------------------------
/certs/gem-public_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApyaWNo
3 | aG9sbGlzMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
4 | b20wHhcNMTMxMDIyMTMwMzI3WhcNMTQxMDIyMTMwMzI3WjBBMRMwEQYDVQQDDApy
5 | aWNoaG9sbGlzMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
6 | FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDppQTU++yinAuC
7 | ydu87c/vDGTmE5Go9/zI48/T0kTco+JbUn4BPUaK0DWCEpZULvqwQAqVQm8JQnIU
8 | 6Z3k1tAQbhtgbG2oWNIxyC7SyXMQw/ag5qoAhw6k3DFE+jGKrREzADFb7vG+nPYp
9 | 4yinx27jCTIAv7/z2AVt6HoHOYh1s0HniJQWCebi7QgNXboMY8MpFxSwNkcFjl14
10 | KMSf9SX7iOyiwqgcJmN0fN4be8pH5j/EdinUL1rWlwldcUo2+6LChBswRPmtdaZv
11 | UyICuX5VfVJA0KrA/ihIMLaZVO5esFso+YrpP+QgbvhLwhn5e/sB5dr3a+y0+GJZ
12 | zPGtm60bAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
13 | BBShIiKLL1E1JG++RUVAOSPO7rZV0TAfBgNVHREEGDAWgRRyaWNoaG9sbGlzQGdt
14 | YWlsLmNvbTAfBgNVHRIEGDAWgRRyaWNoaG9sbGlzQGdtYWlsLmNvbTANBgkqhkiG
15 | 9w0BAQUFAAOCAQEAe4P1TzJlVhUn60Wx/431wNnuHZS9K4gSzmNr4zuZU6lP3rxx
16 | rMsSY1nJY1nTBqX9W62hO+KS14ncbZvNU59ao5YVXHDflEB3Yz20DP9E2Uws64Bx
17 | ify0Dwuq4VV2PiQbczuTGhGupzQpkMttWNZqVdjDbH5k8sGx3MumNX7YUJwUerhZ
18 | bTBme5soNyJzAeWBqCBPT9p98rC6vqhcBfAVF6RbERYL6MPyoBZWqGeuMR4H2X/v
19 | RYcsqDfanYBx7QcftOnbeQq7/Ep7Zx+W9+Ph3TiJLMLdAr7bLkgN1SjvrjTL5mQR
20 | FuQtYvE4LKiUQpG7vLTRB78dQBlSj9fnv2OM9w==
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/lib/swagger/docs.rb:
--------------------------------------------------------------------------------
1 | require "swagger/docs/config"
2 | require "swagger/docs/dsl"
3 | require "swagger/docs/api_declaration_file_metadata"
4 | require "swagger/docs/api_declaration_file"
5 | require "swagger/docs/generator"
6 | require "swagger/docs/impotent_methods"
7 | require "swagger/docs/methods"
8 | require "swagger/docs/task"
9 | require "swagger/docs/version"
10 |
11 | module Swagger
12 | module Docs
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/swagger/docs/api_declaration_file.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class ApiDeclarationFile
4 | attr_reader :metadata, :apis
5 |
6 | def initialize(metadata, apis, models)
7 | @metadata = metadata
8 | @apis = camelize_keys_deep apis
9 | @models = models
10 | end
11 |
12 | def generate_resource
13 | resource = build_resource_root_hash
14 | # Add the already-normalized models to the resource.
15 | resource = resource.merge({:models => models}) if models.present?
16 | resource
17 | end
18 |
19 | def base_path
20 | metadata.base_path
21 | end
22 |
23 | def path
24 | metadata.path
25 | end
26 |
27 | def swagger_version
28 | metadata.swagger_version
29 | end
30 |
31 | def api_version
32 | metadata.api_version
33 | end
34 |
35 | def controller_base_path
36 | metadata.controller_base_path
37 | end
38 |
39 | def camelize_model_properties
40 | metadata.camelize_model_properties
41 | end
42 |
43 | def resource_path
44 | metadata.overridden_resource_path || demod
45 | end
46 |
47 | def resource_file_path
48 | trim_leading_slash(debased_path.to_s.underscore)
49 | end
50 |
51 | def models
52 | normalize_model_properties @models
53 | end
54 |
55 | def authorizations
56 | metadata.authorizations
57 | end
58 |
59 | private
60 |
61 | def build_resource_root_hash
62 | {
63 | "apiVersion" => api_version,
64 | "swaggerVersion" => swagger_version,
65 | "basePath" => base_path,
66 | "resourcePath" => resource_path,
67 | "apis" => apis,
68 | "resourceFilePath" => resource_file_path,
69 | "authorizations" => authorizations
70 | }
71 | end
72 |
73 | def normalize_model_properties(models)
74 | Hash[
75 | models.map do |k, v|
76 | if camelize_model_properties
77 | [k.to_s, camelize_keys_deep(v)]
78 | else
79 | [k.to_s, stringify_keys_deep(v)]
80 | end
81 | end]
82 | end
83 |
84 | def demod
85 | "#{debased_path.to_s.camelize}".demodulize.camelize.underscore
86 | end
87 |
88 | def debased_path
89 | path.gsub("#{controller_base_path}", "")
90 | end
91 |
92 | def trim_leading_slash(str)
93 | return str if !str
94 | str.gsub(/\A\/+/, '')
95 | end
96 |
97 | def camelize_keys_deep(obj)
98 | process_keys_deep(obj){|key| key.to_s.camelize(:lower)}
99 | end
100 |
101 | def stringify_keys_deep(obj)
102 | process_keys_deep(obj){|key| key.to_s}
103 | end
104 |
105 | def process_keys_deep(obj, &block)
106 | if obj.is_a? Hash
107 | Hash[
108 | obj.map do |k, v|
109 | new_key = block.call(k)
110 | new_value = process_keys_deep v, &block
111 | [new_key, new_value]
112 | end
113 | ]
114 | elsif obj.is_a? Array
115 | new_value = obj.collect do |a|
116 | process_keys_deep a, &block
117 | end
118 | else
119 | obj
120 | end
121 | end
122 |
123 | end
124 | end
125 | end
126 |
--------------------------------------------------------------------------------
/lib/swagger/docs/api_declaration_file_metadata.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class ApiDeclarationFileMetadata
4 | DEFAULT_SWAGGER_VERSION = "1.2"
5 | DEFAULT_RESOURCE_PATH = nil
6 |
7 | attr_reader :api_version, :path, :base_path, :controller_base_path, :swagger_version, :camelize_model_properties
8 | attr_reader :authorizations, :overridden_resource_path
9 |
10 | def initialize(api_version, path, base_path, controller_base_path, options={})
11 | @api_version = api_version
12 | @path = path
13 | @base_path = base_path
14 | @controller_base_path = controller_base_path
15 | @swagger_version = options.fetch(:swagger_version, DEFAULT_SWAGGER_VERSION)
16 | @camelize_model_properties = options.fetch(:camelize_model_properties, true)
17 | @authorizations = options.fetch(:authorizations, {})
18 | @overridden_resource_path = options.fetch(:resource_path, DEFAULT_RESOURCE_PATH)
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/swagger/docs/config.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class Config
4 | class << self
5 | @@base_api_controller = nil
6 |
7 | def base_api_controller
8 | @@base_api_controller || ActionController::Base
9 | end
10 |
11 | def base_api_controllers
12 | Array(base_api_controller)
13 | end
14 |
15 | def base_api_controller=(controller)
16 | @@base_api_controller = controller
17 | end
18 |
19 | alias_method :base_api_controllers=, :base_api_controller=
20 |
21 | def base_applications
22 | Array(base_application)
23 | end
24 |
25 | def base_application
26 | Rails.application
27 | end
28 |
29 | def register_apis(versions)
30 | base_api_controllers.each do |controller|
31 | controller.send(:include, ImpotentMethods)
32 | end
33 | @versions = versions
34 | end
35 |
36 | def registered_apis
37 | @versions ||= {}
38 | end
39 |
40 | def transform_path(path, api_version)
41 | # This is only for overriding, so don't perform any path transformations by default.
42 | path
43 | end
44 |
45 | def log_exception
46 | yield
47 | rescue => e
48 | write_log(:error, e)
49 | raise
50 | end
51 |
52 | def log_env_name
53 | 'SD_LOG_LEVEL'
54 | end
55 |
56 | def write_log(type, output)
57 | $stderr.puts output if type == :error and ENV[log_env_name]=="1"
58 | end
59 |
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/swagger/docs/dsl.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class SwaggerDSL
4 | # http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
5 | def self.call(action, caller, &block)
6 | # Create a new SwaggerDSL instance, and instance_eval the block to it
7 | instance = new
8 | instance.instance_eval(&block)
9 | # Now return all of the set instance variables as a Hash
10 | instance.instance_variables.inject({}) { |result_hash, instance_variable|
11 | result_hash[instance_variable] = instance.instance_variable_get(instance_variable)
12 | result_hash # Gotta have the block return the result_hash
13 | }
14 | end
15 |
16 | def summary(text)
17 | @summary = text
18 | end
19 |
20 | def notes(text)
21 | @notes = text
22 | end
23 |
24 | def method(method)
25 | @method = method
26 | end
27 |
28 | def type(type)
29 | @type = type
30 | end
31 |
32 | def items(items)
33 | @items = items
34 | end
35 |
36 | def consumes(mime_types)
37 | @consumes = mime_types
38 | end
39 |
40 | def nickname(nickname)
41 | @nickname = nickname
42 | end
43 |
44 | def parameters
45 | @parameters ||= []
46 | end
47 |
48 | def param(param_type, name, type, required, description = nil, hash={})
49 | parameters << {:param_type => param_type, :name => name, :type => type,
50 | :description => description, :required => required == :required}.merge(hash)
51 | end
52 |
53 | # helper method to generate enums
54 | def param_list(param_type, name, type, required, description = nil, allowed_values = [], hash = {})
55 | hash.merge!({allowable_values: {value_type: "LIST", values: allowed_values}})
56 | param(param_type, name, type, required, description, hash)
57 | end
58 |
59 | def response_messages
60 | @response_messages ||= []
61 | end
62 |
63 | def response(status, text = nil, model = nil)
64 | if status.is_a? Symbol
65 | status = :ok if status == :success
66 | status_code = Rack::Utils.status_code(status)
67 | response_messages << {:code => status_code, :responseModel => model, :message => text || status.to_s.titleize}
68 | else
69 | response_messages << {:code => status, :responseModel => model, :message => text}
70 | end
71 | response_messages.sort_by!{|i| i[:code]}
72 | end
73 | end
74 |
75 | class SwaggerModelDSL
76 | attr_accessor :id
77 |
78 | # http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
79 | def self.call(model_name, caller, &block)
80 | # Create a new SwaggerModelDSL instance, and instance_eval the block to it
81 | instance = new
82 | instance.instance_eval(&block)
83 | instance.id = model_name
84 | # Now return all of the set instance variables as a Hash
85 | instance.instance_variables.inject({}) { |result_hash, instance_var_name|
86 | key = instance_var_name[1..-1].to_sym # Strip prefixed @ sign.
87 | result_hash[key] = instance.instance_variable_get(instance_var_name)
88 | result_hash # Gotta have the block return the result_hash
89 | }
90 | end
91 |
92 | def properties
93 | @properties ||= {}
94 | end
95 |
96 | def required
97 | @required ||= []
98 | end
99 |
100 | def description(description)
101 | @description = description
102 | end
103 |
104 | def property(name, type, required, description = nil, hash={})
105 | properties[name] = {
106 | type: type,
107 | description: description,
108 | }.merge!(hash)
109 | self.required << name if required == :required
110 | end
111 |
112 | # helper method to generate enums
113 | def property_list(name, type, required, description = nil, allowed_values = [], hash = {})
114 | hash.merge!({allowable_values: {value_type: "LIST", values: allowed_values}})
115 | property(name, type, required, description, hash)
116 | end
117 | end
118 | end
119 | end
120 |
--------------------------------------------------------------------------------
/lib/swagger/docs/generator.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class Generator
4 |
5 | DEFAULT_VER = "1.0"
6 | DEFAULT_CONFIG = {
7 | :api_file_path => "public/",
8 | :api_file_name => "api-docs.json",
9 | :base_path => "/",
10 | :clean_directory => false,
11 | :formatting => :pretty
12 | }
13 |
14 | class << self
15 |
16 | def set_real_methods
17 | # replace impotent methods with live ones
18 | Config.base_api_controllers.each do |controller|
19 | controller.send(:include, Methods)
20 | end
21 | end
22 |
23 | def write_docs(apis = nil)
24 | results = generate_docs(apis)
25 | results.each{|api_version, result| write_doc(result) }
26 | end
27 |
28 | def write_doc(result)
29 | settings = result[:settings]
30 | config = result[:config]
31 | create_output_paths(settings[:api_file_path])
32 | clean_output_paths(settings[:api_file_path]) if config[:clean_directory] || false
33 | root = result[:root]
34 | resources = root.delete 'resources'
35 | root.merge!(config[:attributes] || {}) # merge custom user attributes like info
36 | # write the api-docs file
37 | write_to_file("#{settings[:api_file_path]}/#{config[:api_file_name]}", root, config)
38 | # write the individual resource files
39 | resources.each do |resource|
40 | resource_file_path = resource.delete 'resourceFilePath'
41 | write_to_file(File.join(settings[:api_file_path], "#{resource_file_path}.json"), resource, config)
42 | end
43 | result
44 | end
45 |
46 | def generate_docs(apis=nil)
47 | apis ||= Config.registered_apis
48 | results = {}
49 | set_real_methods
50 |
51 | apis[DEFAULT_VER] = DEFAULT_CONFIG if apis.empty?
52 |
53 | apis.each do |api_version, config|
54 | settings = get_settings(api_version, config)
55 | config.reverse_merge!(DEFAULT_CONFIG)
56 | results[api_version] = generate_doc(api_version, settings, config)
57 | results[api_version][:settings] = settings
58 | results[api_version][:config] = config
59 | end
60 | results
61 | end
62 |
63 | def generate_doc(api_version, settings, config)
64 | root = {
65 | "apiVersion" => api_version,
66 | "swaggerVersion" => "1.2",
67 | "basePath" => settings[:base_path],
68 | :apis => [],
69 | :authorizations => settings[:authorizations]
70 | }
71 | results = {:processed => [], :skipped => []}
72 | resources = []
73 |
74 | get_route_paths(settings[:controller_base_path]).each do |path|
75 | ret = process_path(path, root, config, settings)
76 | results[ret[:action]] << ret
77 | if ret[:action] == :processed
78 | resources << generate_resource(ret[:path], ret[:apis], ret[:models], settings, root, config, ret[:klass].swagger_config)
79 | debased_path = get_debased_path(ret[:path], settings[:controller_base_path])
80 | resource_api = {
81 | path: "/#{Config.transform_path(trim_leading_slash(debased_path), api_version)}.{format}",
82 | description: ret[:klass].swagger_config[:description]
83 | }
84 | root[:apis] << resource_api
85 | end
86 | end
87 | root['resources'] = resources
88 | results[:root] = root
89 | results
90 | end
91 |
92 | private
93 |
94 | def transform_spec_to_api_path(spec, controller_base_path, extension)
95 | api_path = spec.to_s.dup
96 | api_path.gsub!('(.:format)', extension ? ".#{extension}" : '')
97 | api_path.gsub!(/:(\w+)/, '{\1}')
98 | api_path.gsub!(controller_base_path, '')
99 | "/" + trim_slashes(api_path)
100 | end
101 |
102 | def camelize_keys_deep!(h)
103 | h.keys.each do |k|
104 | ks = k.to_s.camelize(:lower)
105 | h[ks] = h.delete k
106 | camelize_keys_deep! h[ks] if h[ks].kind_of? Hash
107 | if h[ks].kind_of? Array
108 | h[ks].each do |a|
109 | next unless a.kind_of? Hash
110 | camelize_keys_deep! a
111 | end
112 | end
113 | end
114 | end
115 |
116 | def trim_leading_slash(str)
117 | return str if !str
118 | str.gsub(/\A\/+/, '')
119 | end
120 |
121 | def trim_trailing_slash(str)
122 | return str if !str
123 | str.gsub(/\/+\z/, '')
124 | end
125 |
126 | def trim_slashes(str)
127 | trim_leading_slash(trim_trailing_slash(str))
128 | end
129 |
130 | def get_debased_path(path, controller_base_path)
131 | path.gsub("#{controller_base_path}", "")
132 | end
133 |
134 | def process_path(path, root, config, settings)
135 | return {action: :skipped, reason: :empty_path} if path.empty?
136 | klass = Config.log_exception { "#{path.to_s.camelize}Controller".constantize } rescue nil
137 | return {action: :skipped, path: path, reason: :klass_not_present} if !klass
138 | return {action: :skipped, path: path, reason: :not_swagger_resource} if !klass.methods.include?(:swagger_config) or !klass.swagger_config[:controller]
139 | return {action: :skipped, path: path, reason: :not_kind_of_parent_controller} if config[:parent_controller] && !(klass < config[:parent_controller])
140 | apis, models, defined_nicknames = [], {}, []
141 | routes.select{|i| i.defaults[:controller] == path}.each do |route|
142 | unless nickname_defined?(defined_nicknames, path, route) # only add once for each route once e.g. PATCH, PUT
143 | ret = get_route_path_apis(path, route, klass, settings, config)
144 | apis = apis + ret[:apis]
145 | models.merge!(ret[:models])
146 | defined_nicknames << ret[:nickname] if ret[:nickname].present?
147 | end
148 | end
149 | {action: :processed, path: path, apis: apis, models: models, klass: klass}
150 | end
151 |
152 | def route_verbs(route)
153 | if defined?(route.verb.source) then route.verb.source.to_s.delete('$'+'^').split('|') else [route.verb] end.collect{|verb| verb.downcase.to_sym}
154 | end
155 |
156 | def path_route_nickname(path, route)
157 | action = route.defaults[:action]
158 | "#{path.camelize}##{action}"
159 | end
160 |
161 | def nickname_defined?(defined_nicknames, path, route)
162 | target_nickname = path_route_nickname(path, route)
163 | defined_nicknames.each{|nickname| return true if nickname == target_nickname }
164 | false
165 | end
166 |
167 | def generate_resource(path, apis, models, settings, root, config, swagger_config)
168 | metadata = ApiDeclarationFileMetadata.new(
169 | root["apiVersion"], path, root["basePath"],
170 | settings[:controller_base_path],
171 | camelize_model_properties: config.fetch(:camelize_model_properties, true),
172 | swagger_version: root["swaggerVersion"],
173 | authorizations: root[:authorizations],
174 | resource_path: swagger_config[:resource_path]
175 | )
176 | declaration = ApiDeclarationFile.new(metadata, apis, models)
177 | declaration.generate_resource
178 | end
179 |
180 | def routes
181 | Config.base_applications.map{|app| app.routes.routes.to_a }.flatten
182 | end
183 |
184 | def get_route_path_apis(path, route, klass, settings, config)
185 | models, apis = {}, []
186 | action = route.defaults[:action]
187 | verbs = route_verbs(route)
188 | return {apis: apis, models: models, nickname: nil} if !operation = klass.swagger_actions[action.to_sym]
189 | operation = Hash[operation.map {|k, v| [k.to_s.gsub("@","").to_sym, v.respond_to?(:deep_dup) ? v.deep_dup : v.dup] }] # rename :@instance hash keys
190 | nickname = operation[:nickname] = path_route_nickname(path, route)
191 |
192 | route_path = if defined?(route.path.spec) then route.path.spec else route.path end
193 | api_path = transform_spec_to_api_path(route_path, settings[:controller_base_path], config[:api_extension_type])
194 | operation[:parameters] = filter_path_params(api_path, operation[:parameters]) if operation[:parameters]
195 | operations = verbs.collect{|verb|
196 | op = operation.dup
197 | op[:method] = verb
198 | op
199 | }
200 | apis << {:path => api_path, :operations => operations}
201 | models = get_klass_models(klass)
202 |
203 | {apis: apis, models: models, nickname: nickname}
204 | end
205 |
206 | def get_klass_models(klass)
207 | models = {}
208 | # Add any declared models to the root of the resource.
209 | klass.swagger_models.each do |model_name, model|
210 | formatted_model = {
211 | id: model[:id],
212 | required: model[:required],
213 | properties: model[:properties],
214 | }
215 | formatted_model[:description] = model[:description] if model[:description]
216 | models[model[:id]] = formatted_model
217 | end
218 | models
219 | end
220 |
221 | def get_settings(api_version, config)
222 | base_path = trim_trailing_slash(config[:base_path] || "")
223 | controller_base_path = trim_leading_slash(config[:controller_base_path] || "")
224 | base_path += "/#{controller_base_path}" unless controller_base_path.empty?
225 | api_file_path = config[:api_file_path]
226 | authorizations = config[:authorizations]
227 | settings = {
228 | base_path: base_path,
229 | controller_base_path: controller_base_path,
230 | api_file_path: api_file_path,
231 | authorizations: authorizations
232 | }.freeze
233 | end
234 |
235 | def get_route_paths(controller_base_path)
236 | paths = routes.map{|i| "#{i.defaults[:controller]}" }
237 | paths.uniq.select{|i| i.start_with?(controller_base_path)}
238 | end
239 |
240 | def create_output_paths(api_file_path)
241 | FileUtils.mkdir_p(api_file_path) # recursively create out output path
242 | end
243 |
244 | def clean_output_paths(api_file_path)
245 | Dir.foreach(api_file_path) do |f|
246 | fn = File.join(api_file_path, f)
247 | File.delete(fn) if !File.directory?(fn) and File.extname(fn) == '.json'
248 | end
249 | end
250 |
251 | def write_to_file(path, structure, config={})
252 | content = case config[:formatting]
253 | when :pretty; JSON.pretty_generate structure
254 | else; structure.to_json
255 | end
256 | FileUtils.mkdir_p File.dirname(path)
257 | File.open(path, 'w') { |file| file.write content }
258 | end
259 |
260 | def filter_path_params(path, params)
261 | params.reject do |param|
262 | param_as_variable = "{#{param[:name]}}"
263 | param[:param_type] == :path && !path.include?(param_as_variable)
264 | end
265 | end
266 | end
267 | end
268 | end
269 | end
270 |
--------------------------------------------------------------------------------
/lib/swagger/docs/impotent_methods.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | module ImpotentMethods
4 |
5 | def self.included(base)
6 | base.extend ClassMethods
7 | end
8 |
9 | module ClassMethods
10 | private
11 |
12 | def swagger_api(action, params = {}, &block)
13 | end
14 |
15 | def swagger_model(model_name, &block)
16 | end
17 |
18 | def swagger_controller(controller, description, params={})
19 | end
20 | end
21 |
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/swagger/docs/methods.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/hash/deep_merge"
2 | module Swagger
3 | module Docs
4 | module Methods
5 | def self.included(base)
6 | base.extend ClassMethods
7 | end
8 |
9 | module ClassMethods
10 | def swagger_controller(controller, description, params = {})
11 | swagger_config[:controller] = controller
12 | swagger_config[:description] = description
13 | swagger_config[:resource_path] = params[:resource_path]
14 | end
15 |
16 | def swagger_actions
17 | swagger_dsl = {}
18 | Array(@swagger_dsl).each do |action, params, controller, block|
19 | dsl = SwaggerDSL.call(action, controller, &block)
20 | swagger_dsl[action] ||= {}
21 | swagger_dsl[action].deep_merge!(dsl) { |key, old, new| Array(old) + Array(new) }
22 | swagger_dsl[action].deep_merge!(params) # merge in user api parameters
23 | end
24 | swagger_dsl
25 | end
26 |
27 | def swagger_models
28 | swagger_model_dsls ||= {}
29 | Array(@swagger_model_dsls).each do |model_name, controller, block|
30 | model_dsl = SwaggerModelDSL.call(model_name, controller, &block)
31 | swagger_model_dsls[model_name] = model_dsl
32 | end
33 | swagger_model_dsls
34 | end
35 |
36 | def swagger_config
37 | @swagger_config ||= {}
38 | end
39 |
40 | def swagger_api(action, params = {}, &block)
41 | @swagger_dsl ||= []
42 | @swagger_dsl << [action, params, self, block]
43 | end
44 |
45 | def swagger_model(model_name, &block)
46 | @swagger_model_dsls ||= []
47 | @swagger_model_dsls << [model_name, self, block]
48 | end
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/swagger/docs/task.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | class Task < Rails::Railtie
4 | rake_tasks do
5 | Dir[File.join(File.dirname(__FILE__),'../../tasks/*.rake')].each { |f| load f }
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/swagger/docs/version.rb:
--------------------------------------------------------------------------------
1 | module Swagger
2 | module Docs
3 | VERSION = "0.2.9"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/tasks/swagger.rake:
--------------------------------------------------------------------------------
1 | namespace :swagger do
2 |
3 | desc "Generate Swagger documentation files"
4 | task :docs => [:environment] do |t,args|
5 | results = Swagger::Docs::Generator.write_docs(Swagger::Docs::Config.registered_apis)
6 | results.each do |k,v|
7 | puts "#{k}: #{v[:processed].count} processed / #{v[:skipped].count} skipped"
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/fixtures/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController
2 | cattr_accessor :context
3 | self.context = "original"
4 | end
5 |
--------------------------------------------------------------------------------
/spec/fixtures/controllers/custom_resource_path_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class SuperclassController < ApplicationController
4 | end
5 | class CustomResourcePathController < SuperclassController
6 | swagger_controller :custom_resource_path, "User Management", resource_path: "resource/testing"
7 |
8 | swagger_api :index do
9 | summary "Fetches all User items"
10 | param :query, :page, :integer, :optional, "Page number"
11 | param :path, :nested_id, :integer, :optional, "Team Id"
12 | response :unauthorized
13 | response :not_acceptable, "The request you made is not acceptable"
14 | response :requested_range_not_satisfiable
15 | end
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/spec/fixtures/controllers/ignored_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class IgnoredController
4 | end
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/fixtures/controllers/multiple_routes_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class MultipleRoutesController < ApplicationController
4 | swagger_controller :multiple_routes, "Multiple Routes"
5 |
6 | swagger_api :index do
7 | summary "Creates a new User"
8 | param :form, :first_name, :string, :required, "First name"
9 | response :unauthorized
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/fixtures/controllers/nested_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class SuperclassController < ApplicationController
4 | end
5 | class NestedController < SuperclassController
6 | swagger_controller :nested, "User Management"
7 |
8 | swagger_api :index do
9 | summary "Fetches all User items"
10 | param :query, :page, :integer, :optional, "Page number"
11 | param :path, :nested_id, :integer, :optional, "Team Id"
12 | response :unauthorized
13 | response :not_acceptable, "The request you made is not acceptable"
14 | response :requested_range_not_satisfiable
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/fixtures/controllers/sample_controller.rb:
--------------------------------------------------------------------------------
1 | module Api
2 | module V1
3 | class SuperclassController < ApplicationController
4 | end
5 | class SampleController < SuperclassController
6 | swagger_controller :users, "User Management"
7 |
8 | swagger_api :index do
9 | summary "Fetches all User items"
10 | param :query, :page, :integer, :optional, "Page number"
11 | param :path, :nested_id, :integer, :optional, "Team Id"
12 | response :ok, "Some text", :Tag
13 | response :unauthorized
14 | response :not_acceptable, "The request you made is not acceptable"
15 | response :requested_range_not_satisfiable
16 | end
17 |
18 | swagger_api :show do
19 | summary "Fetches a single User item"
20 | param :path, :id, :integer, :optional, "User Id"
21 | response :unauthorized
22 | response :not_acceptable
23 | response :not_found
24 | end
25 |
26 | swagger_api :create do
27 | summary "Creates a new User"
28 | consumes [ "application/json", "text/xml" ]
29 | param :form, :first_name, :string, :required, "First name"
30 | param :form, :last_name, :string, :required, "Last name"
31 | param :form, :email, :string, :required, "Email address"
32 | param_list :form, :role, :string, :required, "Role", [ "admin", "superadmin", "user" ]
33 | param :body, :body, :json, :required, 'JSON formatted body'
34 | items '{$ref" => "setup"}'
35 | response :unauthorized
36 | response :not_acceptable
37 | end
38 |
39 | swagger_api :update do
40 | summary "Updates an existing User"
41 | notes "Only the given fields are updated."
42 | param :path, :id, :integer, :required, "User Id"
43 | param :form, :first_name, :string, :optional, "First name"
44 | param :form, :last_name, :string, :optional, "Last name"
45 | param :form, :email, :string, :optional, "Email address"
46 | param :form, :tag, :Tag, :required, "Tag object"
47 | response :unauthorized
48 | response :not_found
49 | response :not_acceptable
50 | end
51 |
52 | swagger_api :destroy do
53 | summary "Deletes an existing User item"
54 | param :path, :id, :integer, :optional, "User Id"
55 | response :unauthorized
56 | response :not_found
57 | end
58 |
59 | # a method that intentionally has no parameters
60 | swagger_api :new do
61 | summary "Builds a new User item"
62 | end
63 |
64 | swagger_api :context_dependent do
65 | summary "An action dependent on the context of the controller " +
66 | "class. Right now it is: " + ApplicationController.context
67 | response :ok
68 | response :unauthorized
69 | end
70 |
71 | # Support for Swagger complex types:
72 | # https://github.com/wordnik/swagger-core/wiki/Datatypes#wiki-complex-types
73 | swagger_model :Tag do
74 | description "A Tag object."
75 | property :id, :integer, :required, "User Id"
76 | property :name, :string, :optional, "Name", foo: "test"
77 | property_list :type, :string, :optional, "Type", ["info", "warning", "error"]
78 | end
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/api_declaration_file_metadata_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Swagger::Docs::ApiDeclarationFileMetadata do
4 |
5 | describe "#initialize" do
6 | it "sets the api_version property" do
7 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
8 | expect(metadata.api_version).to eq("1.0")
9 | end
10 | it "sets the path property" do
11 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
12 | expect(metadata.path).to eq("path")
13 | end
14 | it "sets the base_path property" do
15 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
16 | expect(metadata.base_path).to eq("basePath")
17 | end
18 | it "sets the controller_base_path property" do
19 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
20 | expect(metadata.controller_base_path).to eq("controllerBasePath")
21 | end
22 | it "defaults the swagger_version property to DEFAULT_SWAGGER_VERSION" do
23 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
24 | expect(metadata.swagger_version).to eq(described_class::DEFAULT_SWAGGER_VERSION)
25 | end
26 | it "allows the swagger_version property to be_overriden" do
27 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath", swagger_version: "2.0")
28 | expect(metadata.swagger_version).to eq("2.0")
29 | end
30 | it "defaults the camelize_model_properties property to true" do
31 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
32 | expect(metadata.camelize_model_properties).to eq(true)
33 | end
34 | it "allows the camelize_model_properties property to be overidden" do
35 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath", camelize_model_properties: false)
36 | expect(metadata.camelize_model_properties).to eq(false)
37 | end
38 | it "defaults the authorizations property to empty hash" do
39 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath")
40 | expect(metadata.authorizations).to eq({})
41 | end
42 | it "allows the authorizations property to be overidden" do
43 | authorizations = {foo: 'bar'}
44 | metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath", authorizations: authorizations)
45 | expect(metadata.authorizations).to eq(authorizations)
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/api_declaration_file_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Swagger::Docs::ApiDeclarationFile do
4 | let(:apis) do
5 | [
6 | {
7 | :path=>"sample/{id}",
8 | :operations=>[
9 | {
10 | :summary=>"Updates an existing User",
11 | :parameters=>[
12 | {:param_type=>:path, :name=>:id, :type=>:integer, :description=>"User Id", :required=>true},
13 | {:param_type=>:form, :name=>:first_name, :type=>:string, :description=>"First name", :required=>false},
14 | {:param_type=>:form, :name=>:last_name, :type=>:string, :description=>"Last name", :required=>false},
15 | {:param_type=>:form, :name=>:email, :type=>:string, :description=>"Email address", :required=>false},
16 | {:param_type=>:form, :name=>:tag, :type=>:Tag, :description=>"Tag object", :required=>true}
17 | ],
18 | :response_messages=>[
19 | {:code=>401, :message=>"Unauthorized"},
20 | {:code=>404, :message=>"Not Found"},
21 | {:code=>406, :message=>"Not Acceptable"}
22 | ],
23 | :notes=>"Only the given fields are updated.",
24 | :method=>:put,
25 | :nickname=>"Api::V1::Sample#update",
26 | :consumes=>["application/json", "text/xml"]
27 | }
28 | ]
29 | }
30 | ]
31 | end
32 | let(:models) do
33 | {
34 | :Tag=>
35 | {
36 | :id=>:Tag,
37 | :required=>[:id],
38 | :properties=>
39 | {
40 | :id=>{:type=>:integer, :description=>"User Id"},
41 | :first_name=>{:type=>:string, :description=>"First Name"},
42 | :last_name=>{:type=>:string, :description=>"Last Name"}
43 | },
44 | :description=>"A Tag object."
45 | }
46 | }
47 | end
48 | let(:metadata) do
49 | Swagger::Docs::ApiDeclarationFileMetadata.new("1.0", "api/v1/sample", "http://api.no.where/", "")
50 | end
51 |
52 | describe "#generate_resource" do
53 | it "generates the appropriate response" do
54 | declaration = described_class.new(metadata, apis, models)
55 |
56 | expected_response = {
57 | "apiVersion"=> declaration.api_version,
58 | "swaggerVersion"=> declaration.swagger_version,
59 | "basePath"=> declaration.base_path,
60 | "apis"=> declaration.apis,
61 | "resourcePath"=> declaration.resource_path,
62 | :models=> declaration.models,
63 | "resourceFilePath" => declaration.resource_file_path,
64 | "authorizations" => {}
65 | }
66 | expect(declaration.generate_resource).to eq(expected_response)
67 | end
68 | end
69 | describe "#base_path" do
70 | it "returns metadata.base_path" do
71 | metadata = double("metadata", base_path: "/hello")
72 | declaration = described_class.new(metadata, apis, models)
73 | expect(declaration.base_path).to eq(metadata.base_path)
74 | end
75 | end
76 | describe "#path" do
77 | it "returns metadata.path" do
78 | metadata = double("metadata", path: "/hello")
79 | declaration = described_class.new(metadata, apis, models)
80 | expect(declaration.path).to eq(metadata.path)
81 | end
82 | end
83 | describe "#controller_base_path" do
84 | it "returns metadata.controller_base_path" do
85 | metadata = double("metadata", controller_base_path: "/hello")
86 | declaration = described_class.new(metadata, apis, models)
87 | expect(declaration.controller_base_path).to eq(metadata.controller_base_path)
88 | end
89 | end
90 | describe "#resource_path" do
91 | it "returns the debased controller path" do
92 | metadata = double("metadata", overridden_resource_path: nil, controller_base_path: "/hello", path: "/hello/test-endpoint")
93 | declaration = described_class.new(metadata, apis, models)
94 | expect(declaration.resource_path).to eq("test_endpoint")
95 | end
96 | context "with an overridden_resource_path" do
97 | it "returns the overriden resource path directly" do
98 | metadata = double("metadata", overridden_resource_path: "testing-path", controller_base_path: "/hello", path: "/hello/test-endpoint")
99 | declaration = described_class.new(metadata, apis, models)
100 | expect(declaration.resource_path).to eq("testing-path")
101 | end
102 | end
103 | end
104 | describe "#swagger_version" do
105 | it "returns metadata.swagger_version" do
106 | metadata = double("metadata", swagger_version: "1.2")
107 | declaration = described_class.new(metadata, apis, models)
108 | expect(declaration.swagger_version).to eq(metadata.swagger_version)
109 | end
110 | end
111 | describe "#api_version" do
112 | it "returns metadata.api_version" do
113 | metadata = double("metadata", api_version: "1.0")
114 | declaration = described_class.new(metadata, apis, models)
115 | expect(declaration.api_version).to eq(metadata.api_version)
116 | end
117 | end
118 | describe "#camelize_model_properties" do
119 | it "returns metadata.camelize_model_properties" do
120 | metadata = double("metadata", camelize_model_properties: false)
121 | declaration = described_class.new(metadata, apis, models)
122 | expect(declaration.camelize_model_properties).to eq(metadata.camelize_model_properties)
123 | end
124 | end
125 | describe "#models" do
126 | context "with camelize_model_properties set to true" do
127 | it "returns a models hash that's ready for output" do
128 | declaration = described_class.new(metadata, apis, models)
129 | allow(declaration).to receive(:camelize_model_properties).and_return(true)
130 | expected_models_hash = {
131 | "Tag" =>
132 | {
133 | "id" => :Tag,
134 | "required" =>[:id],
135 | "properties" =>
136 | {
137 | "id" =>{"type"=>:integer, "description"=>"User Id"},
138 | "firstName"=>{"type"=>:string, "description"=>"First Name"},
139 | "lastName"=>{"type"=>:string, "description"=>"Last Name"},
140 | },
141 | "description"=>"A Tag object."
142 | }
143 | }
144 |
145 | expect(declaration.models).to eq(expected_models_hash)
146 | end
147 | end
148 | context "with camelize_model_properties set to false" do
149 | it "returns a models hash that's ready for output" do
150 | declaration = described_class.new(metadata, apis, models)
151 | allow(declaration).to receive(:camelize_model_properties).and_return(false)
152 | expected_models_hash = {
153 | "Tag" =>
154 | {
155 | "id" => :Tag,
156 | "required" =>[:id],
157 | "properties" =>
158 | {
159 | "id" =>{"type"=>:integer, "description"=>"User Id"},
160 | "first_name"=>{"type"=>:string, "description"=>"First Name"},
161 | "last_name"=>{"type"=>:string, "description"=>"Last Name"},
162 | },
163 | "description"=>"A Tag object."
164 | }
165 | }
166 |
167 | expect(declaration.models).to eq(expected_models_hash)
168 | end
169 | end
170 | end
171 | describe "#apis" do
172 | it "returns a api hash that's ready for output" do
173 | declaration = described_class.new(metadata, apis, models)
174 | expected_apis_array = [
175 | {
176 | "path"=>"sample/{id}",
177 | "operations"=>[
178 | {
179 | "summary"=>"Updates an existing User",
180 | "parameters"=>[
181 | {"paramType"=>:path, "name"=>:id, "type"=>:integer, "description"=>"User Id", "required"=>true},
182 | {"paramType"=>:form, "name"=>:first_name, "type"=>:string, "description"=>"First name", "required"=>false},
183 | {"paramType"=>:form, "name"=>:last_name, "type"=>:string, "description"=>"Last name", "required"=>false},
184 | {"paramType"=>:form, "name"=>:email, "type"=>:string, "description"=>"Email address", "required"=>false},
185 | {"paramType"=>:form, "name"=>:tag, "type"=>:Tag, "description"=>"Tag object", "required"=>true}
186 | ],
187 | "responseMessages"=>[
188 | {"code"=>401, "message"=>"Unauthorized"},
189 | {"code"=>404, "message"=>"Not Found"},
190 | {"code"=>406, "message"=>"Not Acceptable"}
191 | ],
192 | "notes"=>"Only the given fields are updated.",
193 | "method"=>:put,
194 | "nickname"=>"Api::V1::Sample#update",
195 | "consumes"=>["application/json", "text/xml"]
196 | }
197 | ]
198 | }
199 | ]
200 | expect(declaration.apis).to eq(expected_apis_array)
201 | end
202 | end
203 | end
204 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/config_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Swagger::Docs::Config do
4 |
5 | require "fixtures/controllers/application_controller"
6 |
7 | let(:test_controller) { Class.new }
8 |
9 | before(:each) do
10 | stub_const('ActionController::Base', ApplicationController)
11 | end
12 |
13 | subject { Swagger::Docs::Config }
14 |
15 | describe "::base_api_controller" do
16 | it "returns ActionController::Base by default" do
17 | expect(subject.base_api_controller).to eq(ActionController::Base)
18 | end
19 | it "allows assignment of another class" do
20 | subject.base_api_controller = test_controller
21 | expect(subject.base_api_controller).to eq(test_controller)
22 | end
23 | end
24 |
25 | describe "::base_api_controllers" do
26 | it "returns an array with ActionController::Base by default" do
27 | expect(subject.base_api_controllers).to eq([ActionController::Base])
28 | end
29 | it "allows assignment of multiple classes" do
30 | subject.base_api_controllers = [test_controller, ActionController::Base]
31 | expect(subject.base_api_controllers).to eq([test_controller, ActionController::Base])
32 | end
33 | end
34 |
35 | describe "::base_application" do
36 | it "defaults to Rails.application" do
37 | expect(subject.base_application).to eq (Rails.application)
38 | end
39 | end
40 |
41 | describe "::base_applications" do
42 | before(:each) { allow( subject ).to receive(:base_application).and_return(:app) }
43 | it "defaults to Rails.application an an Array" do
44 | expect(subject.base_applications).to eq [:app]
45 | end
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/dsl_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Swagger::Docs::SwaggerDSL do
4 | subject { described_class.new() }
5 |
6 | describe "#response" do
7 | it "adds code, responseModel and message to response_messages" do
8 | subject.response(:ok, "Some sample text", "Tag")
9 | expect(subject.response_messages).to eq([{:code=>200, :responseModel=>"Tag", :message=>"Some sample text"}])
10 | end
11 |
12 | it "accept :success was an :ok status" do
13 | subject.response(:success, "Some sample text", "Tag")
14 | expect(subject.response_messages).to eq([{:code=>200, :responseModel=>"Tag", :message=>"Some sample text"}])
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Swagger::Docs::Generator do
4 |
5 | require "fixtures/controllers/application_controller"
6 | require "fixtures/controllers/ignored_controller"
7 |
8 | before(:each) do
9 | FileUtils.rm_rf(tmp_dir)
10 | stub_const('ActionController::Base', ApplicationController)
11 | end
12 |
13 | let(:routes) {[
14 | stub_route( "^GET$", "index", "api/v1/ignored", "/api/v1/ignored(.:format)"),
15 | stub_route( "^GET$", "index", "api/v1/sample", "/api/v1/sample(.:format)"),
16 | stub_string_verb_route("GET", "index", "api/v1/nested", "/api/v1/nested/:nested_id/nested_sample(.:format)"),
17 | stub_string_verb_route("GET", "index", "api/v1/custom_resource_path", "/api/v1/custom_resource_path/:custom_resource_path/custom_resource_path_sample(.:format)"),
18 | stub_route( "^PATCH$", "create", "api/v1/sample", "/api/v1/sample(.:format)"),
19 | stub_route( "^PUT$", "create", "api/v1/sample", "/api/v1/sample(.:format)"), # intentional duplicate of above route to ensure PATCH is used
20 | stub_route( "^GET$", "show", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
21 | stub_route( "^PUT$", "update", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
22 | stub_route( "^DELETE$", "destroy", "api/v1/sample", "/api/v1/sample/:id(.:format)"),
23 | stub_route( "^GET$", "new", "api/v1/sample", "/api/v1/sample/new(.:format)"), # no parameters for this method
24 | stub_route( "^GET$", "index", "", "/api/v1/empty_path"), # intentional empty path should not cause any errors
25 | stub_route( "^GET$", "ignored", "api/v1/sample", "/api/v1/ignored(.:format)"), # an action without documentation should not cause any errors
26 | stub_route( "^GET|POST$","index", "api/v1/multiple_routes", "/api/v1/multiple_routes(.:format)") # multiple route methods
27 | ]}
28 |
29 | let(:tmp_dir) { Pathname.new('/tmp/swagger-docs/') }
30 | let(:file_resources) { tmp_dir + 'api-docs.json' }
31 | let(:file_resource) { tmp_dir + 'api/v1/sample.json' }
32 | let(:file_resource_nested) { tmp_dir + 'nested.json' }
33 | let(:file_resource_custom_resource_path) { tmp_dir + 'custom_resource_path.json' }
34 |
35 | let(:default_config) {
36 | {
37 | :controller_base_path => "api/v1",
38 | :api_file_path => "#{tmp_dir}",
39 | :base_path => "http://api.no.where/",
40 | :attributes => {
41 | :info => {
42 | "title" => "Swagger Sample App",
43 | "description" => "This is a sample description.",
44 | "termsOfServiceUrl" => "http://helloreverb.com/terms/",
45 | "contact" => "apiteam@wordnik.com",
46 | "license" => "Apache 2.0",
47 | "licenseUrl" => "http://www.apache.org/licenses/LICENSE-2.0.html"
48 | }
49 | }
50 | }
51 | }
52 |
53 | let(:controllers) { [
54 | "fixtures/controllers/sample_controller",
55 | "fixtures/controllers/nested_controller",
56 | "fixtures/controllers/custom_resource_path_controller",
57 | "fixtures/controllers/multiple_routes_controller"
58 | ]}
59 |
60 | context "without controller base path" do
61 | let(:config) {
62 | {
63 | DEFAULT_VER => {:api_file_path => "#{tmp_dir}", :base_path => "http://api.no.where/"}
64 | }
65 | }
66 | before(:each) do
67 | allow(Rails).to receive_message_chain(:application, :routes, :routes).and_return(routes)
68 | Swagger::Docs::Generator.set_real_methods
69 | require "fixtures/controllers/sample_controller"
70 | generate(config)
71 | end
72 | context "resources files" do
73 | let(:resources) { file_resources.read }
74 | let(:response) { JSON.parse(resources) }
75 | it "writes basePath correctly" do
76 | expect(response["basePath"]).to eq "http://api.no.where"
77 | end
78 | it "writes apis correctly" do
79 | expect(response["apis"].count).to eq 1
80 | end
81 | it "writes api path correctly" do
82 | expect(response["apis"][0]["path"]).to eq "/api/v1/sample.{format}"
83 | end
84 |
85 | context "api_file_name" do
86 | let(:api_file_name) { 'swagger-docs.json' }
87 | let(:config) {{
88 | DEFAULT_VER => {
89 | :api_file_path => tmp_dir,
90 | :api_file_name => api_file_name }
91 | }}
92 | let(:file_resources) { tmp_dir + api_file_name }
93 | specify { expect(File.exists? file_resources).to be true }
94 | end
95 | end
96 | context "resource file" do
97 | let(:resource) { file_resource.read }
98 | let(:response) { JSON.parse(resource) }
99 | let(:first) { response["apis"].first }
100 | let(:operations) { first["operations"] }
101 | # {"apiVersion":"1.0","swaggerVersion":"1.2","basePath":"/api/v1","resourcePath":"/sample"
102 | it "writes basePath correctly" do
103 | expect(response["basePath"]).to eq "http://api.no.where"
104 | end
105 | it "writes resourcePath correctly" do
106 | expect(response["resourcePath"]).to eq "sample"
107 | end
108 | it "writes out expected api count" do
109 | expect(response["apis"].count).to eq 6
110 | end
111 | context "first api" do
112 | #"apis":[{"path":" /sample","operations":[{"summary":"Fetches all User items"
113 | #,"method":"get","nickname":"Api::V1::Sample#index"}]
114 | it "writes path correctly" do
115 | expect(first["path"]).to eq "/api/v1/sample"
116 | end
117 | end
118 | end
119 | end
120 | context "with controller base path" do
121 | let(:config) { Swagger::Docs::Config.register_apis({DEFAULT_VER => default_config})}
122 | let(:file_resource) { tmp_dir + 'sample.json' }
123 | let(:resource) { file_resource.read }
124 | let(:response) { JSON.parse(resource) }
125 | let(:apis) { response["apis"] }
126 | before(:each) do
127 | allow(Rails).to receive_message_chain(:application, :routes, :routes).and_return(routes)
128 | Swagger::Docs::Generator.set_real_methods
129 | controllers.each{ |path| require path }
130 | end
131 | context "test suite initialization" do
132 | it "the resources file does not exist" do
133 | expect(file_resource).to_not exist
134 | end
135 | it "the resource file does not exist" do
136 | expect(file_resource).to_not exist
137 | end
138 | end
139 | describe "#generate" do
140 | it "respects parent_controller config option" do
141 | new_config = default_config
142 | new_config[:parent_controller] = ApplicationController
143 | api_config = Swagger::Docs::Config.register_apis(DEFAULT_VER => new_config)
144 | results = generate(api_config)
145 | expect(results[DEFAULT_VER][:processed].count).to eq(controllers.count)
146 | end
147 | end
148 | describe "#write_docs" do
149 | context "no apis registered" do
150 | before(:each) do
151 | Swagger::Docs::Config.register_apis({})
152 | end
153 | it "generates using default config" do
154 | results = generate({})
155 | expect(results[DEFAULT_VER][:processed].count).to eq(controllers.count)
156 | end
157 | end
158 | before(:each) do
159 | generate(config)
160 | end
161 | context "api-docs resources file" do
162 | it "writes the file" do
163 | expect(file_resources).to exist
164 | end
165 | context "custom user attributes" do
166 | let(:parsed_resources) {
167 | JSON.parse(File.read file_resources)
168 | }
169 | it "it has info hash" do
170 | expect(parsed_resources.keys).to include("info")
171 | end
172 | it "has title field" do
173 | expect(parsed_resources["info"]["title"]).to eq "Swagger Sample App"
174 | end
175 | it "has description field" do
176 | expect(parsed_resources["info"]["description"]).to eq "This is a sample description."
177 | end
178 | end
179 | end
180 | it "cleans json files in directory when set" do
181 | file_to_delete = Pathname.new(File.join(config['1.0'][:api_file_path], 'delete_me.json'))
182 | File.open(file_to_delete, 'w') {|f| f.write("{}") }
183 | expect(file_to_delete).to exist
184 | config[DEFAULT_VER][:clean_directory] = true
185 | generate(config)
186 | expect(file_to_delete).to_not exist
187 | end
188 | it "keeps non json files in directory when cleaning" do
189 | file_to_keep = Pathname.new(File.join(config['1.0'][:api_file_path], 'keep_me'))
190 | File.open(file_to_keep, 'w') {|f| f.write("{}") }
191 | config[DEFAULT_VER][:clean_directory] = true
192 | generate(config)
193 | expect(file_to_keep).to exist
194 | end
195 | it "writes the resource file" do
196 | expect(file_resource).to exist
197 | end
198 | it "returns results hash" do
199 | results = generate(config)
200 | expect(results[DEFAULT_VER][:processed].count).to eq(controllers.count)
201 | expect(results[DEFAULT_VER][:skipped].count).to eq 1
202 | end
203 | it "writes pretty json files when set" do
204 | config[DEFAULT_VER][:formatting] = :pretty
205 | generate(config)
206 | resources = File.read file_resources
207 | expect(resources.scan(/\n/).length).to be > 1
208 | end
209 | context "resources files" do
210 | let(:resources) { file_resources.read }
211 | let(:response) { JSON.parse(resources) }
212 | it "writes version correctly" do
213 | expect(response["apiVersion"]).to eq DEFAULT_VER
214 | end
215 | it "writes swaggerVersion correctly" do
216 | expect(response["swaggerVersion"]).to eq "1.2"
217 | end
218 | it "writes basePath correctly" do
219 | expect(response["basePath"]).to eq "http://api.no.where/api/v1"
220 | end
221 | it "writes apis correctly" do
222 | expect(response["apis"].count).to eq(controllers.count)
223 | end
224 | it "writes api path correctly" do
225 | expect(response["apis"][0]["path"]).to eq "/sample.{format}"
226 | end
227 | it "writes api description correctly" do
228 | expect(response["apis"][0]["description"]).to eq "User Management"
229 | end
230 | end
231 | context "nested resource file" do
232 | let(:resource) { file_resource_nested.read }
233 | let(:response) { JSON.parse(resource) }
234 | let(:apis) { response["apis"] }
235 | context "apis" do
236 | context "show" do
237 | let(:api) { get_api_operation(apis, "/nested/{nested_id}/nested_sample", :get) }
238 | let(:operations) { get_api_operations(apis, "/nested/{nested_id}/nested_sample") }
239 | context "parameters" do
240 | it "has correct count" do
241 | expect(api["parameters"].count).to eq 2
242 | end
243 | end
244 | end
245 | end
246 | end
247 | context "multiple routes resource file" do
248 | let(:file_resource) { tmp_dir + 'multiple_routes.json' }
249 | it "handles multiple GET path" do
250 | resource = get_api_operation(apis, "/multiple_routes", :get)
251 | expect(resource["method"]).to eq "get"
252 | end
253 | it "handles multiple POST path" do
254 | resource = get_api_operation(apis, "/multiple_routes", :post)
255 | expect(resource["method"]).to eq "post"
256 | end
257 | end
258 | context "sample resource file" do
259 | # {"apiVersion":"1.0","swaggerVersion":"1.2","basePath":"/api/v1","resourcePath":"/sample"
260 | it "writes version correctly" do
261 | expect(response["apiVersion"]).to eq DEFAULT_VER
262 | end
263 | it "writes swaggerVersion correctly" do
264 | expect(response["swaggerVersion"]).to eq "1.2"
265 | end
266 | it "writes basePath correctly" do
267 | expect(response["basePath"]).to eq "http://api.no.where/api/v1"
268 | end
269 | it "writes resourcePath correctly" do
270 | expect(response["resourcePath"]).to eq "sample"
271 | end
272 | it "writes out expected api count" do
273 | expect(response["apis"].count).to eq 6
274 | end
275 | describe "context dependent documentation" do
276 | after(:each) do
277 | ApplicationController.context = "original"
278 | end
279 | let(:routes) {[stub_route("^GET$", "context_dependent", "api/v1/sample", "/api/v1/sample(.:format)")]}
280 | let(:operations) { apis[0]["operations"] }
281 | it "should be the original" do
282 | ApplicationController.context = "original"
283 | generate(config)
284 | expect(operations.first["summary"]).to eq "An action dependent on the context of the controller class. Right now it is: original"
285 | end
286 | context "when modified" do
287 | it "should be modified" do
288 | ApplicationController.context = "modified"
289 | generate(config)
290 | expect(operations.first["summary"]).to eq "An action dependent on the context of the controller class. Right now it is: modified"
291 | end
292 | end
293 | end
294 | context "apis" do
295 | context "index" do
296 | let(:api) { get_api_operation(apis, "/sample", :get) }
297 | let(:operations) { get_api_operations(apis, "/sample") }
298 | #"apis":[{"path":" /sample","operations":[{"summary":"Fetches all User items"
299 | #,"method":"get","nickname":"Api::V1::Sample#index"}]
300 | it "writes path correctly when api extension type is not set" do
301 | expect(apis.first["path"]).to eq "/sample"
302 | end
303 | it "writes path correctly when api extension type is set" do
304 | config[DEFAULT_VER][:api_extension_type] = :json
305 | generate(config)
306 | expect(apis.first["path"]).to eq "/sample.json"
307 | end
308 | it "writes summary correctly" do
309 | expect(operations.first["summary"]).to eq "Fetches all User items"
310 | end
311 | it "writes method correctly" do
312 | expect(operations.first["method"]).to eq "get"
313 | end
314 | it "writes nickname correctly" do
315 | expect(operations.first["nickname"]).to eq "Api::V1::Sample#index"
316 | end
317 | it "writes responseModel attribute" do
318 | expect(api["responseMessages"].find{|m| m["responseModel"] == "Tag"}).to_not be_nil
319 | end
320 | it "writes response code as 200" do
321 | expect(api["responseMessages"].find{|m| m["responseModel"] == "Tag"}["code"]).to eq 200
322 | end
323 | #"parameters"=>[
324 | # {"paramType"=>"query", "name"=>"page", "type"=>"integer", "description"=>"Page number", "required"=>false},
325 | # {"paramType"=>"path", "name"=>"nested_id", "type"=>"integer", "description"=>"Team Id", "required"=>false}], "responseMessages"=>[{"code"=>401, "message"=>"Unauthorized"}, {"code"=>406, "message"=>"The request you made is not acceptable"}, {"code"=>416, "message"=>"Requested Range Not Satisfiable"}], "method"=>"get", "nickname"=>"Api::V1::Sample#index"}
326 | #]
327 | context "parameters" do
328 | let(:params) { operations.first["parameters"] }
329 | it "has correct count" do
330 | expect(params.count).to eq 1
331 | end
332 | it "writes paramType correctly" do
333 | expect(params.first["paramType"]).to eq "query"
334 | end
335 | it "writes name correctly" do
336 | expect(params.first["name"]).to eq "page"
337 | end
338 | it "writes type correctly" do
339 | expect(params.first["type"]).to eq "integer"
340 | end
341 | it "writes description correctly" do
342 | expect(params.first["description"]).to eq "Page number"
343 | end
344 | it "writes required correctly" do
345 | expect(params.first["required"]).to be_falsey
346 | end
347 | end
348 | context "list parameter" do
349 | let(:api) { get_api_operation(apis, "/sample", :patch) }
350 | let(:params) {api["parameters"] }
351 | it "writes description correctly" do
352 | expect(params[3]["description"]).to eq "Role"
353 | end
354 | end
355 | #"responseMessages":[{"code":401,"message":"Unauthorized"},{"code":406,"message":"Not Acceptable"},{"code":416,"message":"Requested Range Not Satisfiable"}]
356 | context "response messages" do
357 | let(:response_msgs) { operations.first["responseMessages"] }
358 | it "has correct count" do
359 | expect(response_msgs.count).to eq 4
360 | end
361 | it "writes code correctly" do
362 | expect(response_msgs.first["code"]).to eq 200
363 | end
364 | it "writes message correctly" do
365 | expect(response_msgs.first["message"]).to eq "Some text"
366 | end
367 | it "writes specified message correctly" do
368 | expect(response_msgs[1]["message"]).to eq "Unauthorized"
369 | end
370 | end
371 | end
372 | context "create" do
373 | let(:api) { get_api_operation(apis, "/sample", :patch) }
374 | it "writes list parameter values correctly" do
375 | expected_param = {"valueType"=>"LIST", "values"=>["admin", "superadmin", "user"]}
376 | expected_body = {"paramType"=>"body", "name"=>"body", "type"=>"json", "description"=>"JSON formatted body", "required"=>true}
377 | expected_consumes = ["application/json", "text/xml"]
378 | expect(get_api_parameter(api, "role")["allowableValues"]).to eq expected_param
379 | expect(get_api_parameter(api, "body")).to eq expected_body
380 | expect(api["consumes"]).to eq ["application/json", "text/xml"]
381 | expect(api["items"]).to eq("{$ref\" => \"setup\"}")
382 | end
383 | it "doesn't write out route put method" do
384 | expect(get_api_operation(apis, "sample", :put)).to be_nil
385 | end
386 | end
387 | context "update" do
388 | let(:api) { get_api_operation(apis, "/sample/{id}", :put) }
389 | it "writes notes correctly" do
390 | expect(api["notes"]).to eq "Only the given fields are updated."
391 | end
392 | it "writes model param correctly" do
393 | expected_param = {
394 | "paramType" => "form",
395 | "name" => "tag",
396 | "type" => "Tag",
397 | "description" => "Tag object",
398 | "required" => true,
399 | }
400 | expect(get_api_parameter(api, "tag")).to eq expected_param
401 | end
402 | end
403 | end
404 | context "models" do
405 | let(:models) { response["models"] }
406 | # Based on https://github.com/wordnik/swagger-core/wiki/Datatypes
407 | it "writes model correctly" do
408 | expected_model = {
409 | "id" => "Tag",
410 | "required" => ["id"],
411 | "description" => "A Tag object.",
412 | "properties" => {
413 | "name" => {
414 | "type" => "string",
415 | "description" => "Name",
416 | "foo" => "test",
417 | },
418 | "id" => {
419 | "type" => "integer",
420 | "description" => "User Id",
421 | },
422 | "type" => {
423 | "type" => "string",
424 | "description" => "Type",
425 | "allowableValues" => {
426 | "valueType" => "LIST",
427 | "values" => [
428 | "info",
429 | "warning",
430 | "error"
431 | ]
432 | }
433 | }
434 | }
435 | }
436 | expect(models['Tag']).to eq expected_model
437 | end
438 | end
439 | context "custom resource_path resource file" do
440 | let(:resource) { file_resource_custom_resource_path.read }
441 | let(:response) { JSON.parse(resource) }
442 | let(:apis) { response["apis"] }
443 | # {"apiVersion":"1.0","swaggerVersion":"1.2","basePath":"/api/v1","resourcePath":"/sample"
444 | it "writes resourcePath correctly" do
445 | expect(response["resourcePath"]).to eq "resource/testing"
446 | end
447 | end
448 | end
449 | end
450 | end
451 | end
452 |
--------------------------------------------------------------------------------
/spec/lib/swagger/docs/methods.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Swagger::Docs::Methods do
4 |
5 | describe "#swagger_actions" do
6 | it "merges additional configuration parameters into dsl" do
7 | methods = Object.new
8 | methods.extend(Swagger::Docs::Methods::ClassMethods)
9 | methods.swagger_api("test", {produces: [ "application/json" ], consumes: [ "multipart/form-data" ]}) do
10 | end
11 | expect(methods.swagger_actions()).to eq({"test"=>{:produces=>["application/json"], :consumes=>["multipart/form-data"]}})
12 | end
13 | end
14 |
15 | end
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "rails"
2 | require "swagger/docs"
3 | require "ostruct"
4 | require "json"
5 | require 'pathname'
6 |
7 | DEFAULT_VER = Swagger::Docs::Generator::DEFAULT_VER
8 |
9 | RSpec.configure do |config|
10 | config.expect_with :rspec do |c|
11 | c.syntax = :expect
12 | end
13 | config.color = true
14 |
15 | config.before(:each) do
16 | Swagger::Docs::Config.base_api_controller = nil # use default object
17 | end
18 | end
19 |
20 | def generate(config)
21 | Swagger::Docs::Generator::write_docs(config)
22 | end
23 |
24 | def stub_string_verb_route(verb, action, controller, spec)
25 | double("route", :verb => verb,
26 | :defaults => {:action => action, :controller => controller},
27 | :path => spec
28 | )
29 | end
30 |
31 | def stub_route(verb, action, controller, spec)
32 | double("route", :verb => double("verb", :source => verb),
33 | :defaults => {:action => action, :controller => controller},
34 | :path => double("path", :spec => spec)
35 | )
36 | end
37 |
38 | def get_api_paths(apis, path)
39 | apis.select{|api| api["path"] == path}
40 | end
41 |
42 | def get_api_operations(apis, path)
43 | apis = get_api_paths(apis, path)
44 | apis.collect{|api| api["operations"]}.flatten
45 | end
46 |
47 | def get_api_operation(apis, path, method)
48 | operations = get_api_operations(apis, path)
49 | operations.each{|operation| return operation if operation["method"] == method.to_s}
50 | nil
51 | end
52 |
53 | def get_api_parameter(api, name)
54 | api["parameters"].each{|param| return param if param["name"] == name}
55 | nil
56 | end
57 |
--------------------------------------------------------------------------------
/swagger-docs.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'swagger/docs/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "swagger-docs"
8 | spec.version = Swagger::Docs::VERSION
9 | spec.authors = ["Rich Hollis"]
10 | spec.email = ["richhollis@gmail.com"]
11 | spec.description = %q{Generates json files for rails apps to use with swagger-ui}
12 | spec.summary = %q{Generates swagger-ui json files for rails apps with APIs. You add the swagger DSL to your controller classes and then run one rake task to generate the json files.}
13 | spec.homepage = "https://github.com/richhollis/swagger-docs"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files`.split($/)
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ["lib"]
20 |
21 | #spec.cert_chain = ['certs/gem-public_cert.pem']
22 | #spec.signing_key = File.expand_path("~/.gemcert/gem-private_key.pem") if $0 =~ /gem\z/
23 |
24 | spec.add_development_dependency "bundler", "~> 1.3"
25 | spec.add_development_dependency "rake", "~> 10"
26 | spec.add_development_dependency "rspec", "~> 3"
27 | spec.add_development_dependency "appraisal", "~> 1"
28 |
29 | spec.add_runtime_dependency "rails", ">= 3"
30 | spec.add_runtime_dependency "activesupport", ">= 3"
31 | end
32 |
--------------------------------------------------------------------------------