From 290b700e56baf685a11a531a66474fe845bd7e24 Mon Sep 17 00:00:00 2001 From: blake Date: Sun, 20 Jan 2019 18:12:36 +0100 Subject: [PATCH] Add spec --- lib/grape-swagger.rb | 21 +- lib/grape-swagger/doc_methods/extensions.rb | 12 +- lib/grape-swagger/openapi_3/doc_methods.rb | 8 + .../openapi_3/doc_methods/move_params.rb | 2 +- .../openapi_3/doc_methods/parse_params.rb | 7 +- .../doc_methods/parse_request_body.rb | 122 ---------- lib/grape-swagger/openapi_3/endpoint.rb | 30 +-- lib/grape-swagger/swagger_2/doc_methods.rb | 2 - lib/grape-swagger/swagger_2/endpoint.rb | 2 +- spec/openapi_3/guarded_endpoint_spec.rb | 2 +- spec/openapi_3/namespace_tags_spec.rb | 71 +++++- .../params_array_collection_format_spec.rb | 112 +++++++++ spec/support/model_parsers/mock_parser.rb | 227 +++++++++++++++++- 13 files changed, 463 insertions(+), 155 deletions(-) delete mode 100644 lib/grape-swagger/openapi_3/doc_methods/parse_request_body.rb create mode 100644 spec/openapi_3/params_array_collection_format_spec.rb diff --git a/lib/grape-swagger.rb b/lib/grape-swagger.rb index a59868bc..76614b10 100644 --- a/lib/grape-swagger.rb +++ b/lib/grape-swagger.rb @@ -7,6 +7,10 @@ require 'grape-swagger/errors' require 'grape-swagger/version' require 'grape-swagger/model_parsers' +require 'grape-swagger/swagger_2/endpoint' +require 'grape-swagger/openapi_3/endpoint' +require 'grape-swagger/openapi_3/doc_methods' +require 'grape-swagger/swagger_2/doc_methods' module GrapeSwagger class << self @@ -135,6 +139,10 @@ def add_swagger_documentation(options = {}) @target_class.combined_routes = combined_routes @target_class.combined_namespaces = combined_namespaces + endpoint_type = options[:openapi_version] == '3.0' ? Grape::OpenAPI3Endpoint : Grape::Swagger2Endpoint + set_endpoint_type(@target_class, endpoint_type) + set_endpoint_type(documentation_class, endpoint_type) + documentation_class end @@ -144,6 +152,13 @@ def version_for(options) options[:version] = version if version end + def set_endpoint_type(app, klass) + app.endpoints.each do |endpoint| + endpoint.class.include(klass) + set_endpoint_type(endpoint.options[:app], klass) if endpoint.options[:app] + end + end + def combine_namespaces(app) combined_namespaces = {} endpoints = app.endpoints.clone @@ -166,14 +181,10 @@ def combine_namespaces(app) end def create_documentation_class(openapi_version) - Class.new(Grape::API) do + Class.new(GrapeInstance) do if openapi_version == '3.0' - require 'grape-swagger/openapi_3/endpoint' - require 'grape-swagger/openapi_3/doc_methods' extend GrapeOpenAPI::DocMethods else - require 'grape-swagger/swagger_2/endpoint' - require 'grape-swagger/swagger_2/doc_methods' extend GrapeSwagger::DocMethods end end diff --git a/lib/grape-swagger/doc_methods/extensions.rb b/lib/grape-swagger/doc_methods/extensions.rb index 0c5334f7..c4079136 100644 --- a/lib/grape-swagger/doc_methods/extensions.rb +++ b/lib/grape-swagger/doc_methods/extensions.rb @@ -55,9 +55,17 @@ def find_definition(status, path) response = path[method][:responses][status] return if response.nil? - return response[:schema]['$ref'].split('/').last if response[:schema].key?('$ref') + # Swagger 2 + if response[:schema] + return response[:schema]['$ref'].split('/').last if response[:schema].key?('$ref') + return response[:schema]['items']['$ref'].split('/').last if response[:schema].key?('items') + end - response[:schema]['items']['$ref'].split('/').last if response[:schema].key?('items') + # OpenAPI 3 + response[:content].each do |_,v| + return v[:schema]['$ref'].split('/').last if v[:schema].key?('$ref') + return v[:schema]['items']['$ref'].split('/').last if v[:schema].key?('items') + end end def add_extension_to(part, extensions) diff --git a/lib/grape-swagger/openapi_3/doc_methods.rb b/lib/grape-swagger/openapi_3/doc_methods.rb index b11f3bee..eacbd31a 100644 --- a/lib/grape-swagger/openapi_3/doc_methods.rb +++ b/lib/grape-swagger/openapi_3/doc_methods.rb @@ -32,6 +32,8 @@ def setup(options) target_class = options[:target_class] guard = options[:swagger_endpoint_guard] formatter = options[:format] + api_doc = options[:api_documentation].dup + specific_api_doc = options[:specific_api_documentation].dup class_variables_from(options) @@ -41,6 +43,8 @@ def setup(options) end end + desc api_doc.delete(:desc), api_doc + instance_eval(guard) unless guard.nil? output_path_definitions = proc do |combi_routes, endpoint| @@ -70,10 +74,14 @@ def setup(options) output_path_definitions.call(target_class.combined_namespace_routes, self) end + desc specific_api_doc.delete(:desc), { params: + specific_api_doc.delete(:params) || {} }.merge(specific_api_doc) + params do requires :name, type: String, desc: 'Resource name of mounted API' optional :locale, type: Symbol, desc: 'Locale of API documentation' end + get "#{mount_path}/:name" do I18n.locale = params[:locale] || I18n.default_locale diff --git a/lib/grape-swagger/openapi_3/doc_methods/move_params.rb b/lib/grape-swagger/openapi_3/doc_methods/move_params.rb index a7810ab9..f7488298 100644 --- a/lib/grape-swagger/openapi_3/doc_methods/move_params.rb +++ b/lib/grape-swagger/openapi_3/doc_methods/move_params.rb @@ -4,7 +4,7 @@ module GrapeSwagger module DocMethods - class MoveParams + class OpenAPIMoveParams class << self attr_accessor :definitions diff --git a/lib/grape-swagger/openapi_3/doc_methods/parse_params.rb b/lib/grape-swagger/openapi_3/doc_methods/parse_params.rb index bbeb6565..4445e626 100644 --- a/lib/grape-swagger/openapi_3/doc_methods/parse_params.rb +++ b/lib/grape-swagger/openapi_3/doc_methods/parse_params.rb @@ -2,7 +2,7 @@ module GrapeSwagger module DocMethods - class ParseParams + class OpenAPIParseParams class << self def call(param, settings, path, route, definitions) method = route.request_method @@ -44,11 +44,11 @@ def document_required(settings) def document_range_values(settings) values = settings[:values] || nil enum_or_range_values = parse_enum_or_range_values(values) - @parsed_param.merge!(enum_or_range_values) if enum_or_range_values + @parsed_param[:schema].merge!(enum_or_range_values) if enum_or_range_values end def document_default_value(settings) - @parsed_param[:default] = settings[:default] if settings[:default].present? + @parsed_param[:schema][:default] = settings[:default] if settings[:default].present? end def document_type_and_format(settings, data_type) @@ -76,6 +76,7 @@ def document_array_param(value_type, definitions) if definitions[value_type[:data_type]] array_items['$ref'] = "#/components/schemas/#{@parsed_param[:schema][:type]}" else + puts value_type.inspect array_items[:type] = type || @parsed_param[:schema][:type] == 'array' ? 'string' : @parsed_param[:schema][:type] end array_items[:format] = @parsed_param.delete(:format) if @parsed_param[:format] diff --git a/lib/grape-swagger/openapi_3/doc_methods/parse_request_body.rb b/lib/grape-swagger/openapi_3/doc_methods/parse_request_body.rb deleted file mode 100644 index 2269a16c..00000000 --- a/lib/grape-swagger/openapi_3/doc_methods/parse_request_body.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -module GrapeSwagger - module DocMethods - class ParseRequestBody - class << self - def call(param, settings, path, route, _definitions) - method = route.request_method - additional_documentation = settings.fetch(:documentation, {}) - settings.merge!(additional_documentation) - data_type = DataType.call(settings) - - value_type = settings.merge(data_type: data_type, path: path, param_name: param, method: method) - - type = param_type(value_type) - return nil if type.nil? - - # required properties - @parsed_param = { - in: type, - name: settings[:full_name] || param - } - - # optional properties - document_description(settings) - document_type_and_format(settings, data_type) - document_array_param(value_type, definitions) if value_type[:is_array] - document_default_value(settings) unless value_type[:is_array] - document_range_values(settings) unless value_type[:is_array] - document_required(settings) - - @parsed_param - end - - private - - def document_description(settings) - description = settings[:desc] || settings[:description] - @parsed_param[:description] = description if description - end - - def document_required(settings) - @parsed_param[:required] = settings[:required] || false - @parsed_param[:required] = true if @parsed_param[:in] == 'path' - end - - def document_range_values(settings) - values = settings[:values] || nil - enum_or_range_values = parse_enum_or_range_values(values) - @parsed_param.merge!(enum_or_range_values) if enum_or_range_values - end - - def document_default_value(settings) - @parsed_param[:default] = settings[:default] if settings[:default].present? - end - - def document_type_and_format(settings, data_type) - if DataType.primitive?(data_type) - data = DataType.mapping(data_type) - @parsed_param[:type], @parsed_param[:format] = data - else - @parsed_param[:type] = data_type - end - @parsed_param[:format] = settings[:format] if settings[:format].present? - end - - def document_array_param(value_type, definitions) - if value_type[:documentation].present? - param_type = value_type[:documentation][:param_type] - doc_type = value_type[:documentation][:type] - type = DataType.mapping(doc_type) if doc_type && !DataType.request_primitive?(doc_type) - collection_format = value_type[:documentation][:collectionFormat] - end - - param_type ||= value_type[:param_type] - - array_items = {} - if definitions[value_type[:data_type]] - array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}" - else - array_items[:type] = type || @parsed_param[:type] == 'array' ? 'string' : @parsed_param[:type] - end - array_items[:format] = @parsed_param.delete(:format) if @parsed_param[:format] - - values = value_type[:values] || nil - enum_or_range_values = parse_enum_or_range_values(values) - array_items.merge!(enum_or_range_values) if enum_or_range_values - - array_items[:default] = value_type[:default] if value_type[:default].present? - - @parsed_param[:in] = param_type || 'formData' - @parsed_param[:items] = array_items - @parsed_param[:type] = 'array' - @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format) - end - - def param_type(value_type) - if value_type[:path].include?("{#{value_type[:param_name]}}") - nil - elsif %w[POST PUT PATCH].include?(value_type[:method]) - DataType.request_primitive?(value_type[:data_type]) ? 'formData' : 'body' - end - end - - def parse_enum_or_range_values(values) - case values - when Proc - parse_enum_or_range_values(values.call) if values.parameters.empty? - when Range - parse_range_values(values) if values.first.is_a?(Integer) - else - { enum: values } if values - end - end - - def parse_range_values(values) - { minimum: values.first, maximum: values.last } - end - end - end - end -end diff --git a/lib/grape-swagger/openapi_3/endpoint.rb b/lib/grape-swagger/openapi_3/endpoint.rb index 2474c3e6..c2a302f9 100644 --- a/lib/grape-swagger/openapi_3/endpoint.rb +++ b/lib/grape-swagger/openapi_3/endpoint.rb @@ -5,7 +5,7 @@ require 'grape-swagger/endpoint/params_parser' module Grape - class Endpoint + module OpenAPI3Endpoint def content_types_for(target_class) content_types = (target_class.content_types || {}).values @@ -30,10 +30,10 @@ def swagger_object(_target_class, request, options) servers = servers.is_a?(Hash) ? [servers] : servers object = { - info: info_object(options[:info].merge(version: options[:doc_version])), - openapi: '3.0.0', - security: options[:security], - authorizations: options[:authorizations], + info: info_object(options[:info].merge(version: options[:doc_version])), + openapi: '3.0.0', + security: options[:security], + authorizations: options[:authorizations], servers: servers } @@ -50,12 +50,12 @@ def swagger_object(_target_class, request, options) # building info object def info_object(infos) result = { - title: infos[:title] || 'API title', - description: infos[:description], - termsOfService: infos[:terms_of_service_url], - contact: contact_object(infos), - license: license_object(infos), - version: infos[:version] + title: infos[:title] || 'API title', + description: infos[:description], + termsOfService: infos[:terms_of_service_url], + contact: contact_object(infos), + license: license_object(infos), + version: infos[:version] } GrapeSwagger::DocMethods::Extensions.add_extensions_to_info(infos, result) @@ -68,7 +68,7 @@ def info_object(infos) def license_object(infos) { name: infos.delete(:license), - url: infos.delete(:license_url) + url: infos.delete(:license_url) }.delete_if { |_, value| value.blank? } end @@ -202,11 +202,11 @@ def params_object(route, options, path) elsif value[:documentation] expose_params(value[:documentation][:type]) end - GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions) + GrapeSwagger::DocMethods::OpenAPIParseParams.call(param, value, path, route, @definitions) end - if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(parameters, route.request_method) - parameters = GrapeSwagger::DocMethods::MoveParams.to_definition(path, parameters, route, @definitions) + if GrapeSwagger::DocMethods::OpenAPIMoveParams.can_be_moved?(parameters, route.request_method) + parameters = GrapeSwagger::DocMethods::OpenAPIMoveParams.to_definition(path, parameters, route, @definitions) end parameters diff --git a/lib/grape-swagger/swagger_2/doc_methods.rb b/lib/grape-swagger/swagger_2/doc_methods.rb index 7c424084..efb850e3 100644 --- a/lib/grape-swagger/swagger_2/doc_methods.rb +++ b/lib/grape-swagger/swagger_2/doc_methods.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# require 'grape-swagger/endpoint' - require 'grape-swagger/doc_methods/status_codes' require 'grape-swagger/doc_methods/produces_consumes' require 'grape-swagger/doc_methods/data_type' diff --git a/lib/grape-swagger/swagger_2/endpoint.rb b/lib/grape-swagger/swagger_2/endpoint.rb index 89afafea..48c97c28 100644 --- a/lib/grape-swagger/swagger_2/endpoint.rb +++ b/lib/grape-swagger/swagger_2/endpoint.rb @@ -5,7 +5,7 @@ require 'grape-swagger/endpoint/params_parser' module Grape - class Endpoint # rubocop:disable Metrics/ClassLength + module Swagger2Endpoint # rubocop:disable Metrics/ClassLength def content_types_for(target_class) content_types = (target_class.content_types || {}).values diff --git a/spec/openapi_3/guarded_endpoint_spec.rb b/spec/openapi_3/guarded_endpoint_spec.rb index 3c4770b5..4bd1f66a 100644 --- a/spec/openapi_3/guarded_endpoint_spec.rb +++ b/spec/openapi_3/guarded_endpoint_spec.rb @@ -41,7 +41,7 @@ def sample_auth(*scopes) description[:auth] = { scopes: scopes } end - Grape::API.extend self + GrapeInstance.extend self end describe 'a guarded api endpoint' do diff --git a/spec/openapi_3/namespace_tags_spec.rb b/spec/openapi_3/namespace_tags_spec.rb index 04cda374..5ff649d8 100644 --- a/spec/openapi_3/namespace_tags_spec.rb +++ b/spec/openapi_3/namespace_tags_spec.rb @@ -6,8 +6,74 @@ include_context 'namespace example' before :all do + module TheApi + class NamespaceApi2 < Grape::API + namespace :hudson do + desc 'Document root' + get '/' do + end + end + + namespace :colorado do + desc 'This gets something.', + notes: '_test_' + + get '/simple' do + { bla: 'something' } + end + end + + namespace :colorado do + desc 'This gets something for URL using - separator.', + notes: '_test_' + + get '/simple-test' do + { bla: 'something' } + end + end + + namespace :thames do + desc 'this gets something else', + headers: { + 'XAuthToken' => { description: 'A required header.', required: true }, + 'XOtherHeader' => { description: 'An optional header.', required: false } + }, + http_codes: [ + { code: 403, message: 'invalid pony' }, + { code: 405, message: 'no ponies left!' } + ] + + get '/simple_with_headers' do + { bla: 'something_else' } + end + end + + namespace :niles do + desc 'this takes an array of parameters', + params: { + 'items[]' => { description: 'array of items', is_array: true } + } + + post '/items' do + {} + end + end + + namespace :niles do + desc 'this uses a custom parameter', + params: { + 'custom' => { type: CustomType, description: 'array of items', is_array: true } + } + + get '/custom' do + {} + end + end + end + end + class TestApi < Grape::API - mount TheApi::NamespaceApi + mount TheApi::NamespaceApi2 add_swagger_documentation openapi_version: '3.0' end end @@ -19,6 +85,7 @@ def app describe 'retrieves swagger-documentation on /swagger_doc' do subject do get '/swagger_doc.json' + puts last_response.body JSON.parse(last_response.body) end @@ -44,6 +111,7 @@ def app describe 'retrieves the documentation for mounted-api' do subject do get '/swagger_doc/colorado.json' + puts last_response.body JSON.parse(last_response.body) end @@ -61,6 +129,7 @@ def app describe 'includes headers' do subject do get '/swagger_doc/thames.json' + puts last_response.body JSON.parse(last_response.body) end diff --git a/spec/openapi_3/params_array_collection_format_spec.rb b/spec/openapi_3/params_array_collection_format_spec.rb new file mode 100644 index 00000000..43514055 --- /dev/null +++ b/spec/openapi_3/params_array_collection_format_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Group Array Params, using collection format' do + def app + Class.new(Grape::API) do + format :json + + params do + optional :array_of_strings, type: Array[String], documentation: { desc: 'array in csv collection format', param_type: 'query' } + end + + get '/array_of_strings_without_collection_format' do + { 'declared_params' => declared(params) } + end + + params do + optional :array_of_strings, type: Array[String], documentation: { collectionFormat: 'multi', desc: 'array in multi collection format', param_type: 'query' } + end + + get '/array_of_strings_multi_collection_format' do + { 'declared_params' => declared(params) } + end + + params do + optional :array_of_strings, type: Array[String], documentation: { collectionFormat: 'foo', param_type: 'query' } + end + + get '/array_of_strings_invalid_collection_format' do + { 'declared_params' => declared(params) } + end + + params do + optional :array_of_strings, type: Array[String], desc: 'array in brackets collection format', documentation: { collectionFormat: 'brackets', param_type: 'query' } + end + + get '/array_of_strings_brackets_collection_format' do + { 'declared_params' => declared(params) } + end + + add_swagger_documentation openapi_version: '3.0' + end + end + + describe 'documentation for array parameter in default csv collectionFormat' do + subject do + get '/swagger_doc/array_of_strings_without_collection_format' + puts last_response.body + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/array_of_strings_without_collection_format']['get']['parameters']).to eql( + [ + { 'in' => 'formData', 'name' => 'array_of_strings', 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => false, 'description' => 'array in csv collection format' } + ] + ) + end + end + + describe 'documentation for array parameters in multi collectionFormat set from documentation' do + subject do + get '/swagger_doc/array_of_strings_multi_collection_format' + puts last_response.body + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/array_of_strings_multi_collection_format']['get']['parameters']).to eql( + [ + { 'in' => 'formData', 'name' => 'array_of_strings', 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => false, 'collectionFormat' => 'multi', 'description' => 'array in multi collection format' } + ] + ) + end + end + + describe 'documentation for array parameters in brackets collectionFormat set from documentation' do + subject do + get '/swagger_doc/array_of_strings_brackets_collection_format' + puts last_response.body + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/array_of_strings_brackets_collection_format']['post']['requestBody']['content']['application/x-www-form-urlencoded']).to eql( + [ + { 'in' => 'formData', 'name' => 'array_of_strings', 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => false, 'collectionFormat' => 'brackets', 'description' => 'array in brackets collection format' } + ] + ) + end + end + + describe 'documentation for array parameters with collectionFormat set to invalid option' do + subject do + get '/swagger_doc/array_of_strings_invalid_collection_format' + puts last_response.body + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/array_of_strings_invalid_collection_format']['post']['requestBody']['content']['application/x-www-form-urlencoded']).to eql( + 'schema' => { + 'properties' => { + 'array_of_strings' => { 'items' => { 'type' => 'string' }, 'type' => 'array' } + }, + 'type' => 'object' + } + ) + end + end +end diff --git a/spec/support/model_parsers/mock_parser.rb b/spec/support/model_parsers/mock_parser.rb index 46eae30f..213c116d 100644 --- a/spec/support/model_parsers/mock_parser.rb +++ b/spec/support/model_parsers/mock_parser.rb @@ -11,7 +11,7 @@ def documentation id: { type: Integer, desc: 'Identity of Something' }, text: { type: String, desc: 'Content of something.' }, links: { type: 'link', is_array: true }, - others: { type: 'text', is_array: false } + others: { type: 'string', is_array: false } } end end @@ -192,6 +192,229 @@ class ApiResponse < OpenStruct; end } end + let(:openapi_json) do + { + 'components' => { + 'schemas' => { + 'ApiError' => { + 'description' => 'This gets Things.', + 'properties' => { 'mock_data' => { 'description' => "it's a mock", 'type' => 'string' } }, + 'type' => 'object' + }, + 'QueryInput' => { + 'description' => 'nested route inside namespace', + 'properties' => { + 'mock_data' => { 'description' => "it's a mock", 'type' => 'string' } + }, + 'type' => 'object' + }, 'Something' => { + 'description' => 'This gets Things.', + 'properties' => { + 'mock_data' => { 'description' => "it's a mock", 'type' => 'string' } + }, + 'type' => 'object' + } + } + }, + 'info' => { + 'contact' => { + 'email' => 'Contact@email.com', + 'name' => 'Contact name', + 'url' => 'www.The-Contact-URL.org' + }, + 'description' => 'A description of the API.', + 'license' => { + 'name' => 'The name of the license.', + 'url' => 'www.The-URL-of-the-license.org' + }, + 'termsOfService' => 'www.The-URL-of-the-terms-and-service.com', + 'title' => 'The API title to be displayed on the API homepage.', + 'version' => '0.0.1' + }, + 'openapi' => '3.0.0', + 'paths' => { + '/dummy/{id}' => { + 'delete' => { + 'description' => 'dummy route.', + 'operationId' => 'deleteDummyId', + 'parameters' => [{ + 'in' => 'path', 'name' => 'id', 'required' => true, 'schema' => { 'format' => 'int32', 'type' => 'integer' } + }], + 'responses' => { + '204' => { 'description' => 'dummy route.' }, + '401' => { 'content' => { 'application/json' => {} }, 'description' => 'Unauthorized' } + }, + 'tags' => ['dummy'] + } + }, '/thing' => { + 'get' => { + 'description' => 'This gets Things.', + 'operationId' => 'getThing', + 'parameters' => [ + { 'description' => 'Identity of Something', + 'in' => 'query', + 'name' => 'id', + 'required' => false, + 'schema' => { 'format' => 'int32', 'type' => 'integer' } }, + { 'description' => 'Content of something.', + 'in' => 'query', + 'name' => 'text', + 'required' => false, + 'schema' => { 'type' => 'string' } }, + { 'in' => 'query', + 'name' => 'others', + 'required' => false, + 'schema' => { 'type' => 'string' } } + ], + 'responses' => { + '200' => { + 'content' => { 'application/json' => {} }, + 'description' => 'This gets Things.' + }, + '401' => { + 'content' => { 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/ApiError' } } }, + 'description' => 'Unauthorized' + } + }, 'tags' => ['thing'] + }, 'post' => { + 'description' => 'This creates Thing.', + 'operationId' => 'postThing', + 'requestBody' => { + 'content' => { + 'application/json' => { + 'schema' => { 'properties' => {}, 'type' => 'object' } + }, + 'application/x-www-form-urlencoded' => { + 'schema' => { + 'properties' => { + 'links' => { 'items' => { 'type' => 'string' }, 'type' => 'array' }, + 'text' => { 'description' => 'Content of something.', 'type' => 'string' } + }, + 'required' => %w[text links], + 'type' => 'object' + } + } + } + }, + 'responses' => { + '201' => { 'description' => 'This creates Thing.' }, + '422' => { 'content' => { 'application/json' => {} }, 'description' => 'Unprocessible Entity' } + }, + 'tags' => ['thing'] + } + }, + '/thing/{id}' => { + 'delete' => { + 'description' => 'This deletes Thing.', + 'operationId' => 'deleteThingId', + 'parameters' => [{ + 'in' => 'path', + 'name' => 'id', + 'required' => true, + 'schema' => { 'format' => 'int32', 'type' => 'integer' } + }], + 'responses' => { + '200' => { + 'content' => { 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/Something' } } }, + 'description' => 'This deletes Thing.' + } + }, + 'tags' => ['thing'] + }, + 'get' => { + 'description' => 'This gets Thing.', + 'operationId' => 'getThingId', + 'parameters' => [{ + 'in' => 'path', + 'name' => 'id', + 'required' => true, + 'schema' => { 'format' => 'int32', 'type' => 'integer' } + }], + 'responses' => { + '200' => { 'content' => { 'application/json' => {} }, 'description' => 'getting a single thing' }, + '401' => { 'content' => { 'application/json' => {} }, 'description' => 'Unauthorized' } + }, + 'tags' => ['thing'] + }, + 'put' => { + 'description' => 'This updates Thing.', + 'operationId' => 'putThingId', + 'parameters' => [{ + 'in' => 'path', + 'name' => 'id', + 'required' => true, + 'schema' => { 'format' => 'int32', 'type' => 'integer' } + }], + 'requestBody' => { + 'content' => { + 'application/json' => { 'schema' => { 'properties' => {}, 'type' => 'object' } }, + 'application/x-www-form-urlencoded' => { + 'schema' => { + 'properties' => { + 'links' => { 'items' => { 'type' => 'string' }, 'type' => 'array' }, + 'text' => { 'description' => 'Content of something.', 'type' => 'string' } + }, + 'type' => 'object' + } + } + } + }, + 'responses' => { + '200' => { + 'content' => { + 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/Something' } } + }, + 'description' => 'This updates Thing.' + } + }, + 'tags' => ['thing'] + } + }, + '/thing2' => { + 'get' => { + 'description' => 'This gets Things.', + 'operationId' => 'getThing2', + 'responses' => { + '200' => { + 'content' => { 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/Something' } } }, + 'description' => 'get Horses' + }, + '401' => { + 'content' => { + 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/ApiError' } } + }, + 'description' => 'HorsesOutError' + } + }, 'tags' => ['thing2'] + } + }, + '/v3/other_thing/{elements}' => { + 'get' => { + 'description' => 'nested route inside namespace', + 'operationId' => 'getV3OtherThingElements', + 'responses' => { + '200' => { + 'content' => { + 'application/json' => { 'schema' => { '$ref' => '#/components/schemas/QueryInput' } } + }, + 'description' => 'nested route inside namespace' + } + }, 'tags' => ['other_thing'], + 'x-amazon-apigateway-auth' => { 'type' => 'none' }, + 'x-amazon-apigateway-integration' => { 'httpMethod' => 'get', 'type' => 'aws', 'uri' => 'foo_bar_uri' } + } + } + }, + 'servers' => [{ 'url' => 'http://example.org/api' }], + 'tags' => [ + { 'description' => 'Operations about other_things', 'name' => 'other_thing' }, + { 'description' => 'Operations about things', 'name' => 'thing' }, + { 'description' => 'Operations about thing2s', 'name' => 'thing2' }, + { 'description' => 'Operations about dummies', 'name' => 'dummy' } + ] + } + end + let(:swagger_json) do { 'info' => { @@ -233,7 +456,7 @@ class ApiResponse < OpenStruct; end { 'in' => 'query', 'name' => 'id', 'description' => 'Identity of Something', 'type' => 'integer', 'format' => 'int32', 'required' => false }, { 'in' => 'query', 'name' => 'text', 'description' => 'Content of something.', 'type' => 'string', 'required' => false }, { 'in' => 'formData', 'name' => 'links', 'type' => 'array', 'items' => { 'type' => 'link' }, 'required' => false }, - { 'in' => 'query', 'name' => 'others', 'type' => 'text', 'required' => false } + { 'in' => 'query', 'name' => 'others', 'type' => 'string', 'required' => false } ], 'responses' => { '200' => { 'description' => 'This gets Things.' }, '401' => { 'description' => 'Unauthorized', 'schema' => { '$ref' => '#/definitions/ApiError' } } }, 'tags' => ['thing'],