├── .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 Version](https://badge.fury.io/rb/swagger-docs.svg)][gem] 6 | [![Dependency Status](https://gemnasium.com/richhollis/swagger-docs.svg?travis)][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 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 |
OptionDescriptionDefault
api_extension_typeThe extension, if necessary, used for your API - e.g. :json or :xml nil
api_file_pathThe output file path where generated swagger-docs files are written to. public/
base_pathThe URI base path for your API - e.g. api.somedomain.com/
base_api_controller / base_api_controllersThe 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.ActionController::Base
clean_directoryWhen 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.false
formattingSpecifies which formatting method to apply to the JSON that is written. Available options: :none, :pretty:pretty
camelize_model_propertiesCamelizes property names of models. For example, a property name called first_name would be converted to firstName.true
parent_controllerAssign a different controller to use for the configuration
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 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 |
MethodDescription
summaryThe summary of the API
notes (optional)The associated notes for the API
paramStandard API Parameter
param_listStandard API Enum/List parameter.
responseTakes 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.
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 | ![Screen shot 1](https://github.com/richhollis/swagger-docs-sample/raw/master/swagger-docs-screenshot-2.png) 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 | --------------------------------------------------------------------------------