├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib └── swagger │ ├── blocks.rb │ └── blocks │ ├── class_methods.rb │ ├── errors.rb │ ├── internal_helpers.rb │ ├── node.rb │ ├── nodes │ ├── all_of_node.rb │ ├── callback_destination_node.rb │ ├── callback_method_node.rb │ ├── callback_node.rb │ ├── component_node.rb │ ├── contact_node.rb │ ├── content_node.rb │ ├── example_node.rb │ ├── external_docs_node.rb │ ├── flow_node.rb │ ├── header_node.rb │ ├── info_node.rb │ ├── items_node.rb │ ├── license_node.rb │ ├── link_node.rb │ ├── link_parameter_node.rb │ ├── one_of_node.rb │ ├── operation_node.rb │ ├── parameter_node.rb │ ├── path_node.rb │ ├── properties_node.rb │ ├── property_node.rb │ ├── request_body_node.rb │ ├── response_node.rb │ ├── root_node.rb │ ├── schema_node.rb │ ├── scopes_node.rb │ ├── security_requirement_node.rb │ ├── security_scheme_node.rb │ ├── server_node.rb │ ├── tag_node.rb │ ├── value_node.rb │ ├── variable_node.rb │ ├── vendor_extension_node.rb │ └── xml_node.rb │ ├── root.rb │ └── version.rb ├── spec ├── lib │ ├── swagger_v2_api_declaration.json │ ├── swagger_v2_blocks_spec.rb │ ├── swagger_v3_api_declaration.json │ └── swagger_v3_blocks_spec.rb └── spec_helper.rb └── swagger-blocks.gemspec /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # 2 | # PLEASE READ THIS BEFORE FILING AN ISSUE: 3 | # https://github.com/fotinakis/swagger-blocks/#filing-issues 4 | # 5 | -------------------------------------------------------------------------------- /.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 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --warnings 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | rvm: 5 | - 1.9.3 6 | - 2.1.1 7 | - ruby-head 8 | before_install: 9 | - gem update bundler 10 | script: bundle exec rspec 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in swagger-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mike Fotinakis 2 | 3 | The MIT License (MIT) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger::Blocks 2 | 3 | [![Build Status](https://travis-ci.org/fotinakis/swagger-blocks.svg?branch=master)](https://travis-ci.org/fotinakis/swagger-blocks) 4 | [![Gem Version](https://badge.fury.io/rb/swagger-blocks.svg)](http://badge.fury.io/rb/swagger-blocks) 5 | 6 | Swagger::Blocks is a DSL for pure Ruby code blocks that can be turned into JSON. 7 | 8 | It helps you write API docs in the [Swagger](https://helloreverb.com/developers/swagger) style in Ruby and then automatically build JSON that is compatible with [Swagger UI](http://petstore.swagger.wordnik.com/#!/pet). 9 | 10 | ## Features 11 | 12 | * Supports **live updating** by design. Change code, refresh your API docs. 13 | * **Works with all Ruby web frameworks** including Rails, Sinatra, etc. 14 | * **100% support** for all features of the [Swagger 2.0](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md) spec. 15 | * Flexible—you can use Swagger::Blocks anywhere, split up blocks to fit your style preferences, etc. Since it's pure Ruby and serves definitions dynamically, you can easily use initializers/config objects to change values or even **show different APIs based on environment**. 16 | * 1:1 naming with the Swagger spec—block names and nesting should match almost exactly with the swagger spec, with rare exceptions to make things more convenient. 17 | 18 | ## Swagger UI demo 19 | 20 | http://petstore.swagger.io/ 21 | 22 | ![swagger-sample](https://cloud.githubusercontent.com/assets/75300/5822830/4769805c-a08c-11e4-9efe-d57cf0f752e0.png) 23 | 24 | ## Installation 25 | 26 | Add this line to your application's Gemfile: 27 | 28 | gem 'swagger-blocks' 29 | 30 | Or install directly with `gem install swagger-blocks`. 31 | 32 | ## Swagger 2.0 example (Rails) 33 | 34 | This is a simplified example based on the objects in the Petstore [Swagger Sample App](http://petstore.swagger.wordnik.com/#!/pet). For a more complex and complete example, see the [swagger_v2_blocks_spec.rb](https://github.com/fotinakis/swagger-blocks/blob/master/spec/lib/swagger_v2_blocks_spec.rb) file. 35 | 36 | Also note that **Rails is not required**, you can use Swagger::Blocks in plain Ruby objects. 37 | 38 | ### PetsController 39 | 40 | ```Ruby 41 | class PetsController < ActionController::Base 42 | include Swagger::Blocks 43 | 44 | swagger_path '/pets/{id}' do 45 | operation :get do 46 | key :summary, 'Find Pet by ID' 47 | key :description, 'Returns a single pet if the user has access' 48 | key :operationId, 'findPetById' 49 | key :tags, [ 50 | 'pet' 51 | ] 52 | parameter do 53 | key :name, :id 54 | key :in, :path 55 | key :description, 'ID of pet to fetch' 56 | key :required, true 57 | key :type, :integer 58 | key :format, :int64 59 | end 60 | response 200 do 61 | key :description, 'pet response' 62 | schema do 63 | key :'$ref', :Pet 64 | end 65 | end 66 | response :default do 67 | key :description, 'unexpected error' 68 | schema do 69 | key :'$ref', :ErrorModel 70 | end 71 | end 72 | end 73 | end 74 | swagger_path '/pets' do 75 | operation :get do 76 | key :summary, 'All Pets' 77 | key :description, 'Returns all pets from the system that the user has access to' 78 | key :operationId, 'findPets' 79 | key :produces, [ 80 | 'application/json', 81 | 'text/html', 82 | ] 83 | key :tags, [ 84 | 'pet' 85 | ] 86 | parameter do 87 | key :name, :tags 88 | key :in, :query 89 | key :description, 'tags to filter by' 90 | key :required, false 91 | key :type, :array 92 | items do 93 | key :type, :string 94 | end 95 | key :collectionFormat, :csv 96 | end 97 | parameter do 98 | key :name, :limit 99 | key :in, :query 100 | key :description, 'maximum number of results to return' 101 | key :required, false 102 | key :type, :integer 103 | key :format, :int32 104 | end 105 | response 200 do 106 | key :description, 'pet response' 107 | schema do 108 | key :type, :array 109 | items do 110 | key :'$ref', :Pet 111 | end 112 | end 113 | end 114 | response :default do 115 | key :description, 'unexpected error' 116 | schema do 117 | key :'$ref', :ErrorModel 118 | end 119 | end 120 | end 121 | operation :post do 122 | key :description, 'Creates a new pet in the store. Duplicates are allowed' 123 | key :operationId, 'addPet' 124 | key :produces, [ 125 | 'application/json' 126 | ] 127 | key :tags, [ 128 | 'pet' 129 | ] 130 | parameter do 131 | key :name, :pet 132 | key :in, :body 133 | key :description, 'Pet to add to the store' 134 | key :required, true 135 | schema do 136 | key :'$ref', :PetInput 137 | end 138 | end 139 | response 200 do 140 | key :description, 'pet response' 141 | schema do 142 | key :'$ref', :Pet 143 | end 144 | end 145 | response :default do 146 | key :description, 'unexpected error' 147 | schema do 148 | key :'$ref', :ErrorModel 149 | end 150 | end 151 | end 152 | end 153 | 154 | # ... 155 | end 156 | ``` 157 | 158 | ### Models 159 | 160 | #### Pet model 161 | 162 | ```Ruby 163 | class Pet < ActiveRecord::Base 164 | include Swagger::Blocks 165 | 166 | swagger_schema :Pet do 167 | key :required, [:id, :name] 168 | property :id do 169 | key :type, :integer 170 | key :format, :int64 171 | end 172 | property :name do 173 | key :type, :string 174 | end 175 | property :tag do 176 | key :type, :string 177 | end 178 | end 179 | 180 | swagger_schema :PetInput do 181 | allOf do 182 | schema do 183 | key :'$ref', :Pet 184 | end 185 | schema do 186 | key :required, [:name] 187 | property :id do 188 | key :type, :integer 189 | key :format, :int64 190 | end 191 | end 192 | end 193 | end 194 | 195 | # ... 196 | end 197 | ``` 198 | 199 | #### Error model 200 | 201 | ``` Ruby 202 | class ErrorModel # Notice, this is just a plain ruby object. 203 | include Swagger::Blocks 204 | 205 | swagger_schema :ErrorModel do 206 | key :required, [:code, :message] 207 | property :code do 208 | key :type, :integer 209 | key :format, :int32 210 | end 211 | property :message do 212 | key :type, :string 213 | end 214 | end 215 | end 216 | ``` 217 | 218 | ### Docs controller 219 | 220 | To integrate these definitions with Swagger UI, we need a docs controller that can serve the JSON definitions. 221 | 222 | ```Ruby 223 | resources :apidocs, only: [:index] 224 | ``` 225 | 226 | ```Ruby 227 | class ApidocsController < ActionController::Base 228 | include Swagger::Blocks 229 | 230 | swagger_root do 231 | key :swagger, '2.0' 232 | info do 233 | key :version, '1.0.0' 234 | key :title, 'Swagger Petstore' 235 | key :description, 'A sample API that uses a petstore as an example to ' \ 236 | 'demonstrate features in the swagger-2.0 specification' 237 | key :termsOfService, 'http://helloreverb.com/terms/' 238 | contact do 239 | key :name, 'Wordnik API Team' 240 | end 241 | license do 242 | key :name, 'MIT' 243 | end 244 | end 245 | tag do 246 | key :name, 'pet' 247 | key :description, 'Pets operations' 248 | externalDocs do 249 | key :description, 'Find more info here' 250 | key :url, 'https://swagger.io' 251 | end 252 | end 253 | key :host, 'petstore.swagger.wordnik.com' 254 | key :basePath, '/api' 255 | key :consumes, ['application/json'] 256 | key :produces, ['application/json'] 257 | end 258 | 259 | # A list of all classes that have swagger_* declarations. 260 | SWAGGERED_CLASSES = [ 261 | PetsController, 262 | Pet, 263 | ErrorModel, 264 | self, 265 | ].freeze 266 | 267 | def index 268 | render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES) 269 | end 270 | end 271 | ``` 272 | 273 | The special part of this controller is this line: 274 | 275 | ```Ruby 276 | render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES) 277 | ``` 278 | 279 | That is the only line necessary to build the full [root Swagger object](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#schema) JSON and all definitions underneath it. You simply pass in a list of all the "swaggered" classes in your app. 280 | 281 | If you want to include controllers outside the standard path just use the full class path including module names, like: 282 | 283 | ```Ruby 284 | SWAGGERED_CLASSES = [ 285 | Api::V1::PetsController, 286 | self, 287 | ] 288 | ``` 289 | 290 | If you are receiving a "swagger_root must be declared" error make sure you are including "self" in your SWAGGERED_CLASSES definition, as shown above. 291 | 292 | Now, simply point Swagger UI at `/apidocs` and everything should Just Work™. If you change any of the Swagger block definitions, you can simply refresh Swagger UI to see the changes. 293 | 294 | ### Security handling 295 | 296 | To support Swagger's definitions for API key auth or OAuth2, use `security_definition` in your `swagger_root`: 297 | 298 | ```Ruby 299 | swagger_root do 300 | key :swagger, '2.0' 301 | 302 | # ... 303 | 304 | security_definition :api_key do 305 | key :type, :apiKey 306 | key :name, :api_key 307 | key :in, :header 308 | end 309 | security_definition :petstore_auth do 310 | key :type, :oauth2 311 | key :authorizationUrl, 'http://swagger.io/api/oauth/dialog' 312 | key :flow, :implicit 313 | scopes do 314 | key 'write:pets', 'modify pets in your account' 315 | key 'read:pets', 'read your pets' 316 | end 317 | end 318 | end 319 | ``` 320 | 321 | You can then apply [security requirement objects](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#securityRequirementObject) to the entire `swagger_root`, or to individual operations: 322 | 323 | ```Ruby 324 | swagger_path '/pets/{id}' do 325 | operation :get do 326 | 327 | # ... 328 | 329 | security do 330 | key :api_key, [] 331 | end 332 | security do 333 | key :petstore_auth, ['write:pets', 'read:pets'] 334 | end 335 | end 336 | end 337 | ``` 338 | 339 | #### Nested complex objects 340 | 341 | The `key` block simply takes the value you give and puts it directly into the final JSON object. So, if you need to set more complex objects, you can just do: 342 | 343 | ```ruby 344 | key :foo, {some_complex: {nested_object: true}} 345 | ``` 346 | 347 | #### Parameter referencing 348 | 349 | It is possible to reference parameters rather than explicitly define them in every action in which they are used. 350 | 351 | To define a reusable parameter, declare it within `swagger_root` 352 | To reference the parameter, call it within a `swagger_path` or `operation` node 353 | 354 | ```ruby 355 | swagger_root do 356 | key :swagger, '2.0' 357 | # ... 358 | parameter :species do 359 | key :name, :species 360 | key :description, 'Species of this pet' 361 | end 362 | end 363 | 364 | swagger_path '/pets/' do 365 | operation :post do 366 | parameter :species 367 | # ... 368 | end 369 | end 370 | ``` 371 | 372 | #### Inline keys 373 | 374 | It is possible to omit numerous `key` calls using inline hash keys on any block. 375 | 376 | All three calls are equivalent: 377 | 378 | ```ruby 379 | parameter do 380 | key :paramType, :path 381 | key :name, :petId 382 | key :description, 'ID of pet that needs to be fetched' 383 | key :type, :string 384 | end 385 | ``` 386 | 387 | ```ruby 388 | parameter paramType: :path, name: :petId do 389 | key :description, 'ID of pet that needs to be fetched' 390 | key :type, :string 391 | end 392 | ``` 393 | 394 | ```ruby 395 | parameter paramType: :path, 396 | name: :petId, 397 | description: 'ID of pet that needs to be fetched', 398 | type: :string 399 | ``` 400 | 401 | These inline keys can be used on any block, not just `parameter` blocks. 402 | 403 | #### Writing JSON to a file 404 | 405 | If you are not serving the JSON directly and need to write it to a file for some reason, you can easily use `build_root_json` for that as well: 406 | 407 | ```Ruby 408 | swagger_data = Swagger::Blocks.build_root_json(SWAGGERED_CLASSES) 409 | File.open('swagger.json', 'w') { |file| file.write(swagger_data.to_json) } 410 | ``` 411 | 412 | #### Overriding attributes 413 | 414 | If certain attributes must be customized on-the-fly, you can merge a hash containing the customized values on the returned JSON. You can wrap ```build_root_json``` inside your own method: 415 | 416 | ```Ruby 417 | def build_and_override_root_json(overrides = {}) 418 | Swagger::Blocks.build_root_json(SWAGGERED_CLASSES).merge(overrides) 419 | end 420 | ``` 421 | 422 | #### Reducing boilerplate 423 | 424 | To create reusable parameters, please see [parameter referencing](#parameter-referencing). 425 | 426 | Most APIs have some common responses for 401s, 404s, etc. Rather than declaring these responses over and over, you can create a reusable module. 427 | 428 | ```ruby 429 | module SwaggerResponses 430 | module AuthenticationError 431 | def self.extended(base) 432 | base.response 401 do 433 | key :description, 'not authorized' 434 | schema do 435 | key :'$ref', :AuthenticationError 436 | end 437 | end 438 | end 439 | end 440 | end 441 | ``` 442 | 443 | Now, you just need to extend it: 444 | 445 | ```ruby 446 | operation :post do 447 | extend SwaggerResponses::AuthenticationError 448 | # ... 449 | response 200 do 450 | # ... 451 | end 452 | end 453 | ``` 454 | 455 | ## Reference 456 | 457 | See the [swagger_v2_blocks_spec.rb](https://github.com/fotinakis/swagger-blocks/blob/master/spec/lib/swagger_v2_blocks_spec.rb) for examples of more complex features and declarations possible. 458 | 459 | ### Swagger 1.2 460 | 461 | The old [Swagger 1.2](https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md) spec is not supported in swagger-blocks >= 2.0.0, but you may use [1.4.0](https://github.com/fotinakis/swagger-blocks/tree/v1.4.0). 462 | 463 | ## Contributing 464 | 465 | 1. Fork it ( https://github.com/fotinakis/swagger-blocks/fork ) 466 | 2. Create your feature branch (`git checkout -b my-new-feature`) 467 | 3. Commit your changes (`git commit -am 'Add some feature'`) 468 | 4. Push to the branch (`git push origin my-new-feature`) 469 | 5. Create a new Pull Request 470 | 471 | Throw a ★ on it! :) 472 | 473 | ## Filing issues 474 | 475 | **Please DO [file an issue](https://github.com/fotinakis/swagger-blocks/issues)**: 476 | 477 | - If you find a bug or some part of the Swagger 2.0 spec that swagger-blocks does not support. 478 | - To propose and discuss a code change before submitting a PR for it. 479 | - To talk about anything related specifically to swagger-blocks, not Swagger itself. 480 | 481 | **Please DO NOT file an issue**: 482 | 483 | - If you have a question about Swagger or Swagger UI. We simply cannot support all Swagger-related questions. Check out the http://swagger.io/community/ for help. 484 | 485 | ## Release notes 486 | 487 | * v2.0.1: Bugfix to allow nested arrays of `items`. 488 | * v2.0.0: Code cleanup, drop support for Swagger 1.2 spec. 489 | * v1.4.0: Allow parameters to be defined once in swagger_root and reused. 490 | * v1.3.4: Fix support for fully-qualified URIs in `$ref` values. 491 | * v1.3.3: Bugfix to allow `parameter` inside `swagger_path`. 492 | * v1.3.2: Bugfix to allow `property` inside `items` for rare extended schema uses. 493 | * v1.3.1: Bugfix to allow nested objects via `property` nested in `property`. 494 | * v1.3.0: Added support for condensed syntax via inline keys on every block. 495 | * v1.2.0: Improved support for `$ref` Path Item Object parameters. 496 | * v1.1.3: Rename tags directive to tag for consistency. 497 | * v1.1.2: Bugfix for security definition support. 498 | * v1.1.1: Bugfix for tags node support. 499 | * v1.1: Support for Swagger 2.0 spec. 500 | * v1.0.1: Make backwards-compatible with Ruby 1.9.3. 501 | * v1.0.0: Initial major release. 502 | 503 | ## Credits 504 | 505 | Thanks to [@ali-graham](https://github.com/ali-graham) for contributing support for Swagger 2.0. 506 | 507 | Original idea inspired by [@richhollis](https://github.com/richhollis/)'s [swagger-docs](https://github.com/richhollis/swagger-docs/) gem. 508 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/swagger/blocks.rb: -------------------------------------------------------------------------------- 1 | require 'swagger/blocks/root' 2 | require 'swagger/blocks/internal_helpers' 3 | require 'swagger/blocks/class_methods' 4 | require 'swagger/blocks/errors' 5 | 6 | module Swagger 7 | module Blocks 8 | autoload :Node, 'swagger/blocks/node' 9 | 10 | module Nodes 11 | autoload :AllOfNode, 'swagger/blocks/nodes/all_of_node' 12 | autoload :CallbackNode, 'swagger/blocks/nodes/callback_node' 13 | autoload :CallbackDestinationNode, 'swagger/blocks/nodes/callback_destination_node' 14 | autoload :CallbackMethodNode, 'swagger/blocks/nodes/callback_method_node' 15 | autoload :ComponentNode, 'swagger/blocks/nodes/component_node' 16 | autoload :ContactNode, 'swagger/blocks/nodes/contact_node' 17 | autoload :ContentNode, 'swagger/blocks/nodes/content_node' 18 | autoload :ExampleNode, 'swagger/blocks/nodes/example_node' 19 | autoload :ExternalDocsNode, 'swagger/blocks/nodes/external_docs_node' 20 | autoload :FlowNode, 'swagger/blocks/nodes/flow_node' 21 | autoload :HeaderNode, 'swagger/blocks/nodes/header_node' 22 | autoload :InfoNode, 'swagger/blocks/nodes/info_node' 23 | autoload :ItemsNode, 'swagger/blocks/nodes/items_node' 24 | autoload :LicenseNode, 'swagger/blocks/nodes/license_node' 25 | autoload :LinkNode, 'swagger/blocks/nodes/link_node' 26 | autoload :LinkParameterNode, 'swagger/blocks/nodes/link_parameter_node' 27 | autoload :OneOfNode, 'swagger/blocks/nodes/one_of_node' 28 | autoload :OperationNode, 'swagger/blocks/nodes/operation_node' 29 | autoload :ParameterNode, 'swagger/blocks/nodes/parameter_node' 30 | autoload :PathNode, 'swagger/blocks/nodes/path_node' 31 | autoload :PropertiesNode, 'swagger/blocks/nodes/properties_node' 32 | autoload :PropertyNode, 'swagger/blocks/nodes/property_node' 33 | autoload :RequestBodyNode, 'swagger/blocks/nodes/request_body_node' 34 | autoload :ResponseNode, 'swagger/blocks/nodes/response_node' 35 | autoload :RootNode, 'swagger/blocks/nodes/root_node' 36 | autoload :SchemaNode, 'swagger/blocks/nodes/schema_node' 37 | autoload :ScopesNode, 'swagger/blocks/nodes/scopes_node' 38 | autoload :SecurityRequirementNode, 'swagger/blocks/nodes/security_requirement_node' 39 | autoload :SecuritySchemeNode, 'swagger/blocks/nodes/security_scheme_node' 40 | autoload :ServerNode, 'swagger/blocks/nodes/server_node' 41 | autoload :TagNode, 'swagger/blocks/nodes/tag_node' 42 | autoload :ValueNode, 'swagger/blocks/nodes/value_node' 43 | autoload :VariableNode, 'swagger/blocks/nodes/variable_node' 44 | autoload :XmlNode, 'swagger/blocks/nodes/xml_node' 45 | autoload :VendorExtensionNode, 'swagger/blocks/nodes/vendor_extension_node' 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/swagger/blocks/class_methods.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module ClassMethods 4 | private 5 | 6 | # v2.0: Defines a Swagger Object 7 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#swagger-object 8 | def swagger_root(inline_keys = nil, &block) 9 | @swagger_root_node ||= Swagger::Blocks::Nodes::RootNode.call(inline_keys: inline_keys, &block) 10 | end 11 | 12 | def version 13 | if defined?(@swagger_root_node) && @swagger_root_node.data[:info] && @swagger_root_node.data[:info].version == '3.0.0' 14 | '3.0.0' 15 | else 16 | '2.0' 17 | end 18 | end 19 | 20 | # v2.0: Defines a Swagger Path Item object 21 | # https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#path-item-object 22 | def swagger_path(path, &block) 23 | path = path.to_sym 24 | 25 | # TODO enforce that path name begins with a '/' 26 | # (or x- , but need to research Vendor Extensions first) 27 | 28 | @swagger_path_node_map ||= {} 29 | 30 | path_node = @swagger_path_node_map[path] 31 | if path_node 32 | # Merge this path declaration into the previous one 33 | path_node.instance_eval(&block) 34 | else 35 | # First time we've seen this path 36 | @swagger_path_node_map[path] = Swagger::Blocks::Nodes::PathNode.call(version: version, &block) 37 | end 38 | end 39 | 40 | # v2.0: Defines a Swagger Definition Schema, 41 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#definitionsObject and 42 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#schema-object 43 | def swagger_schema(name, inline_keys = nil, &block) 44 | @swagger_schema_node_map ||= {} 45 | 46 | schema_node = @swagger_schema_node_map[name] 47 | if schema_node 48 | # Merge this schema_node declaration into the previous one 49 | schema_node.instance_eval(&block) 50 | else 51 | # First time we've seen this schema_node 52 | @swagger_schema_node_map[name] = Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 53 | end 54 | end 55 | 56 | def swagger_component(inline_keys = nil, &block) 57 | @swagger_components_node ||= Swagger::Blocks::Nodes::ComponentNode.call(version: '3.0.0', inline_keys: inline_keys, &block) 58 | end 59 | 60 | def _swagger_nodes 61 | # Avoid initialization warnings. 62 | @swagger_root_node ||= nil 63 | @swagger_path_node_map ||= {} 64 | @swagger_schema_node_map ||= nil 65 | @swagger_api_root_node_map ||= {} 66 | @swagger_models_node ||= nil 67 | @swagger_components_node ||= nil 68 | 69 | data = {root_node: @swagger_root_node} 70 | data[:path_node_map] = @swagger_path_node_map 71 | data[:schema_node_map] = @swagger_schema_node_map 72 | data[:api_node_map] = @swagger_api_root_node_map 73 | data[:models_node] = @swagger_models_node 74 | data[:component_node] = @swagger_components_node 75 | data 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/swagger/blocks/errors.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | class Error < StandardError; end 4 | class DeclarationError < Error; end 5 | class NotFoundError < Error; end 6 | class NotSupportedError < Error; end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/swagger/blocks/internal_helpers.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module InternalHelpers 4 | # Return [root_node, api_node_map] from all of the given swaggered_classes. 5 | def self.parse_swaggered_classes(swaggered_classes) 6 | root_nodes = [] 7 | 8 | api_node_map = {} 9 | models_nodes = [] 10 | 11 | path_node_map = {} 12 | schema_node_map = {} 13 | component_node = nil 14 | 15 | swaggered_classes.each do |swaggered_class| 16 | next unless swaggered_class.respond_to?(:_swagger_nodes, true) 17 | swagger_nodes = swaggered_class.send(:_swagger_nodes) 18 | root_node = swagger_nodes[:root_node] 19 | root_nodes << root_node if root_node 20 | 21 | # 2.0 22 | if swagger_nodes[:path_node_map] 23 | path_node_map.merge!(swagger_nodes[:path_node_map]) 24 | end 25 | if swagger_nodes[:schema_node_map] 26 | schema_node_map.merge!(swagger_nodes[:schema_node_map]) 27 | end 28 | if swagger_nodes[:component_node] 29 | if component_node 30 | merge_components(component_node, swagger_nodes, :examples) 31 | merge_components(component_node, swagger_nodes, :links) 32 | merge_components(component_node, swagger_nodes, :parameters) 33 | merge_components(component_node, swagger_nodes, :requestBodies) 34 | merge_components(component_node, swagger_nodes, :responses) 35 | merge_components(component_node, swagger_nodes, :schemas) 36 | merge_components(component_node, swagger_nodes, :securitySchemes) 37 | else 38 | component_node = swagger_nodes[:component_node] 39 | end 40 | end 41 | end 42 | 43 | data = {root_node: self.limit_root_node(root_nodes)} 44 | 45 | if data[:root_node].is_swagger_2_0? 46 | data[:path_nodes] = path_node_map 47 | data[:schema_nodes] = schema_node_map 48 | elsif data[:root_node].is_openapi_3_0? 49 | data[:path_nodes] = path_node_map 50 | data[:component_node] = component_node 51 | else 52 | data[:api_node_map] = api_node_map 53 | data[:models_nodes] = models_nodes 54 | end 55 | data 56 | end 57 | 58 | def self.merge_components(component_node, swagger_nodes, key) 59 | component_node.data[key] ||= {} 60 | component_node.data[key].merge!(swagger_nodes[:component_node].data[key]) if swagger_nodes[:component_node].data[key] 61 | end 62 | 63 | # Make sure there is exactly one root_node and return it. 64 | # TODO should this merge the contents of the root nodes instead? 65 | def self.limit_root_node(root_nodes) 66 | if root_nodes.length == 0 67 | raise Swagger::Blocks::DeclarationError.new( 68 | 'swagger_root must be declared') 69 | elsif root_nodes.length > 1 70 | raise Swagger::Blocks::DeclarationError.new( 71 | 'Only one swagger_root declaration is allowed.') 72 | end 73 | root_nodes.first 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/swagger/blocks/node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | # Base node for representing every object in the Swagger DSL. 4 | class Node 5 | attr_accessor :name 6 | attr_writer :version 7 | VERSION_2 = '2.0' 8 | VERSION_3 = '3.0.0' 9 | 10 | def self.call(options = {}, &block) 11 | # Create a new instance and evaluate the block into it. 12 | instance = new 13 | instance.name = options[:name] if options[:name] 14 | instance.version = options[:version] 15 | instance.keys options[:inline_keys] 16 | instance.instance_eval(&block) if block 17 | instance 18 | end 19 | 20 | def as_json(options = {}) 21 | version = options.fetch(:version, VERSION_2) 22 | 23 | result = {} 24 | self.data.each do |key, value| 25 | if value.is_a?(Node) 26 | result[key] = value.as_json(version: version) 27 | elsif value.is_a?(Array) 28 | result[key] = [] 29 | value.each do |v| 30 | result[key] << value_as_json(v, version) 31 | end 32 | elsif value.is_a?(Hash) 33 | result[key] = {} 34 | value.each_pair {|k, v| result[key][k] = value_as_json(v, version) } 35 | elsif version == VERSION_2 && ref?(key) && !static_ref?(value) 36 | result[key] = "#/definitions/#{value}" 37 | elsif version == VERSION_3 && ref?(key) && self.is_a?(Swagger::Blocks::Nodes::LinkNode) && !static_ref?(value) 38 | result[key] = "#/components/links/#{value}" 39 | elsif version == VERSION_3 && ref?(key) && self.is_a?(Swagger::Blocks::Nodes::ExampleNode) && !static_ref?(value) 40 | result[key] = "#/components/examples/#{value}" 41 | elsif version == VERSION_3 && ref?(key) && self.is_a?(Swagger::Blocks::Nodes::ParameterNode) && !static_ref?(value) 42 | result[key] = "#/components/parameters/#{value}" 43 | elsif version == VERSION_3 && ref?(key) && self.is_a?(Swagger::Blocks::Nodes::RequestBodyNode) && !static_ref?(value) 44 | result[key] = "#/components/requestBodies/#{value}" 45 | elsif version == VERSION_3 && ref?(key) && self.is_a?(Swagger::Blocks::Nodes::ResponseNode) && !static_ref?(value) 46 | result[key] = "#/components/responses/#{value}" 47 | elsif version == VERSION_3 && ref?(key) && !static_ref?(value) 48 | result[key] = "#/components/schemas/#{value}" 49 | else 50 | result[key] = value 51 | end 52 | end 53 | return result if !name 54 | # If 'name' is given to this node, wrap the data with a root element with the given name. 55 | {name => result} 56 | end 57 | 58 | def ref?(key) 59 | key.to_s.eql?('$ref') 60 | end 61 | 62 | def static_ref?(value) 63 | value.to_s =~ %r{^#/|https?://} 64 | end 65 | 66 | def value_as_json(value, version) 67 | if value.respond_to?(:as_json) 68 | value.as_json(version: version) 69 | else 70 | value 71 | end 72 | end 73 | 74 | def data 75 | @data ||= {} 76 | end 77 | 78 | def keys(data) 79 | self.data.merge!(data) if data 80 | end 81 | 82 | def key(key, value) 83 | self.data[key] = value 84 | end 85 | 86 | def version 87 | return @version if instance_variable_defined?('@version') && @version 88 | return VERSION_2 if data.has_key?(:swagger) && data[:swagger] == VERSION_2 89 | return VERSION_3 if data.has_key?(:openapi) && data[:openapi] == VERSION_3 90 | raise DeclarationError, "You must specify swagger '#{VERSION_2}' or openapi '#{VERSION_3}'" 91 | end 92 | 93 | def is_swagger_2_0? 94 | version == VERSION_2 95 | end 96 | 97 | def is_openapi_3_0? 98 | version == VERSION_3 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/all_of_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class AllOfNode < Node 5 | def as_json(options = {}) 6 | version = options.fetch(:version, '2.0') 7 | result = [] 8 | 9 | self.data.each do |value| 10 | if value.is_a?(Node) 11 | result << value.as_json(version: version) 12 | elsif value.is_a?(Array) 13 | r = [] 14 | value.each { |v| r << value_as_json(v, version) } 15 | result << r 16 | elsif (is_swagger_2_0? || is_openapi_3_0?) && value.is_a?(Hash) 17 | r = {} 18 | value.each_pair {|k, v| r[k] = value_as_json(v, version) } 19 | result << r 20 | else 21 | result = value 22 | end 23 | end 24 | return result if !name 25 | # If 'name' is given to this node, wrap the data with a root element with the given name. 26 | {name => result} 27 | end 28 | 29 | def data 30 | @data ||= [] 31 | end 32 | 33 | def key(key, value) 34 | raise NotSupportedError 35 | end 36 | 37 | def schema(inline_keys = nil, &block) 38 | data << Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/callback_destination_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class CallbackDestinationNode < Node 5 | def method(method_name, inline_keys = nil, &block) 6 | self.data[method_name] = Swagger::Blocks::Nodes::CallbackMethodNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/callback_method_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class CallbackMethodNode < Node 5 | def request_body(inline_keys = nil, &block) 6 | self.data[:requestBody] = Swagger::Blocks::Nodes::RequestBodyNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | 9 | def response(resp, inline_keys = nil, &block) 10 | self.data[:responses] ||= {} 11 | self.data[:responses][resp] = Swagger::Blocks::Nodes::ResponseNode.call(version: version, inline_keys: inline_keys, &block) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/callback_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class CallbackNode < Node 5 | def destination(address, inline_keys = nil, &block) 6 | self.data[address] = Swagger::Blocks::Nodes::CallbackDestinationNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/component_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ComponentNode < Node 5 | def schema(name, inline_keys = nil, &block) 6 | self.data[:schemas] ||= {} 7 | schema_node = self.data[:schemas][name] 8 | 9 | if schema_node 10 | # Merge this schema_node declaration into the previous one 11 | schema_node.instance_eval(&block) 12 | else 13 | # First time we've seen this schema_node 14 | self.data[:schemas][name] = Swagger::Blocks::Nodes::SchemaNode.call(version: '3.0.0', inline_keys: inline_keys, &block) 15 | end 16 | end 17 | 18 | def link(name, inline_keys = nil, &block) 19 | self.data[:links] ||= {} 20 | self.data[:links][name] = Swagger::Blocks::Nodes::LinkNode.call(version: version, inline_keys: inline_keys, &block) 21 | end 22 | 23 | def example(name, inline_keys = nil, &block) 24 | self.data[:examples] ||= {} 25 | self.data[:examples][name] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 26 | end 27 | 28 | def security_scheme(name, inline_keys = nil, &block) 29 | self.data[:securitySchemes] ||= {} 30 | self.data[:securitySchemes][name] = Swagger::Blocks::Nodes::SecuritySchemeNode.call(version: version, inline_keys: inline_keys, &block) 31 | end 32 | 33 | def parameter(name, inline_keys = nil, &block) 34 | self.data[:parameters] ||= {} 35 | self.data[:parameters][name] = Swagger::Blocks::Nodes::ParameterNode.call(version: version, inline_keys: inline_keys, &block) 36 | end 37 | 38 | def request_body(name, inline_keys = nil, &block) 39 | self.data[:requestBodies] ||= {} 40 | self.data[:requestBodies][name] = Swagger::Blocks::Nodes::RequestBodyNode.call(version: version, inline_keys: inline_keys, &block) 41 | end 42 | 43 | def response(name, inline_keys = nil, &block) 44 | self.data[:responses] ||= {} 45 | self.data[:responses][name] = Swagger::Blocks::Nodes::ResponseNode.call(version: version, inline_keys: inline_keys, &block) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/contact_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#contact-object 5 | class ContactNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/content_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ContentNode < Node 5 | def schema(inline_keys = nil, &block) 6 | self.data[:schema] = Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | 9 | def example(name = nil, inline_keys = nil, &block) 10 | if name.nil? 11 | self.data[:example] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 12 | else 13 | self.data[:examples] ||= {} 14 | self.data[:examples][name] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/example_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ExampleNode < Node 5 | def value(inline_keys = nil, &block) 6 | self.data[:value] = Swagger::Blocks::Nodes::ValueNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/external_docs_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#externalDocumentationObject 5 | class ExternalDocsNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/flow_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class FlowNode < Node 5 | def scopes(inline_keys = nil, &block) 6 | self.data[:scopes] = Swagger::Blocks::Nodes::ScopesNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/header_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#headerObject 5 | class HeaderNode < Node 6 | def items(inline_keys = nil, &block) 7 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def schema(inline_keys = nil, &block) 11 | self.data[:schema] = Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/info_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#infoObject 5 | class InfoNode < Node 6 | def contact(inline_keys = nil, &block) 7 | self.data[:contact] = Swagger::Blocks::Nodes::ContactNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def license(inline_keys = nil, &block) 11 | self.data[:license] = Swagger::Blocks::Nodes::LicenseNode.call(version: version, inline_keys: inline_keys, &block) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/items_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: 5 | class ItemsNode < Node 6 | def property(name, inline_keys = nil, &block) 7 | self.data[:properties] ||= Swagger::Blocks::Nodes::PropertiesNode.new 8 | self.data[:properties].version = version 9 | self.data[:properties].property(name, inline_keys, &block) 10 | end 11 | 12 | def items(inline_keys = nil, &block) 13 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/license_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#license-object 5 | class LicenseNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/link_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class LinkNode < Node 5 | def parameters(inline_keys = nil, &block) 6 | self.data[:parameters] ||= Swagger::Blocks::Nodes::LinkParameterNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/link_parameter_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class LinkParameterNode < Node 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/one_of_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class OneOfNode < Node 5 | def items(inline_keys = nil, &block) 6 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/operation_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#operation-object 5 | class OperationNode < Node 6 | def parameter(inline_keys = nil, &block) 7 | inline_keys = {'$ref' => "#/parameters/#{inline_keys}"} if inline_keys.is_a?(Symbol) 8 | 9 | self.data[:parameters] ||= [] 10 | self.data[:parameters] << Swagger::Blocks::Nodes::ParameterNode.call(version: version, inline_keys: inline_keys, &block) 11 | end 12 | 13 | def response(resp, inline_keys = nil, &block) 14 | # TODO validate 'resp' is as per spec 15 | self.data[:responses] ||= {} 16 | self.data[:responses][resp] = Swagger::Blocks::Nodes::ResponseNode.call(version: version, inline_keys: inline_keys, &block) 17 | end 18 | 19 | def externalDocs(inline_keys = nil, &block) 20 | self.data[:externalDocs] = Swagger::Blocks::Nodes::ExternalDocsNode.call(version: version, inline_keys: inline_keys, &block) 21 | end 22 | 23 | def security(inline_keys = nil, &block) 24 | self.data[:security] ||= [] 25 | self.data[:security] << Swagger::Blocks::Nodes::SecurityRequirementNode.call(version: version, inline_keys: inline_keys, &block) 26 | end 27 | 28 | def request_body(inline_keys = nil, &block) 29 | self.data[:requestBody] = Swagger::Blocks::Nodes::RequestBodyNode.call(version: version, inline_keys: inline_keys, &block) 30 | end 31 | 32 | def callback(event_name, inline_keys = nil, &block) 33 | self.data[:callbacks] ||= {} 34 | self.data[:callbacks][event_name] = Swagger::Blocks::Nodes::CallbackNode.call(version: version, inline_keys: inline_keys, &block) 35 | end 36 | 37 | def server(inline_keys = nil, &block) 38 | raise NotSupportedError unless is_openapi_3_0? 39 | self.data[:servers] ||= [] 40 | self.data[:servers] << Swagger::Blocks::Nodes::ServerNode.call(version: version, inline_keys: inline_keys, &block) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/parameter_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#parameter-object 5 | class ParameterNode < Node 6 | def schema(inline_keys = nil, &block) 7 | self.data[:schema] = Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def items(inline_keys = nil, &block) 11 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 12 | end 13 | 14 | def example(name, inline_keys = nil, &block) 15 | self.data[:examples] ||= {} 16 | self.data[:examples][name] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/path_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#path-item-object 5 | class PathNode < Node 6 | OPERATION_TYPES = [:get, :put, :post, :delete, :options, :head, :patch].freeze 7 | 8 | # TODO support ^x- Vendor Extensions 9 | def operation(op, inline_keys = nil, &block) 10 | op = op.to_sym 11 | raise ArgumentError.new("#{name} not in #{OPERATION_TYPES}") if !OPERATION_TYPES.include?(op) 12 | self.data[op] = Swagger::Blocks::Nodes::OperationNode.call(version: version, inline_keys: inline_keys, &block) 13 | end 14 | 15 | def parameter(inline_keys = nil, &block) 16 | inline_keys = {'$ref' => "#/parameters/#{inline_keys}"} if inline_keys.is_a?(Symbol) 17 | 18 | self.data[:parameters] ||= [] 19 | self.data[:parameters] << Swagger::Blocks::Nodes::ParameterNode.call(version: version, inline_keys: inline_keys, &block) 20 | end 21 | 22 | def server(inline_keys = nil, &block) 23 | raise NotSupportedError unless is_openapi_3_0? 24 | self.data[:servers] ||= [] 25 | self.data[:servers] << Swagger::Blocks::Nodes::ServerNode.call(version: version, inline_keys: inline_keys, &block) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/properties_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class PropertiesNode < Node 5 | def property(name, inline_keys = nil, &block) 6 | self.data[name] = Swagger::Blocks::Nodes::PropertyNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/property_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class PropertyNode < Node 5 | def items(inline_keys = nil, &block) 6 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 7 | end 8 | 9 | def property(name, inline_keys = nil, &block) 10 | self.data[:properties] ||= Swagger::Blocks::Nodes::PropertiesNode.new 11 | self.data[:properties].version = version 12 | self.data[:properties].property(name, inline_keys, &block) 13 | end 14 | 15 | def one_of(&block) 16 | self.data[:oneOf] ||= [] 17 | self.data[:oneOf] << Swagger::Blocks::Nodes::OneOfNode.call(version: version, &block) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/request_body_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class RequestBodyNode < Node 5 | def content(type, inline_keys = nil, &block) 6 | self.data[:content] ||= {} 7 | self.data[:content][type] = Swagger::Blocks::Nodes::ContentNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/response_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#responseObject 5 | class ResponseNode < Node 6 | def schema(inline_keys = nil, &block) 7 | self.data[:schema] = Swagger::Blocks::Nodes::SchemaNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def header(head, inline_keys = nil, &block) 11 | # TODO validate 'head' is as per spec 12 | self.data[:headers] ||= {} 13 | self.data[:headers][head] = Swagger::Blocks::Nodes::HeaderNode.call(version: version, inline_keys: inline_keys, &block) 14 | end 15 | 16 | def content(type, inline_keys = nil, &block) 17 | self.data[:content] ||= {} 18 | self.data[:content][type] = Swagger::Blocks::Nodes::ContentNode.call(version: version, inline_keys: inline_keys, &block) 19 | end 20 | 21 | def example(name = nil, inline_keys = nil, &block) 22 | if name.nil? 23 | self.data[:example] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 24 | else 25 | self.data[:examples] ||= {} 26 | self.data[:examples][name] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 27 | end 28 | end 29 | 30 | def link(name, inline_keys = nil, &block) 31 | self.data[:links] ||= {} 32 | self.data[:links][name] = Swagger::Blocks::Nodes::LinkNode.call(version: version, inline_keys: inline_keys, &block) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/root_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class RootNode < Node 5 | 6 | def info(inline_keys = nil, &block) 7 | self.data[:info] = Swagger::Blocks::Nodes::InfoNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def parameter(param, inline_keys = nil, &block) 11 | raise NotSupportedError unless is_swagger_2_0? 12 | 13 | # TODO validate 'param' is as per spec 14 | self.data[:parameters] ||= {} 15 | self.data[:parameters][param] = Swagger::Blocks::Nodes::ParameterNode.call(version: version, inline_keys: inline_keys, &block) 16 | end 17 | 18 | def response(resp, inline_keys = nil, &block) 19 | raise NotSupportedError unless is_swagger_2_0? 20 | 21 | # TODO validate 'resp' is as per spec 22 | self.data[:responses] ||= {} 23 | self.data[:responses][resp] = Swagger::Blocks::Nodes::ResponseNode.call(version: version, inline_keys: inline_keys, &block) 24 | end 25 | 26 | def security_definition(name, inline_keys = nil, &block) 27 | raise NotSupportedError unless is_swagger_2_0? 28 | 29 | self.data[:securityDefinitions] ||= {} 30 | self.data[:securityDefinitions][name] = Swagger::Blocks::Nodes::SecuritySchemeNode.call(version: version, inline_keys: inline_keys, &block) 31 | end 32 | 33 | def security(inline_keys = nil, &block) 34 | raise NotSupportedError unless is_swagger_2_0? || is_openapi_3_0? 35 | 36 | self.data[:security] ||= [] 37 | self.data[:security] << Swagger::Blocks::Nodes::SecurityRequirementNode.call(version: version, inline_keys: inline_keys, &block) 38 | end 39 | 40 | def externalDocs(inline_keys = nil, &block) 41 | self.data[:externalDocs] = Swagger::Blocks::Nodes::ExternalDocsNode.call(version: version, inline_keys: inline_keys, &block) 42 | end 43 | 44 | def tag(inline_keys = nil, &block) 45 | raise NotSupportedError unless is_swagger_2_0? || is_openapi_3_0? 46 | 47 | self.data[:tags] ||= [] 48 | self.data[:tags] << Swagger::Blocks::Nodes::TagNode.call(version: version, inline_keys: inline_keys, &block) 49 | end 50 | 51 | def server(inline_keys = nil, &block) 52 | raise NotSupportedError unless is_openapi_3_0? 53 | 54 | self.data[:servers] ||= [] 55 | self.data[:servers] << Swagger::Blocks::Nodes::ServerNode.call(version: version, inline_keys: inline_keys, &block) 56 | end 57 | 58 | def extension(name, inline_keys = nil, &block) 59 | raise NotSupportedError unless is_openapi_3_0? 60 | 61 | self.data[name] ||= [] 62 | self.data[name] << Swagger::Blocks::Nodes::VendorExtensionNode.call(version: version, inline_keys: inline_keys, &block) 63 | end 64 | 65 | # Use 'tag' instead. 66 | # @deprecated 67 | alias_method :tags, :tag 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/schema_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#schema-object 5 | class SchemaNode < Node 6 | def items(inline_keys = nil, &block) 7 | self.data[:items] = Swagger::Blocks::Nodes::ItemsNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | 10 | def allOf(&block) 11 | self.data[:allOf] = Swagger::Blocks::Nodes::AllOfNode.call(version: version, &block) 12 | end 13 | 14 | def property(name, inline_keys = nil, &block) 15 | self.data[:properties] ||= Swagger::Blocks::Nodes::PropertiesNode.new 16 | self.data[:properties].version = version 17 | self.data[:properties].property(name, inline_keys, &block) 18 | end 19 | 20 | def xml(inline_keys = nil, &block) 21 | self.data[:xml] = Swagger::Blocks::Nodes::XmlNode.call(version: version, inline_keys: inline_keys, &block) 22 | end 23 | 24 | def externalDocs(inline_keys = nil, &block) 25 | self.data[:externalDocs] = Swagger::Blocks::Nodes::ExternalDocsNode.call(version: version, inline_keys: inline_keys, &block) 26 | end 27 | 28 | def example(inline_keys = nil, &block) 29 | self.data[:example] = Swagger::Blocks::Nodes::ExampleNode.call(version: version, inline_keys: inline_keys, &block) 30 | end 31 | 32 | def one_of(&block) 33 | self.data[:oneOf] ||= [] 34 | self.data[:oneOf] << Swagger::Blocks::Nodes::OneOfNode.call(version: version, &block) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/scopes_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ScopesNode < Node 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/security_requirement_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#securityRequirementObject 5 | class SecurityRequirementNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/security_scheme_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class SecuritySchemeNode < Node 5 | # TODO support ^x- Vendor Extensions 6 | 7 | def scopes(inline_keys = nil, &block) 8 | self.data[:scopes] = Swagger::Blocks::Nodes::ScopesNode.call(version: version, inline_keys: inline_keys, &block) 9 | end 10 | 11 | def flow(name, inline_keys = nil, &block) 12 | self.data[:flows] ||= {} 13 | self.data[:flows][name] = Swagger::Blocks::Nodes::FlowNode.call(version: version, inline_keys: inline_keys, &block) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/server_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ServerNode < Node 5 | def variable(name, inline_keys = nil, &block) 6 | self.data[:variables] ||= {} 7 | self.data[:variables][name] = Swagger::Blocks::Nodes::VariableNode.call(version: version, inline_keys: inline_keys, &block) 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/tag_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#tag-object 5 | class TagNode < Node 6 | 7 | # TODO support ^x- Vendor Extensions 8 | 9 | def externalDocs(inline_keys = nil, &block) 10 | self.data[:externalDocs] = Swagger::Blocks::Nodes::ExternalDocsNode.call(version: version, inline_keys: inline_keys, &block) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/value_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class ValueNode < Node 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/variable_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | class VariableNode < Node 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/vendor_extension_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v3.0: https://swagger.io/docs/specification/openapi-extensions/ 5 | class VendorExtensionNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/nodes/xml_node.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | module Nodes 4 | # v2.0: 5 | class XmlNode < Node 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/swagger/blocks/root.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'swagger/blocks/version' 3 | 4 | module Swagger 5 | module Blocks 6 | 7 | # Inject the swagger_root, swagger_api_root, and swagger_model class methods. 8 | def self.included(base) 9 | base.extend(ClassMethods) 10 | end 11 | 12 | def self.build_root_json(swaggered_classes) 13 | data = Swagger::Blocks::InternalHelpers.parse_swaggered_classes(swaggered_classes) 14 | 15 | if data[:root_node].is_swagger_2_0? 16 | data[:root_node].key(:paths, data[:path_nodes]) # Required, so no empty check. 17 | if data[:schema_nodes] && !data[:schema_nodes].empty? 18 | data[:root_node].key(:definitions, data[:schema_nodes]) 19 | end 20 | end 21 | 22 | if data[:root_node].is_openapi_3_0? 23 | data[:root_node].key(:paths, data[:path_nodes]) # Required, so no empty check. 24 | if data[:component_node] && !data[:component_node].data.empty? 25 | data[:root_node].key(:components, data[:component_node]) 26 | end 27 | end 28 | 29 | data[:root_node].as_json(version: data[:root_node].version) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/swagger/blocks/version.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Blocks 3 | VERSION = '3.0.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/lib/swagger_v2_api_declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "host": "petstore.swagger.wordnik.com", 16 | "basePath": "/api", 17 | "schemes": [ 18 | "http" 19 | ], 20 | "consumes": [ 21 | "application/json" 22 | ], 23 | "produces": [ 24 | "application/json" 25 | ], 26 | "securityDefinitions": { 27 | "api_key": { 28 | "type": "apiKey", 29 | "name": "api_key", 30 | "in": "header" 31 | }, 32 | "petstore_auth": { 33 | "type": "oauth2", 34 | "authorizationUrl": "http://swagger.io/api/oauth/dialog", 35 | "flow": "implicit", 36 | "scopes": { 37 | "write:pets": "modify pets in your account", 38 | "read:pets": "read your pets" 39 | } 40 | } 41 | }, 42 | "externalDocs": { 43 | "description": "Find more info here", 44 | "url": "https://swagger.io" 45 | }, 46 | "tags": [ 47 | { 48 | "name": "pet", 49 | "description": "Pets operations", 50 | "externalDocs": { 51 | "description": "Find more info here", 52 | "url": "https://swagger.io" 53 | } 54 | } 55 | ], 56 | "parameters": { 57 | "species": { 58 | "name": "species", 59 | "in": "body", 60 | "description": "Species of this pet", 61 | "type": "string" 62 | } 63 | }, 64 | "paths": { 65 | "/pets": { 66 | "get": { 67 | "description": "Returns all pets from the system that the user has access to", 68 | "operationId": "findPets", 69 | "produces": [ 70 | "application/json", 71 | "application/xml", 72 | "text/xml", 73 | "text/html" 74 | ], 75 | "parameters": [ 76 | { 77 | "name": "tags", 78 | "in": "query", 79 | "description": "tags to filter by", 80 | "required": false, 81 | "type": "array", 82 | "items": { 83 | "type": "string" 84 | }, 85 | "collectionFormat": "csv" 86 | }, 87 | { 88 | "name": "limit", 89 | "in": "query", 90 | "description": "maximum number of results to return", 91 | "required": false, 92 | "type": "integer", 93 | "format": "int32" 94 | } 95 | ], 96 | "responses": { 97 | "200": { 98 | "description": "pet response", 99 | "schema": { 100 | "type": "array", 101 | "items": { 102 | "$ref": "#/definitions/Pet" 103 | } 104 | } 105 | }, 106 | "default": { 107 | "description": "unexpected error", 108 | "schema": { 109 | "$ref": "#/definitions/ErrorModel" 110 | } 111 | } 112 | } 113 | }, 114 | "post": { 115 | "description": "Creates a new pet in the store. Duplicates are allowed", 116 | "operationId": "addPet", 117 | "produces": [ 118 | "application/json" 119 | ], 120 | "parameters": [ 121 | { 122 | "name": "pet", 123 | "in": "body", 124 | "description": "Pet to add to the store", 125 | "required": true, 126 | "schema": { 127 | "$ref": "#/definitions/PetInput" 128 | } 129 | }, 130 | { 131 | "$ref": "#/parameters/species" 132 | } 133 | ], 134 | "responses": { 135 | "200": { 136 | "description": "pet response", 137 | "schema": { 138 | "$ref": "#/parameters/Pet" 139 | } 140 | }, 141 | "default": { 142 | "description": "unexpected error", 143 | "schema": { 144 | "$ref": "http://example.com/schema.json#/definitions/ErrorModel" 145 | } 146 | } 147 | } 148 | } 149 | }, 150 | "/pets/{id}": { 151 | "parameters": [ 152 | { 153 | "name": "id", 154 | "in": "path", 155 | "description": "ID of pet", 156 | "required": true, 157 | "type": "integer", 158 | "format": "int64" 159 | } 160 | ], 161 | "get": { 162 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 163 | "operationId": "findPetById", 164 | "produces": [ 165 | "application/json", 166 | "application/xml", 167 | "text/xml", 168 | "text/html" 169 | ], 170 | "responses": { 171 | "200": { 172 | "description": "pet response", 173 | "schema": { 174 | "$ref": "#/definitions/Pet" 175 | } 176 | }, 177 | "default": { 178 | "description": "unexpected error", 179 | "schema": { 180 | "$ref": "#/definitions/ErrorModel" 181 | } 182 | } 183 | }, 184 | "security": [ 185 | { 186 | "api_key": [] 187 | }, 188 | { 189 | "petstore_auth": [ 190 | "write:pets", 191 | "read:pets" 192 | ] 193 | } 194 | ] 195 | }, 196 | "put": { 197 | "description": "Update a pet in the store.", 198 | "operationId": "updatePet", 199 | "produces": [ 200 | "application/json" 201 | ], 202 | "parameters": [ 203 | { 204 | "name": "pet", 205 | "in": "body", 206 | "description": "Pet to update in the store", 207 | "required": true, 208 | "schema": { 209 | "$ref": "#/definitions/PetInput" 210 | } 211 | }, 212 | { 213 | "$ref": "#/parameters/species" 214 | } 215 | ], 216 | "responses": { 217 | "200": { 218 | "description": "pet response", 219 | "schema": { 220 | "$ref": "#/parameters/Pet" 221 | } 222 | }, 223 | "default": { 224 | "description": "unexpected error", 225 | "schema": { 226 | "$ref": "http://example.com/schema.json#/definitions/ErrorModel" 227 | } 228 | } 229 | } 230 | }, 231 | "delete": { 232 | "description": "deletes a single pet based on the ID supplied", 233 | "operationId": "deletePet", 234 | "responses": { 235 | "204": { 236 | "description": "pet deleted" 237 | }, 238 | "default": { 239 | "description": "unexpected error", 240 | "schema": { 241 | "$ref": "#/definitions/ErrorModel" 242 | } 243 | } 244 | } 245 | } 246 | } 247 | }, 248 | "definitions": { 249 | "Pet": { 250 | "required": [ 251 | "id", 252 | "name" 253 | ], 254 | "properties": { 255 | "id": { 256 | "type": "integer", 257 | "format": "int64" 258 | }, 259 | "name": { 260 | "type": "string" 261 | }, 262 | "colors": { 263 | "type": "array", 264 | "items": { 265 | "type": "string" 266 | } 267 | } 268 | } 269 | }, 270 | "PetInput": { 271 | "allOf": [ 272 | { 273 | "$ref": "#/definitions/Pet" 274 | }, 275 | { 276 | "required": [ 277 | "name" 278 | ], 279 | "properties": { 280 | "id": { 281 | "type": "integer", 282 | "format": "int64" 283 | }, 284 | "name": { 285 | "type": "string" 286 | }, 287 | "nestedObject": { 288 | "type": "object", 289 | "properties": { 290 | "name": { 291 | "type": "string" 292 | } 293 | } 294 | }, 295 | "arrayOfObjects": { 296 | "type": "array", 297 | "items": { 298 | "type": "object", 299 | "properties": { 300 | "name": { 301 | "type": "string" 302 | }, 303 | "age": { 304 | "type": "integer" 305 | } 306 | } 307 | } 308 | }, 309 | "arrayOfArrays": { 310 | "type": "array", 311 | "items": { 312 | "type": "array", 313 | "items": { 314 | "type": "integer" 315 | } 316 | } 317 | } 318 | } 319 | } 320 | ] 321 | }, 322 | "ErrorModel": { 323 | "required": [ 324 | "code", 325 | "message" 326 | ], 327 | "properties": { 328 | "code": { 329 | "type": "integer", 330 | "format": "int32" 331 | }, 332 | "message": { 333 | "type": "string" 334 | } 335 | } 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /spec/lib/swagger_v2_blocks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'swagger/blocks' 3 | 4 | # TODO Test data originally based on the Swagger UI example data 5 | 6 | RESOURCE_LISTING_JSON_V2 = open(File.expand_path('../swagger_v2_api_declaration.json', __FILE__)).read 7 | 8 | class PetControllerV2 9 | include Swagger::Blocks 10 | 11 | swagger_root host: 'petstore.swagger.wordnik.com' do 12 | key :swagger, '2.0' 13 | info version: '1.0.0' do 14 | key :title, 'Swagger Petstore' 15 | key :description, 'A sample API that uses a petstore as an example to ' \ 16 | 'demonstrate features in the swagger-2.0 specification' 17 | key :termsOfService, 'http://helloreverb.com/terms/' 18 | contact do 19 | key :name, 'Wordnik API Team' 20 | end 21 | license do 22 | key :name, 'MIT' 23 | end 24 | end 25 | key :basePath, '/api' 26 | key :schemes, ['http'] 27 | key :consumes, ['application/json'] 28 | key :produces, ['application/json'] 29 | security_definition :api_key, type: :apiKey do 30 | key :name, :api_key 31 | key :in, :header 32 | end 33 | security_definition :petstore_auth do 34 | key :type, :oauth2 35 | key :authorizationUrl, 'http://swagger.io/api/oauth/dialog' 36 | key :flow, :implicit 37 | scopes 'write:pets' => 'modify pets in your account' do 38 | key 'read:pets', 'read your pets' 39 | end 40 | end 41 | externalDocs description: 'Find more info here' do 42 | key :url, 'https://swagger.io' 43 | end 44 | tag name: 'pet' do 45 | key :description, 'Pets operations' 46 | externalDocs description: 'Find more info here' do 47 | key :url, 'https://swagger.io' 48 | end 49 | end 50 | parameter :species do 51 | key :name, :species 52 | key :in, :body 53 | key :description, 'Species of this pet' 54 | key :type, :string 55 | end 56 | end 57 | 58 | swagger_path '/pets' do 59 | operation :get do 60 | key :description, 'Returns all pets from the system that the user has access to' 61 | key :operationId, 'findPets' 62 | key :produces, [ 63 | 'application/json', 64 | 'application/xml', 65 | 'text/xml', 66 | 'text/html', 67 | ] 68 | parameter do 69 | key :name, :tags 70 | key :in, :query 71 | key :description, 'tags to filter by' 72 | key :required, false 73 | key :type, :array 74 | items do 75 | key :type, :string 76 | end 77 | key :collectionFormat, :csv 78 | end 79 | parameter do 80 | key :name, :limit 81 | key :in, :query 82 | key :description, 'maximum number of results to return' 83 | key :required, false 84 | key :type, :integer 85 | key :format, :int32 86 | end 87 | response 200 do 88 | key :description, 'pet response' 89 | schema type: :array do 90 | items do 91 | key :'$ref', :Pet 92 | end 93 | end 94 | end 95 | response :default do 96 | key :description, 'unexpected error' 97 | schema do 98 | key :'$ref', :ErrorModel 99 | end 100 | end 101 | end 102 | operation :post do 103 | key :description, 'Creates a new pet in the store. Duplicates are allowed' 104 | key :operationId, 'addPet' 105 | key :produces, [ 106 | 'application/json' 107 | ] 108 | parameter do 109 | key :name, :pet 110 | key :in, :body 111 | key :description, 'Pet to add to the store' 112 | key :required, true 113 | schema do 114 | key :'$ref', :PetInput 115 | end 116 | end 117 | parameter :species 118 | response 200 do 119 | key :description, 'pet response' 120 | schema do 121 | # Wrong form here, but checks that #/ strings are not transformed. 122 | key :'$ref', '#/parameters/Pet' 123 | end 124 | end 125 | response :default, description: 'unexpected error' do 126 | schema do 127 | key :'$ref', 'http://example.com/schema.json#/definitions/ErrorModel' 128 | end 129 | end 130 | end 131 | end 132 | 133 | swagger_path '/pets/{id}' do 134 | parameter do 135 | key :name, :id 136 | key :in, :path 137 | key :description, 'ID of pet' 138 | key :required, true 139 | key :type, :integer 140 | key :format, :int64 141 | end 142 | operation :put do 143 | key :description, 'Update a pet in the store.' 144 | key :operationId, 'updatePet' 145 | key :produces, [ 146 | 'application/json' 147 | ] 148 | parameter do 149 | key :name, :pet 150 | key :in, :body 151 | key :description, 'Pet to update in the store' 152 | key :required, true 153 | schema do 154 | key :'$ref', :PetInput 155 | end 156 | end 157 | 158 | parameter :species 159 | 160 | response 200 do 161 | key :description, 'pet response' 162 | schema do 163 | # Wrong form here, but checks that #/ strings are not transformed. 164 | key :'$ref', '#/parameters/Pet' 165 | end 166 | end 167 | response :default, description: 'unexpected error' do 168 | schema do 169 | key :'$ref', 'http://example.com/schema.json#/definitions/ErrorModel' 170 | end 171 | end 172 | end 173 | operation :get do 174 | key :description, 'Returns a user based on a single ID, if the user does not have access to the pet' 175 | key :operationId, 'findPetById' 176 | key :produces, [ 177 | 'application/json', 178 | 'application/xml', 179 | 'text/xml', 180 | 'text/html', 181 | ] 182 | response 200 do 183 | key :description, 'pet response' 184 | schema do 185 | key :'$ref', :Pet 186 | end 187 | end 188 | response :default do 189 | key :description, 'unexpected error' 190 | schema do 191 | key :'$ref', :ErrorModel 192 | end 193 | end 194 | security api_key: [] 195 | security do 196 | key :petstore_auth, ['write:pets', 'read:pets'] 197 | end 198 | end 199 | operation :delete do 200 | key :description, 'deletes a single pet based on the ID supplied' 201 | key :operationId, 'deletePet' 202 | response 204 do 203 | key :description, 'pet deleted' 204 | end 205 | response :default do 206 | key :description, 'unexpected error' 207 | schema do 208 | key :'$ref', :ErrorModel 209 | end 210 | end 211 | end 212 | end 213 | 214 | end 215 | 216 | class PetV2 217 | include Swagger::Blocks 218 | 219 | swagger_schema :Pet, required: [:id, :name] do 220 | property :id do 221 | key :type, :integer 222 | key :format, :int64 223 | end 224 | property :name do 225 | key :type, :string 226 | end 227 | property :colors do 228 | key :type, :array 229 | items do 230 | key :type, :string 231 | end 232 | end 233 | end 234 | 235 | swagger_schema :PetInput do 236 | allOf do 237 | schema do 238 | key :'$ref', :Pet 239 | end 240 | schema do 241 | key :required, [:name] 242 | property :id do 243 | key :type, :integer 244 | key :format, :int64 245 | end 246 | property :name do 247 | key :type, :string 248 | end 249 | property :nestedObject do 250 | key :type, :object 251 | property :name do 252 | key :type, :string 253 | end 254 | end 255 | property :arrayOfObjects do 256 | key :type, :array 257 | items do 258 | key :type, :object 259 | property :name do 260 | key :type, :string 261 | end 262 | property :age do 263 | key :type, :integer 264 | end 265 | end 266 | end 267 | property :arrayOfArrays do 268 | key :type, :array 269 | items do 270 | key :type, :array 271 | items do 272 | key :type, :integer 273 | end 274 | end 275 | end 276 | end 277 | end 278 | end 279 | end 280 | 281 | class ErrorModelV2 282 | include Swagger::Blocks 283 | 284 | swagger_schema :ErrorModel do 285 | key :required, [:code, :message] 286 | property :code do 287 | key :type, :integer 288 | key :format, :int32 289 | end 290 | property :message do 291 | key :type, :string 292 | end 293 | end 294 | end 295 | 296 | describe 'Swagger::Blocks v2' do 297 | describe 'build_json' do 298 | it 'outputs the correct data' do 299 | swaggered_classes = [ 300 | PetControllerV2, 301 | PetV2, 302 | ErrorModelV2 303 | ] 304 | actual = Swagger::Blocks.build_root_json(swaggered_classes) 305 | actual = JSON.parse(actual.to_json) # For access consistency. 306 | data = JSON.parse(RESOURCE_LISTING_JSON_V2) 307 | 308 | # Multiple expectations for better test diff output. 309 | expect(actual['info']).to eq(data['info']) 310 | expect(actual['paths']).to be 311 | expect(actual['paths']['/pets']).to be 312 | expect(actual['paths']['/pets']).to eq(data['paths']['/pets']) 313 | expect(actual['paths']['/pets/{id}']).to be 314 | expect(actual['paths']['/pets/{id}']['get']).to be 315 | expect(actual['paths']['/pets/{id}']['get']).to eq(data['paths']['/pets/{id}']['get']) 316 | expect(actual['paths']).to eq(data['paths']) 317 | expect(actual['definitions']).to eq(data['definitions']) 318 | expect(actual).to eq(data) 319 | end 320 | it 'is idempotent' do 321 | swaggered_classes = [PetControllerV2, PetV2, ErrorModelV2] 322 | actual = JSON.parse(Swagger::Blocks.build_root_json(swaggered_classes).to_json) 323 | data = JSON.parse(RESOURCE_LISTING_JSON_V2) 324 | expect(actual).to eq(data) 325 | end 326 | it 'errors if no swagger_root is declared' do 327 | expect { 328 | Swagger::Blocks.build_root_json([]) 329 | }.to raise_error(Swagger::Blocks::DeclarationError) 330 | end 331 | it 'errors if multiple swagger_roots are declared' do 332 | expect { 333 | Swagger::Blocks.build_root_json([PetControllerV2, PetControllerV2]) 334 | }.to raise_error(Swagger::Blocks::DeclarationError) 335 | end 336 | end 337 | end 338 | -------------------------------------------------------------------------------- /spec/lib/swagger_v3_api_declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "version": "1.0.1", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "servers": [ 16 | { 17 | "url": "http://petstore.swagger.io/v1", 18 | "description": "Petstore API" 19 | }, 20 | { 21 | "url": "https://{subdomain}.site.com/{version}", 22 | "description": "The main prod server", 23 | "variables": { 24 | "subdomain": { 25 | "default": "production" 26 | }, 27 | "version": { 28 | "enum": [ 29 | "v1", 30 | "v2" 31 | ], 32 | "default": "v2" 33 | } 34 | } 35 | } 36 | ], 37 | "security": [ 38 | { 39 | "ApiKeyAuth": [] 40 | }, 41 | { 42 | "OAuth2": [ 43 | "read", 44 | "write" 45 | ] 46 | } 47 | ], 48 | "x-tagGroups": [ 49 | { 50 | "name": "Pets", 51 | "tags": ["dogs", "cats"] 52 | } 53 | ], 54 | "tags": [ 55 | { 56 | "name": "dogs", 57 | "description": "Dogs" 58 | }, 59 | { 60 | "name": "cats", 61 | "description": "Cats" 62 | } 63 | ], 64 | "paths": { 65 | "/pets": { 66 | "description": "Perform actions on pet resources", 67 | "servers": [ 68 | { 69 | "url": "http://petstore.swagger.io/", 70 | "description": "Petstore API (without version prefix)" 71 | } 72 | ], 73 | "get": { 74 | "summary": "List all pets", 75 | "operationId": "listPets", 76 | "tags": [ 77 | "pets" 78 | ], 79 | "parameters": [ 80 | { 81 | "name": "limit", 82 | "in": "query", 83 | "description": "How many items to return at one time (max 100)", 84 | "required": false, 85 | "schema": { 86 | "type": "integer", 87 | "format": "int32" 88 | }, 89 | "examples": { 90 | "large": { 91 | "value": 100, 92 | "summary": "Return a maximum of 100 results" 93 | }, 94 | "small": { 95 | "value": 5, 96 | "summary": "Return a maximum of 5 results" 97 | } 98 | } 99 | } 100 | ], 101 | "servers": [ 102 | { 103 | "url": "http://petstore.swagger.io/2.1/", 104 | "description": "Petstore API (with version 2.1 prefix)" 105 | } 106 | ], 107 | "responses": { 108 | "200": { 109 | "description": "A paged array of pets", 110 | "headers": { 111 | "x-next": { 112 | "description": "A link to the next page of responses", 113 | "schema": { 114 | "type": "string" 115 | } 116 | } 117 | }, 118 | "content": { 119 | "application/json": { 120 | "schema": { 121 | "$ref": "#/components/schemas/Pets" 122 | }, 123 | "examples": { 124 | "Rabbit": { 125 | "value": { 126 | "id": 10, 127 | "name": "Rabbit" 128 | } 129 | }, 130 | "Cat": { 131 | "$ref": "#/components/examples/Cat" 132 | } 133 | } 134 | } 135 | }, 136 | "links": { 137 | "getPetById": { 138 | "$ref": "#/components/links/GetPetById" 139 | } 140 | } 141 | }, 142 | "default": { 143 | "description": "unexpected error", 144 | "content": { 145 | "application/json": { 146 | "schema": { 147 | "$ref": "#/components/schemas/Error" 148 | } 149 | } 150 | } 151 | } 152 | } 153 | }, 154 | "post": { 155 | "summary": "Create a pet", 156 | "operationId": "createPets", 157 | "tags": [ 158 | "pets" 159 | ], 160 | "requestBody": { 161 | "description": "Pet to add to the store", 162 | "required": true, 163 | "content": { 164 | "application/json": { 165 | "schema": { 166 | "properties": { 167 | "name": { 168 | "type": "string" 169 | } 170 | }, 171 | "example": { 172 | "name": "Fluffy" 173 | } 174 | } 175 | } 176 | } 177 | }, 178 | "responses": { 179 | "201": { 180 | "description": "New Pet", 181 | "content": { 182 | "application/json": { 183 | "schema": { 184 | "$ref": "#/components/schemas/Pet" 185 | } 186 | } 187 | }, 188 | "links": { 189 | "getPetById": { 190 | "operationId": "showPetById", 191 | "parameters": { 192 | "id": "$response.body#/id" 193 | }, 194 | "description": "The `id` value returned in the response can be used as the `petId` parameter in `GET /pets/{petId}`." 195 | } 196 | } 197 | }, 198 | "default": { 199 | "description": "unexpected error", 200 | "content": { 201 | "application/json": { 202 | "schema": { 203 | "$ref": "#/components/schemas/Error" 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | }, 211 | "/pets/{petId}": { 212 | "parameters": [ 213 | { 214 | "name": "petId", 215 | "in": "path", 216 | "required": true, 217 | "description": "The id of the pet to retrieve", 218 | "schema": { 219 | "type": "string" 220 | } 221 | } 222 | ], 223 | "get": { 224 | "summary": "Info for a specific pet", 225 | "operationId": "showPetById", 226 | "tags": [ 227 | "pets" 228 | ], 229 | "responses": { 230 | "200": { 231 | "description": "Expected response to a valid request", 232 | "content": { 233 | "application/json": { 234 | "schema": { 235 | "$ref": "#/components/schemas/Pet" 236 | } 237 | } 238 | } 239 | }, 240 | "default": { 241 | "description": "unexpected error", 242 | "content": { 243 | "application/json": { 244 | "schema": { 245 | "$ref": "#/components/schemas/Error" 246 | } 247 | } 248 | } 249 | } 250 | } 251 | }, 252 | "post": { 253 | "summary": "Update info for a specific pet", 254 | "operationId": "updatePetById", 255 | "tags": [ 256 | "pets" 257 | ], 258 | "requestBody": { 259 | "$ref": "#/components/requestBodies/PetBody" 260 | }, 261 | "responses": { 262 | "200": { 263 | "$ref": "#/components/responses/UpdatePetBodyResponse" 264 | }, 265 | "default": { 266 | "description": "unexpected error", 267 | "content": { 268 | "application/json": { 269 | "schema": { 270 | "$ref": "#/components/schemas/Error" 271 | } 272 | } 273 | } 274 | } 275 | } 276 | }, 277 | "put": { 278 | "summary": "Replace info for a specific pet", 279 | "operationId": "replacePetById", 280 | "tags": [ 281 | "pets" 282 | ], 283 | "requestBody": { 284 | "$ref": "#/components/requestBodies/PetBody" 285 | }, 286 | "responses": { 287 | "200": { 288 | "$ref": "#/components/responses/ReplacePetBodyResponse" 289 | }, 290 | "default": { 291 | "description": "unexpected error", 292 | "content": { 293 | "application/json": { 294 | "schema": { 295 | "$ref": "#/components/schemas/Error" 296 | } 297 | } 298 | } 299 | } 300 | } 301 | } 302 | }, 303 | "/pets/{petId}/purchase": { 304 | "post": { 305 | "summary": "Purchase a specific pet", 306 | "operationId": "purchasePetById", 307 | "deprecated": true, 308 | "tags": [ 309 | "pets" 310 | ], 311 | "parameters": [ 312 | {"$ref": "#/components/parameters/petId"} 313 | ], 314 | "requestBody": { 315 | "description": "Pet order object", 316 | "required": true, 317 | "content": { 318 | "application/json": { 319 | "schema": { 320 | "oneOf": [ 321 | {"$ref": "#/components/schemas/PetOrderRequest"}, 322 | {"$ref": "#/components/schemas/ComplexPetOrderRequest"} 323 | ] 324 | }, 325 | "example": { 326 | "id": 10, 327 | "name": "Fluffy" 328 | } 329 | } 330 | } 331 | }, 332 | "responses": { 333 | "201": { 334 | "description": "Expected response to a valid request", 335 | "content": { 336 | "application/json": { 337 | "schema": { 338 | "$ref": "#/components/schemas/PetOrder" 339 | } 340 | } 341 | } 342 | }, 343 | "default": { 344 | "description": "unexpected error", 345 | "content": { 346 | "application/json": { 347 | "schema": { 348 | "$ref": "#/components/schemas/Error" 349 | } 350 | } 351 | } 352 | } 353 | }, 354 | "callbacks": { 355 | "orderUpdated": { 356 | "{$request.body#/webhook_url}": { 357 | "post": { 358 | "requestBody": { 359 | "required": true, 360 | "content": { 361 | "application/json": { 362 | "schema": { 363 | "$ref": "#/components/schemas/OrderUpdated" 364 | } 365 | } 366 | } 367 | }, 368 | "responses": { 369 | "200": { 370 | "description": "The server must return an HTTP 200, otherwise delivery will be reattempted." 371 | } 372 | } 373 | } 374 | } 375 | } 376 | } 377 | } 378 | } 379 | }, 380 | "components": { 381 | "schemas": { 382 | "Pet": { 383 | "required": [ 384 | "id", 385 | "name" 386 | ], 387 | "properties": { 388 | "id": { 389 | "type": "integer", 390 | "format": "int64" 391 | }, 392 | "name": { 393 | "type": "string" 394 | }, 395 | "tag_ids": { 396 | "type": "array", 397 | "items": { 398 | "type": "integer", 399 | "format": "int64", 400 | "example": 1 401 | }, 402 | "example": [1, 2, 3] 403 | } 404 | } 405 | }, 406 | "Pets": { 407 | "type": "array", 408 | "items": { 409 | "$ref": "#/components/schemas/Pet" 410 | }, 411 | "example": [{"id": 10, "name": "Rover"}, {"id": 20, "name": "Felicity"}] 412 | }, 413 | "PetOrderRequest": { 414 | "required": [ 415 | "phone_number" 416 | ], 417 | "properties": { 418 | "phone_number": { 419 | "type": "string" 420 | }, 421 | "webhook_url": { 422 | "type": "string" 423 | } 424 | } 425 | }, 426 | "PetOrder": { 427 | "required": [ 428 | "phone_number", 429 | "id", 430 | "status" 431 | ], 432 | "properties": { 433 | "id": { 434 | "type": "integer", 435 | "format": "int64" 436 | }, 437 | "phone_number": { 438 | "type": "string" 439 | }, 440 | "webhook_url": { 441 | "type": "string" 442 | }, 443 | "status": { 444 | "type": "string" 445 | } 446 | } 447 | }, 448 | "OrderUpdated": { 449 | "required": [ 450 | "order_id", 451 | "status", 452 | "phone_number" 453 | ], 454 | "properties": { 455 | "order_id": { 456 | "type": "integer", 457 | "format": "int64" 458 | }, 459 | "phone_number": { 460 | "type": "string" 461 | }, 462 | "status": { 463 | "type": "string" 464 | } 465 | }, 466 | "example": { 467 | "order_id": 123, 468 | "phone_number": "3125556666", 469 | "status": "complete" 470 | } 471 | }, 472 | "Error": { 473 | "required": [ 474 | "code", 475 | "message" 476 | ], 477 | "properties": { 478 | "code": { 479 | "type": "integer", 480 | "format": "int32" 481 | }, 482 | "message": { 483 | "type": "string" 484 | } 485 | } 486 | } 487 | }, 488 | "requestBodies": { 489 | "PetBody": { 490 | "description": "A JSON object containing pet information", 491 | "required": true, 492 | "content": { 493 | "application/json": { 494 | "schema": { 495 | "$ref": "#/components/schemas/Pet" 496 | } 497 | } 498 | } 499 | } 500 | }, 501 | "responses" : { 502 | "UpdatePetBodyResponse": { 503 | "description": "Expected response to a valid request", 504 | "content": { 505 | "application/json": { 506 | "schema": { 507 | "$ref": "#/components/schemas/Pet" 508 | } 509 | } 510 | } 511 | }, 512 | "ReplacePetBodyResponse": { 513 | "description": "Expected response to a valid request", 514 | "content": { 515 | "application/json": { 516 | "schema": { 517 | "$ref": "#/components/schemas/Pet" 518 | } 519 | } 520 | } 521 | } 522 | }, 523 | "links": { 524 | "GetPetById": { 525 | "operationId": "showPetById", 526 | "parameters": { 527 | "petId": "$response.body#/id" 528 | } 529 | } 530 | }, 531 | "examples": { 532 | "PetExample": { 533 | "value": { 534 | "id": 1, 535 | "name": "Rover" 536 | }, 537 | "summary": "An example pet response" 538 | }, 539 | "Cat": { 540 | "value": { 541 | "id": 1, 542 | "name": "Felicity" 543 | }, 544 | "summary": "An example cat response" 545 | } 546 | }, 547 | "securitySchemes": { 548 | "BasicAuth": { 549 | "type": "http", 550 | "scheme": "basic" 551 | }, 552 | "BearerAuth": { 553 | "type": "http", 554 | "scheme": "bearer" 555 | }, 556 | "ApiKeyAuth": { 557 | "type": "apiKey", 558 | "in": "header", 559 | "name": "X-API-Key" 560 | }, 561 | "OpenID": { 562 | "type": "openIdConnect", 563 | "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" 564 | }, 565 | "OAuth2": { 566 | "type": "oauth2", 567 | "flows": { 568 | "authorizationCode": { 569 | "authorizationUrl": "https://example.com/oauth/authorize", 570 | "tokenUrl": "https://example.com/oauth/token", 571 | "scopes": { 572 | "read": "Grants read access", 573 | "write": "Grants write access", 574 | "admin": "Grants access to admin operations" 575 | } 576 | } 577 | } 578 | } 579 | }, 580 | "parameters": { 581 | "petId": { 582 | "name": "petId", 583 | "in": "path", 584 | "required": true, 585 | "description": "The id of the pet to retrieve", 586 | "schema": { 587 | "type": "string" 588 | } 589 | } 590 | } 591 | } 592 | } 593 | -------------------------------------------------------------------------------- /spec/lib/swagger_v3_blocks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'swagger/blocks' 3 | 4 | # TODO: Test data originally based on the Swagger UI example data 5 | 6 | RESOURCE_LISTING_JSON_V3 = open(File.expand_path('../swagger_v3_api_declaration.json', __FILE__)).read 7 | 8 | class PetControllerV3 9 | include Swagger::Blocks 10 | 11 | swagger_root do 12 | key :openapi, '3.0.0' 13 | info version: '1.0.1' do 14 | key :title, 'Swagger Petstore' 15 | key :description, 'A sample API that uses a petstore as an example to ' \ 16 | 'demonstrate features in the swagger-2.0 specification' 17 | key :termsOfService, 'http://helloreverb.com/terms/' 18 | contact do 19 | key :name, 'Wordnik API Team' 20 | end 21 | license do 22 | key :name, 'MIT' 23 | end 24 | end 25 | 26 | server do 27 | key :url, 'http://petstore.swagger.io/v1' 28 | key :description, 'Petstore API' 29 | end 30 | 31 | server do 32 | key :url, 'https://{subdomain}.site.com/{version}' 33 | key :description, 'The main prod server' 34 | 35 | variable :subdomain do 36 | key :default, :production 37 | end 38 | variable :version do 39 | key :enum, ['v1', 'v2'] 40 | key :default, :v2 41 | end 42 | end 43 | 44 | security do 45 | key :ApiKeyAuth, [] 46 | end 47 | security do 48 | key :OAuth2, ['read', 'write'] 49 | end 50 | 51 | extension :'x-tagGroups' do 52 | key :name, 'Pets' 53 | key :tags, ['dogs', 'cats'] 54 | end 55 | 56 | tag do 57 | key :name, 'dogs' 58 | key :description, 'Dogs' 59 | end 60 | 61 | tag do 62 | key :name, 'cats' 63 | key :description, 'Cats' 64 | end 65 | end 66 | 67 | swagger_path '/pets' do 68 | key :description, 'Perform actions on pet resources' 69 | server do 70 | key :url, 'http://petstore.swagger.io/' 71 | key :description, 'Petstore API (without version prefix)' 72 | end 73 | operation :get do 74 | key :summary, 'List all pets' 75 | key :operationId, 'listPets' 76 | key :tags, [ 77 | 'pets' 78 | ] 79 | parameter do 80 | key :name, :limit 81 | key :in, :query 82 | key :description, 'How many items to return at one time (max 100)' 83 | key :required, false 84 | schema do 85 | key :type, :integer 86 | key :format, :int32 87 | end 88 | example :large do 89 | key :value, 100 90 | key :summary, 'Return a maximum of 100 results' 91 | end 92 | example :small do 93 | key :value, 5 94 | key :summary, 'Return a maximum of 5 results' 95 | end 96 | end 97 | server do 98 | key :url, 'http://petstore.swagger.io/2.1/' 99 | key :description, 'Petstore API (with version 2.1 prefix)' 100 | end 101 | response 200 do 102 | key :description, 'A paged array of pets' 103 | header :'x-next' do 104 | key :description, 'A link to the next page of responses' 105 | schema do 106 | key :type, :string 107 | end 108 | end 109 | content :'application/json' do 110 | schema do 111 | key :'$ref', :Pets 112 | end 113 | example :Rabbit do 114 | value do 115 | key :id, 10 116 | key :name, 'Rabbit' 117 | end 118 | end 119 | example :Cat do 120 | key :'$ref', :Cat 121 | end 122 | end 123 | link :getPetById do 124 | key :'$ref', '#/components/links/GetPetById' 125 | end 126 | end 127 | response :default do 128 | key :description, 'unexpected error' 129 | content :'application/json' do 130 | schema do 131 | key :'$ref', :Error 132 | end 133 | end 134 | end 135 | end 136 | operation :post do 137 | key :summary, 'Create a pet' 138 | key :operationId, 'createPets' 139 | key :tags, [ 140 | 'pets' 141 | ] 142 | request_body do 143 | key :description, 'Pet to add to the store' 144 | key :required, true 145 | content 'application/json' do 146 | schema do 147 | property :name do 148 | key :type, :string 149 | end 150 | example do 151 | key :name, 'Fluffy' 152 | end 153 | end 154 | end 155 | end 156 | response 201 do 157 | key :description, 'New Pet' 158 | content :'application/json' do 159 | schema do 160 | key :'$ref', :Pet 161 | end 162 | end 163 | link :getPetById do 164 | key :operationId, 'showPetById' 165 | parameters do 166 | key :id, '$response.body#/id' 167 | end 168 | key :description, 'The `id` value returned in the response can be used as the `petId` parameter in `GET /pets/{petId}`.' 169 | end 170 | end 171 | response :default, description: 'unexpected error' do 172 | content :'application/json' do 173 | schema do 174 | key :'$ref', :Error 175 | end 176 | end 177 | end 178 | end 179 | end 180 | 181 | swagger_path '/pets/{petId}' do 182 | parameter do 183 | key :name, :petId 184 | key :in, :path 185 | key :required, true 186 | key :description, 'The id of the pet to retrieve' 187 | schema do 188 | key :type, 'string' 189 | end 190 | end 191 | operation :get do 192 | key :summary, 'Info for a specific pet' 193 | key :operationId, 'showPetById' 194 | key :tags, [ 195 | 'pets' 196 | ] 197 | response 200 do 198 | key :description, 'Expected response to a valid request' 199 | content :'application/json' do 200 | schema do 201 | key :'$ref', :Pet 202 | end 203 | end 204 | end 205 | response :default do 206 | key :description, 'unexpected error' 207 | content :'application/json' do 208 | schema do 209 | key :'$ref', :Error 210 | end 211 | end 212 | end 213 | end 214 | operation :post do 215 | key :summary, 'Update info for a specific pet' 216 | key :operationId, 'updatePetById' 217 | key :tags, [ 218 | 'pets' 219 | ] 220 | request_body do 221 | key :"$ref", :PetBody 222 | end 223 | response 200 do 224 | key :'$ref', :UpdatePetBodyResponse 225 | end 226 | response :default do 227 | key :description, 'unexpected error' 228 | content :'application/json' do 229 | schema do 230 | key :'$ref', :Error 231 | end 232 | end 233 | end 234 | end 235 | operation :put do 236 | key :summary, 'Replace info for a specific pet' 237 | key :operationId, 'replacePetById' 238 | key :tags, [ 239 | 'pets' 240 | ] 241 | request_body do 242 | key :"$ref", :PetBody 243 | end 244 | response 200 do 245 | key :'$ref', :ReplacePetBodyResponse 246 | end 247 | response :default do 248 | key :description, 'unexpected error' 249 | content :'application/json' do 250 | schema do 251 | key :'$ref', :Error 252 | end 253 | end 254 | end 255 | end 256 | end 257 | end 258 | 259 | class PetV3 260 | include Swagger::Blocks 261 | 262 | swagger_path '/pets/{petId}/purchase' do 263 | operation :post do 264 | key :summary, 'Purchase a specific pet' 265 | key :operationId, 'purchasePetById' 266 | key :deprecated, true 267 | key :tags, [ 268 | 'pets' 269 | ] 270 | parameter do 271 | key :$ref, :petId 272 | end 273 | request_body do 274 | key :description, 'Pet order object' 275 | key :required, true 276 | content 'application/json' do 277 | schema do 278 | one_of do 279 | key :'$ref', 'PetOrderRequest' 280 | end 281 | one_of do 282 | key :'$ref', 'ComplexPetOrderRequest' 283 | end 284 | end 285 | example do 286 | key :id, 10 287 | key :name, 'Fluffy' 288 | end 289 | end 290 | end 291 | response 201 do 292 | key :description, 'Expected response to a valid request' 293 | content :'application/json' do 294 | schema do 295 | key :'$ref', :PetOrder 296 | end 297 | end 298 | end 299 | response :default do 300 | key :description, 'unexpected error' 301 | content :'application/json' do 302 | schema do 303 | key :'$ref', :Error 304 | end 305 | end 306 | end 307 | 308 | callback :orderUpdated do 309 | destination '{$request.body#/webhook_url}' do 310 | method :post do 311 | request_body do 312 | key :required, true 313 | content 'application/json' do 314 | schema do 315 | key :'$ref', :OrderUpdated 316 | end 317 | end 318 | end 319 | response 200 do 320 | key :description, 'The server must return an HTTP 200, otherwise delivery will be reattempted.' 321 | end 322 | end 323 | end 324 | end 325 | end 326 | end 327 | swagger_component do 328 | schema :Pet, required: [:id, :name] do 329 | property :id do 330 | key :type, :integer 331 | key :format, :int64 332 | end 333 | property :name do 334 | key :type, :string 335 | end 336 | property :tag_ids do 337 | key :type, :array 338 | items do 339 | key :type, :integer 340 | key :format, :int64 341 | key :example, 1 342 | end 343 | key :example, [1, 2, 3] 344 | end 345 | end 346 | 347 | schema :Pets do 348 | key :type, :array 349 | items do 350 | key :'$ref', :Pet 351 | end 352 | key :example, [{ id: 10, name: 'Rover' }, { id: 20, name: 'Felicity' }] 353 | end 354 | 355 | schema :PetOrderRequest, required: [:phone_number] do 356 | property :phone_number do 357 | key :type, :string 358 | end 359 | property :webhook_url do 360 | key :type, :string 361 | end 362 | end 363 | 364 | schema :PetOrder, required: [:phone_number, :id, :status] do 365 | property :id do 366 | key :type, :integer 367 | key :format, :int64 368 | end 369 | property :phone_number do 370 | key :type, :string 371 | end 372 | property :webhook_url do 373 | key :type, :string 374 | end 375 | property :status do 376 | key :type, :string 377 | end 378 | end 379 | 380 | schema :OrderUpdated, required: [:order_id, :status, :phone_number] do 381 | property :order_id do 382 | key :type, :integer 383 | key :format, :int64 384 | end 385 | property :phone_number do 386 | key :type, :string 387 | end 388 | property :status do 389 | key :type, :string 390 | end 391 | example do 392 | key :order_id, 123 393 | key :phone_number, '3125556666' 394 | key :status, 'complete' 395 | end 396 | end 397 | 398 | link :GetPetById do 399 | key :operationId, :showPetById 400 | parameters do 401 | key :petId, '$response.body#/id' 402 | end 403 | end 404 | 405 | example :PetExample do 406 | value do 407 | key :id, 1 408 | key :name, 'Rover' 409 | end 410 | key :summary, 'An example pet response' 411 | end 412 | security_scheme :BasicAuth do 413 | key :type, :http 414 | key :scheme, :basic 415 | end 416 | security_scheme :BearerAuth do 417 | key :type, :http 418 | key :scheme, :bearer 419 | end 420 | security_scheme :ApiKeyAuth do 421 | key :type, :apiKey 422 | key :in, :header 423 | key :name, :"X-API-Key" 424 | end 425 | security_scheme :OpenID do 426 | key :type, :openIdConnect 427 | key :openIdConnectUrl, 'https://example.com/.well-known/openid-configuration' 428 | end 429 | parameter :petId do 430 | key :name, :petId 431 | key :in, :path 432 | key :required, true 433 | key :description, 'The id of the pet to retrieve' 434 | schema do 435 | key :type, 'string' 436 | end 437 | end 438 | request_body :PetBody do 439 | key :description, 'A JSON object containing pet information' 440 | key :required, true 441 | content :'application/json' do 442 | schema do 443 | key :'$ref', :Pet 444 | end 445 | end 446 | end 447 | response :ReplacePetBodyResponse do 448 | key :description, 'Expected response to a valid request' 449 | content :'application/json' do 450 | schema do 451 | key :'$ref', :Pet 452 | end 453 | end 454 | end 455 | end 456 | end 457 | 458 | class ErrorModelV3 459 | include Swagger::Blocks 460 | 461 | swagger_component do 462 | schema :Error do 463 | key :required, [:code, :message] 464 | property :code do 465 | key :type, :integer 466 | key :format, :int32 467 | end 468 | property :message do 469 | key :type, :string 470 | end 471 | end 472 | end 473 | end 474 | 475 | class AuxiliaryModelV3 476 | include Swagger::Blocks 477 | 478 | swagger_component do 479 | response :UpdatePetBodyResponse do 480 | key :description, 'Expected response to a valid request' 481 | content :'application/json' do 482 | schema do 483 | key :'$ref', :Pet 484 | end 485 | end 486 | end 487 | example :Cat do 488 | value do 489 | key :id, 1 490 | key :name, 'Felicity' 491 | end 492 | key :summary, 'An example cat response' 493 | end 494 | security_scheme :OAuth2 do 495 | key :type, :oauth2 496 | flow :authorizationCode do 497 | key :authorizationUrl, 'https://example.com/oauth/authorize' 498 | key :tokenUrl, 'https://example.com/oauth/token' 499 | scopes do 500 | key :read, 'Grants read access' 501 | key :write, 'Grants write access' 502 | key :admin, 'Grants access to admin operations' 503 | end 504 | end 505 | end 506 | end 507 | end 508 | 509 | describe 'Swagger::Blocks v3' do 510 | describe 'build_json' do 511 | it 'outputs the correct data' do 512 | swaggered_classes = [ 513 | PetControllerV3, 514 | PetV3, 515 | ErrorModelV3, 516 | AuxiliaryModelV3 517 | ] 518 | actual = Swagger::Blocks.build_root_json(swaggered_classes) 519 | actual = JSON.parse(actual.to_json) # For access consistency. 520 | data = JSON.parse(RESOURCE_LISTING_JSON_V3) 521 | 522 | # Multiple expectations for better test diff output. 523 | expect(actual['info']).to eq(data['info']) 524 | expect(actual['x-tagGroups']).to eq(data['x-tagGroups']) 525 | expect(actual['paths']).to be 526 | expect(actual['paths']['/pets']).to be 527 | expect(actual['paths']['/pets']).to eq(data['paths']['/pets']) 528 | expect(actual['paths']['/pets/{petId}']).to be 529 | expect(actual['paths']['/pets/{petId}']['get']).to be 530 | expect(actual['paths']['/pets/{petId}']['get']).to eq(data['paths']['/pets/{petId}']['get']) 531 | expect(actual['paths']).to eq(data['paths']) 532 | expect(actual['components']).to eq(data['components']) 533 | expect(actual).to eq(data) 534 | end 535 | 536 | it 'is idempotent' do 537 | swaggered_classes = [PetControllerV3, PetV3, ErrorModelV3, AuxiliaryModelV3] 538 | actual = JSON.parse(Swagger::Blocks.build_root_json(swaggered_classes).to_json) 539 | data = JSON.parse(RESOURCE_LISTING_JSON_V3) 540 | expect(actual).to eq(data) 541 | end 542 | 543 | it 'errors if no swagger_root is declared' do 544 | expect do 545 | Swagger::Blocks.build_root_json([]) 546 | end.to raise_error(Swagger::Blocks::DeclarationError) 547 | end 548 | 549 | it 'errors if multiple swagger_roots are declared' do 550 | expect do 551 | Swagger::Blocks.build_root_json([PetControllerV3, PetControllerV3]) 552 | end.to raise_error(Swagger::Blocks::DeclarationError) 553 | end 554 | end 555 | end 556 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 2 | RSpec.configure do |config| 3 | # Allow more verbose output when running an individual spec file. 4 | if config.files_to_run.one? 5 | config.default_formatter = 'doc' 6 | end 7 | 8 | # Run specs in random order to surface order dependencies. If you find an 9 | # order dependency and want to debug it, you can fix the order by providing 10 | # the seed, which is printed after each run. 11 | # --seed 1234 12 | config.order = :random 13 | 14 | # Seed global randomization in this process using the `--seed` CLI option. 15 | # Setting this allows you to use `--seed` to deterministically reproduce 16 | # test failures related to randomization by passing the same `--seed` value 17 | # as the one that triggered the failure. 18 | Kernel.srand config.seed 19 | 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # Enable only the newer, non-monkey-patching expect syntax. 25 | # For more details, see: 26 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 27 | expectations.syntax = :expect 28 | end 29 | 30 | # rspec-mocks config goes here. You can use an alternate test double 31 | # library (such as bogus or mocha) by changing the `mock_with` option here. 32 | config.mock_with :rspec do |mocks| 33 | # Enable only the newer, non-monkey-patching expect syntax. 34 | # For more details, see: 35 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 36 | mocks.syntax = :expect 37 | 38 | # Prevents you from mocking or stubbing a method that does not exist on 39 | # a real object. This is generally recommended. 40 | mocks.verify_partial_doubles = true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /swagger-blocks.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/blocks/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'swagger-blocks' 8 | spec.version = Swagger::Blocks::VERSION 9 | spec.authors = ['Mike Fotinakis'] 10 | spec.email = ['mike@fotinakis.com'] 11 | spec.summary = %q{Define and serve live-updating Swagger JSON for Ruby apps.} 12 | spec.description = %q{} 13 | spec.homepage = 'https://github.com/fotinakis/swagger-blocks' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 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 | spec.required_ruby_version = '>= 1.9.3' 21 | 22 | spec.add_development_dependency 'bundler' 23 | spec.add_development_dependency 'rake' 24 | spec.add_development_dependency 'rspec' 25 | spec.add_development_dependency 'pry' 26 | end 27 | --------------------------------------------------------------------------------