diff --git a/.travis.yml b/.travis.yml index 2a43db97..8e77265f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,14 @@ rvm: - 2.3.0 - 2.2.3 - 2.1.7 - - rbx-2 - jruby-19mode + - jruby-9.0.5.0 + - rbx-2 matrix: allow_failures: - rvm: rbx-2 + - rvm: jruby-19mode env: - GRAPE_VERSION=0.12.0 - GRAPE_VERSION=0.13.0 diff --git a/lib/grape-swagger/doc_methods/move_params.rb b/lib/grape-swagger/doc_methods/move_params.rb index d20d1592..36ab88e2 100644 --- a/lib/grape-swagger/doc_methods/move_params.rb +++ b/lib/grape-swagger/doc_methods/move_params.rb @@ -5,7 +5,7 @@ class << self def to_definition(paths, definitions) @definitions = definitions find_post_put(paths) do |path| - find_definition_and_parameters(path) + find_definition_and_params(path) end end @@ -16,41 +16,35 @@ def find_post_put(paths) end end - def find_definition_and_parameters(path) + def find_definition_and_params(path) path.keys.each do |verb| - parameters = path[verb][:parameters] + params = path[verb][:parameters] - next if parameters.nil? - next unless should_move?(parameters) + next if params.nil? + next unless should_move?(params) - unify!(parameters) + unify!(params) status_code = GrapeSwagger::DocMethods::StatusCodes.get[verb.to_sym][:code] response = path[verb][:responses][status_code] referenced_definition = parse_model(response[:schema]['$ref']) - name = build_definition(verb, referenced_definition) + name = build_definition(referenced_definition, verb) - move_params_to_new(verb, name, parameters) - @definitions[name].delete(:required) if @definitions[name][:required].empty? + move_params_to_new(name, params) path[verb][:parameters] << build_body_parameter(response.dup, name) end end - def build_definition(verb, name) - name = "#{verb}Request#{name}".to_sym - @definitions[name] = { type: 'object', properties: {}, required: [] } - - name - end - - def move_params_to_new(_, name, parameters) + def move_params_to_new(name, params) properties = {} definition = @definitions[name] - request_parameters = parameters.dup - request_parameters.each do |param| + nested_definitions(name, params, properties) + + params.dup.each do |param| next unless movable?(param) + name = param[:name].to_sym properties[name] = {} @@ -61,13 +55,37 @@ def move_params_to_new(_, name, parameters) end properties[name][:readOnly] = true unless deletable?(param) - parameters.delete(param) if deletable?(param) + params.delete(param) if deletable?(param) + definition[:required] << name if deletable?(param) && param[:required] end + definition.delete(:required) if definition[:required].empty? definition[:properties] = properties end + def nested_definitions(name, params, properties) + loop do + nested_name = params.bsearch { |x| x[:name].include?('[') } + return if nested_name.nil? + + nested_name = nested_name[:name].split('[').first + + nested, = params.partition { |x| x[:name].start_with?("#{nested_name}[") } + nested.each { |x| params.delete(x) } + nested_def_name = GrapeSwagger::DocMethods::OperationId.manipulate(nested_name) + def_name = "#{name}#{nested_def_name}" + properties[nested_name] = { '$ref' => "#/definitions/#{def_name}" } + + prepare_nested_names(nested) + build_definition(def_name) + + move_params_to_new(def_name, nested) + end + end + + private + def build_body_parameter(response, name = false) body_param = {} body_param.tap do |x| @@ -79,7 +97,22 @@ def build_body_parameter(response, name = false) end end - private + def build_definition(name, verb = nil) + name = "#{verb}Request#{name}" if verb + @definitions[name] = { type: 'object', properties: {}, required: [] } + + name + end + + def prepare_nested_names(params) + params.each do |param| + param.tap do |x| + name = x[:name].partition('[').last.sub(']', '') + name = name.partition('[').last.sub(']', '') if name.start_with?('[') + x[:name] = name + end + end + end def unify!(params) params.each do |param| @@ -110,8 +143,8 @@ def deletable?(param) false end - def should_move?(parameters) - !parameters.select { |x| x[:in] == 'body' || x[:param_type] == 'body' }.empty? + def should_move?(params) + !params.select { |x| x[:in] == 'body' || x[:param_type] == 'body' }.empty? end end end diff --git a/lib/grape-swagger/doc_methods/operation_id.rb b/lib/grape-swagger/doc_methods/operation_id.rb index 1ee0b788..c31be823 100644 --- a/lib/grape-swagger/doc_methods/operation_id.rb +++ b/lib/grape-swagger/doc_methods/operation_id.rb @@ -2,21 +2,25 @@ module GrapeSwagger module DocMethods class OperationId class << self - def build(method, path = nil) + def build(method = nil, path = nil) verb = method.to_s.downcase - unless path.nil? - operation = path.split('/').map(&:capitalize).join - operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation.include?('-') - operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_') - if path.include?('{') - operation.gsub!(/\{(\w)/, &:upcase) - operation.delete!('{').delete!('}') - end - end + operation = manipulate(path) unless path.nil? "#{verb}#{operation}" end + + def manipulate(path) + operation = path.split('/').map(&:capitalize).join + operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation.include?('-') + operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_') + if path.include?('{') + operation.gsub!(/\{(\w)/, &:upcase) + operation.delete!('{').delete!('}') + end + + operation + end end end end diff --git a/lib/grape-swagger/endpoint.rb b/lib/grape-swagger/endpoint.rb index 8fe2b595..cb0d7d31 100644 --- a/lib/grape-swagger/endpoint.rb +++ b/lib/grape-swagger/endpoint.rb @@ -168,6 +168,7 @@ def response_object(route) value[:code] = 204 end + # next if memo.key?(204) next unless !response_model.start_with?('Swagger_doc') && ((@definitions[response_model] && value[:code].to_s.start_with?('2')) || value[:model]) diff --git a/spec/lib/move_params_spec.rb b/spec/lib/move_params_spec.rb index 6109ace4..fd860bd4 100644 --- a/spec/lib/move_params_spec.rb +++ b/spec/lib/move_params_spec.rb @@ -36,18 +36,12 @@ end end - describe 'build_definition' do - let(:verb) { 'post' } - let(:name) { 'Foo' } - let(:definitions) {{}} - + describe 'find_definition_and_params' do specify do subject.instance_variable_set(:@definitions, definitions) - subject.build_definition(verb, name) + subject.find_definition_and_params(found_path) - definition = definitions.to_a.first - expect(definition.first).to eql :postRequestFoo - expect(definition.last).to eql({ type: 'object', properties: {}, required: [] }) + expect(definitions.keys).to include 'InBody', 'postRequestInBody' end end @@ -61,8 +55,8 @@ specify do subject.instance_variable_set(:@definitions, definitions) - name = subject.build_definition(verb, name) - subject.move_params_to_new(verb, name, params) + name = subject.send(:build_definition, name, verb) + subject.move_params_to_new(name, params) expect(definitions[name]).to eql expected_post_defs expect(params).to be_empty @@ -75,8 +69,8 @@ specify do subject.instance_variable_set(:@definitions, definitions) - name, definition = subject.build_definition(verb, name) - subject.move_params_to_new(verb, name, params) + name, definition = subject.send(:build_definition, name, verb) + subject.move_params_to_new(name, params) expect(definitions[name]).to eql expected_put_defs expect(params.length).to be 1 @@ -84,41 +78,96 @@ end end - describe 'find_definition' do - specify do - subject.instance_variable_set(:@definitions, definitions) - subject.find_definition_and_parameters(found_path) + describe 'nested definitions related' do + describe 'prepare_nested_names' do + before do + subject.send(:prepare_nested_names, params) + end - expect(definitions.keys).to include 'InBody', :postRequestInBody + describe 'simple' do + let(:params) {[{:in=>"body", :name=>"address[street]", :description=>"street", :type=>"string", :required=>true}]} + let(:expected) {[{:in=>"body", :name=>"street", :description=>"street", :type=>"string", :required=>true}]} + specify do + expect(params).to eql expected + end + end + + describe 'nested' do + let(:params) {[{:in=>"body", :name=>"address[street][name]", :description=>"street", :type=>"string", :required=>true}]} + let(:expected) {[{:in=>"body", :name=>"street[name]", :description=>"street", :type=>"string", :required=>true}]} + specify do + expect(params).to eql expected + end + end + + describe 'array' do + let(:params) {[{:in=>"body", :name=>"address[][street_lines]", :description=>"street lines", :type=>"array", :required=>true}]} + let(:expected) {[{:in=>"body", :name=>"street_lines", :description=>"street lines", :type=>"array", :required=>true}]} + specify do + expect(params).to eql expected + end + end end end - describe 'build_body_parameter' do - let(:response) {{ schema: { '$ref' => '#/definitions/Somewhere'} }} + describe 'private methods' do + describe 'build_definition' do + before do + subject.instance_variable_set(:@definitions, definitions) + subject.send(:build_definition, name, verb) + end - describe 'no name given' do - let(:expected_param) { - {:name=>"Somewhere", :in=>"body", :required=>true, :schema=>{'$ref' => "#/definitions/Somewhere"}} - } - specify do - parameter = subject.build_body_parameter(response) - expect(parameter).to eql expected_param + describe 'verb given' do + let(:verb) { 'post' } + let(:name) { 'Foo' } + let(:definitions) {{}} + + specify do + definition = definitions.to_a.first + expect(definition.first).to eql 'postRequestFoo' + expect(definition.last).to eql({ type: 'object', properties: {}, required: [] }) + end + end + + describe 'no verb given' do + let(:name) { 'FooBar' } + let(:definitions) {{}} + let(:verb) { nil } + + specify do + definition = definitions.to_a.first + expect(definition.first).to eql 'FooBar' + expect(definition.last).to eql({ type: 'object', properties: {}, required: [] }) + end end end - describe 'name given' do - let(:name) { 'Foo' } - let(:expected_param) { - {:name=>"Somewhere", :in=>"body", :required=>true, :schema=>{'$ref' => "#/definitions/#{name}"}} - } - specify do - parameter = subject.build_body_parameter(response, name) - expect(parameter).to eql expected_param + describe 'build_body_parameter' do + let(:response) {{ schema: { '$ref' => '#/definitions/Somewhere'} }} + + describe 'no name given' do + let(:name) { nil } + let(:expected_param) { + {:name=>"Somewhere", :in=>"body", :required=>true, :schema=>{'$ref' => "#/definitions/Somewhere"}} + } + specify do + parameter = subject.send(:build_body_parameter, response) + expect(parameter).to eql expected_param + end + end + + describe 'name given' do + let(:name) { 'Foo' } + let(:expected_param) { + {:name=>"Somewhere", :in=>"body", :required=>true, :schema=>{'$ref' => "#/definitions/#{name}"}} + } + specify do + parameter = subject.send(:build_body_parameter, response, name) + expect(parameter).to eql expected_param + end end end - end - describe 'private methods' do describe 'parse_model' do let(:ref) { '#/definitions/InBody' } subject(:object) { described_class.send(:parse_model, ref) } @@ -245,6 +294,5 @@ it { expect(params).to eql expected_params } end end - end end diff --git a/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb b/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb new file mode 100644 index 00000000..862c32dc --- /dev/null +++ b/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' + +describe 'setting of param type, such as `query`, `path`, `formData`, `body`, `header`' do + include_context "the api entities" + + before :all do + module TheApi + class NestedBodyParamTypeApi < Grape::API + namespace :simple_nested_params do + desc 'post in body with nested parameters', + success: TheApi::Entities::UseNestedWithAddress + params do + requires :name, type: String, documentation: { desc: 'name', in: 'body' } + optional :address, type: Hash do + requires :street, type: String, documentation: { desc: 'street', in: 'body' } + requires :postcode, type: String, documentation: { desc: 'postcode', in: 'body' } + requires :city, type: String, documentation: { desc: 'city', in: 'body' } + optional :country, type: String, documentation: { desc: 'country', in: 'body' } + end + end + + post '/in_body' do + { "declared_params" => declared(params) } + end + + desc 'put in body with nested parameters', + success: TheApi::Entities::UseNestedWithAddress + params do + requires :id, type: Integer + optional :name, type: String, documentation: { desc: 'name', in: 'body' } + optional :address, type: Hash do + optional :street, type: String, documentation: { desc: 'street', in: 'body' } + optional :postcode, type: String, documentation: { desc: 'postcode', in: 'formData' } + optional :city, type: String, documentation: { desc: 'city', in: 'body' } + optional :country, type: String, documentation: { desc: 'country', in: 'body' } + end + end + + put '/in_body/:id' do + { "declared_params" => declared(params) } + end + end + + namespace :multiple_nested_params do + desc 'put in body with multiple nested parameters', + success: TheApi::Entities::UseNestedWithAddress + params do + requires :id, type: Integer + optional :name, type: String, documentation: { desc: 'name', in: 'body' } + optional :address, type: Hash do + optional :street, type: String, documentation: { desc: 'street', in: 'body' } + requires :postcode, type: String, documentation: { desc: 'postcode', in: 'formData' } + optional :city, type: String, documentation: { desc: 'city', in: 'body' } + optional :country, type: String, documentation: { desc: 'country', in: 'body' } + end + optional :delivery_address, type: Hash do + optional :street, type: String, documentation: { desc: 'street', in: 'body' } + optional :postcode, type: String, documentation: { desc: 'postcode', in: 'formData' } + optional :city, type: String, documentation: { desc: 'city', in: 'body' } + optional :country, type: String, documentation: { desc: 'country', in: 'body' } + end + end + + put '/in_body/:id' do + { "declared_params" => declared(params) } + end + end + + add_swagger_documentation + end + end + end + + def app + TheApi::NestedBodyParamTypeApi + end + + describe 'nested body parameters given' do + subject do + get '/swagger_doc/simple_nested_params' + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/simple_nested_params/in_body']['post']['parameters']).to eql([{ + "name"=>"UseNestedWithAddress", + "in"=>"body", + "required"=>true, + "schema"=>{"$ref"=>"#/definitions/postRequestUseNestedWithAddress"} + }]) + end + + specify do + expect(subject['definitions']['postRequestUseNestedWithAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "address"=>{"$ref"=>"#/definitions/postRequestUseNestedWithAddressAddress"}, + "name"=>{"type"=>"string", "description"=>"name"} + }, + "required"=>["name"] + }) + expect(subject['definitions']['postRequestUseNestedWithAddressAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "street"=>{"type"=>"string", "description"=>"street"}, + "postcode"=>{"type"=>"string", "description"=>"postcode"}, + "city"=>{"type"=>"string", "description"=>"city"}, + "country"=>{"type"=>"string", "description"=>"country"} + }, + "required"=>["street", "postcode", "city"] + }) + end + + specify do + expect(subject['paths']['/simple_nested_params/in_body/{id}']['put']['parameters']).to eql([ + {"in"=>"path", "name"=>"id", "description"=>nil, "type"=>"integer", "format"=>"int32", "required"=>true}, + { + "name"=>"UseNestedWithAddress", + "in"=>"body", + "required"=>true, + "schema"=>{"$ref"=>"#/definitions/putRequestUseNestedWithAddress"} + } + ]) + end + + specify do + expect(subject['definitions']['putRequestUseNestedWithAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "address"=>{"$ref"=>"#/definitions/putRequestUseNestedWithAddressAddress"}, + "id"=>{"type"=>"integer", "format"=>"int32", "readOnly"=>true}, + "name"=>{"type"=>"string", "description"=>"name"} + }}) + expect(subject['definitions']['putRequestUseNestedWithAddressAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "street"=>{"type"=>"string", "description"=>"street"}, + "postcode"=>{"type"=>"string", "description"=>"postcode"}, + "city"=>{"type"=>"string", "description"=>"city"}, + "country"=>{"type"=>"string", "description"=>"country"} + }}) + end + end + + describe 'multiple nested body parameters given' do + subject do + get '/swagger_doc/multiple_nested_params' + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/multiple_nested_params/in_body/{id}']['put']['parameters']).to eql([ + {"in"=>"path", "name"=>"id", "description"=>nil, "type"=>"integer", "format"=>"int32", "required"=>true}, + {"name"=>"UseNestedWithAddress", "in"=>"body", "required"=>true, "schema"=>{"$ref"=>"#/definitions/putRequestUseNestedWithAddress"}} + ]) + end + + specify do + expect(subject['definitions']['putRequestUseNestedWithAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "address"=>{"$ref"=>"#/definitions/putRequestUseNestedWithAddressAddress"}, + "delivery_address"=>{"$ref"=>"#/definitions/putRequestUseNestedWithAddressDeliveryAddress"}, + "id"=>{"type"=>"integer", "format"=>"int32", "readOnly"=>true}, + "name"=>{"type"=>"string", "description"=>"name"} + }}) + expect(subject['definitions']['putRequestUseNestedWithAddressAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "street"=>{"type"=>"string", "description"=>"street"}, + "postcode"=>{"type"=>"string", "description"=>"postcode"}, + "city"=>{"type"=>"string", "description"=>"city"}, + "country"=>{"type"=>"string", "description"=>"country"} + }, + "required"=>["postcode"] + }) + expect(subject['definitions']['putRequestUseNestedWithAddressDeliveryAddress']).to eql({ + "type"=>"object", + "properties"=>{ + "street"=>{"type"=>"string", "description"=>"street"}, + "postcode"=>{"type"=>"string", "description"=>"postcode"}, + "city"=>{"type"=>"string", "description"=>"city"}, + "country"=>{"type"=>"string", "description"=>"country"} + }}) + end + end +end diff --git a/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb b/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb index b2e8fc76..b722e4ed 100644 --- a/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +++ b/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb @@ -54,41 +54,6 @@ class BodyParamTypeApi < Grape::API end end - # namespace :nested_params do - # desc 'post in body with entity', - # success: TheApi::Entities::UseNestedWithAddress - # params do - # requires :name, type: String, documentation: { desc: 'name', in: 'body' } - # optional :address, type: Hash do - # requires :street, type: String, documentation: { desc: 'street', in: 'body' } - # requires :postcode, type: String, documentation: { desc: 'postcode', in: 'body' } - # requires :city, type: String, documentation: { desc: 'city', in: 'body' } - # optional :country, type: String, documentation: { desc: 'country', in: 'body' } - # end - # end - # - # post '/in_body' do - # { "declared_params" => declared(params) } - # end - # - # desc 'put in body with entity', - # success: TheApi::Entities::UseNestedWithAddress - # params do - # requires :id, type: Integer - # optional :name, type: String, documentation: { desc: 'name', in: 'body' } - # optional :address, type: Hash do - # optional :street, type: String, documentation: { desc: 'street', in: 'body' } - # optional :postcode, type: String, documentation: { desc: 'postcode', in: 'formData' } - # optional :city, type: String, documentation: { desc: 'city', in: 'body' } - # optional :country, type: String, documentation: { desc: 'country', in: 'body' } - # end - # end - # - # put '/in_body/:id' do - # { "declared_params" => declared(params) } - # end - # end - add_swagger_documentation end end @@ -179,15 +144,4 @@ def app }) end end - - # describe 'nested body parameters given' do - # subject do - # get '/swagger_doc/nested_params' - # JSON.parse(last_response.body) - # end - # - # specify do - # # ap subject - # end - # end end