Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
--format documentation
--color
--require "spec_helper"
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Metrics/AbcSize:
# Offense count: 2
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 117
Max: 132

# Offense count: 2
# Configuration parameters: AllowedMethods, AllowedPatterns.
Expand Down
18 changes: 14 additions & 4 deletions lib/grape-swagger/entity/attribute_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def call(entity_options)
documentation = entity_options[:documentation]
return param if documentation.nil?

add_array_documentation(param, documentation) if documentation[:is_array]
add_array_documentation(param, documentation) if array_type?(documentation)

add_attribute_sample(param, documentation, :default)
add_attribute_sample(param, documentation, :example)
Expand All @@ -37,11 +37,19 @@ def call(entity_options)
def model_from(entity_options)
model = entity_options[:using] if entity_options[:using].present?

model ||= entity_options[:documentation][:type] if could_it_be_a_model?(entity_options[:documentation])
documentation = entity_options[:documentation]
model ||= documentation[:type] if could_it_be_a_model?(documentation)

model
end

def array_type?(documentation)
return documentation[:is_array] if documentation.key?(:is_array)
return true if documentation[:type].to_s.downcase == 'array'

documentation[:type].to_s.match?(/\Aarray\[(?<type>.+)\]\z/i)
end

def could_it_be_a_model?(value)
return false if value.nil?

Expand All @@ -57,6 +65,8 @@ def ambiguous_model_type?(type)
end

def primitive_type?(type)
return false if type.nil?

data_type = GrapeSwagger::DocMethods::DataType.call(type)
GrapeSwagger::DocMethods::DataType.request_primitive?(data_type)
end
Expand All @@ -69,7 +79,7 @@ def data_type_from(entity_options)

documented_data_type = document_data_type(documentation, data_type)

if documentation[:is_array]
if array_type?(documentation)
{
type: :array,
items: documented_data_type
Expand Down Expand Up @@ -97,7 +107,7 @@ def document_data_type(documentation, data_type)
end

def entity_model_type(name, entity_options)
if entity_options[:documentation] && entity_options[:documentation][:is_array]
if array_type?(entity_options[:documentation])
{
'type' => 'array',
'items' => {
Expand Down
68 changes: 44 additions & 24 deletions lib/grape-swagger/entity/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,37 +43,57 @@ def extract_params(exposure)
def parse_grape_entity_params(params, parent_model = nil)
return unless params

parsed = params.each_with_object({}) do |(entity_name, entity_options), memo|
documentation_options = entity_options.fetch(:documentation, {})
in_option = documentation_options.fetch(:in, nil).to_s
hidden_option = documentation_options.fetch(:hidden, nil)
next if in_option == 'header' || hidden_option == true

entity_name = entity_name.original if entity_name.is_a?(Alias)
final_entity_name = entity_options.fetch(:as, entity_name)
documentation = entity_options[:documentation]

memo[final_entity_name] = if entity_options[:nesting]
parse_nested(entity_name, entity_options, parent_model)
else
attribute_parser.call(entity_options)
end

next unless documentation

memo[final_entity_name][:readOnly] = documentation[:read_only].to_s == 'true' if documentation[:read_only]
memo[final_entity_name][:description] = documentation[:desc] if documentation[:desc]
required = required_params(params)
parsed_params = parse_params(params, parent_model)

handle_discriminator(parsed_params, required)
end

def parse_params(params, parent_model)
params.each_with_object({}) do |(entity_name, entity_options), memo|
next if skip_param?(entity_options)

original_entity_name = entity_name.is_a?(Alias) ? entity_name.original : entity_name
final_entity_name = entity_options.fetch(:as, original_entity_name)

memo[final_entity_name] = parse_entity_options(entity_options, original_entity_name, parent_model)
add_documentation_to_memo(memo[final_entity_name], entity_options[:documentation])
end
end

def skip_param?(entity_options)
documentation_options = entity_options.fetch(:documentation, {})
in_option = documentation_options.fetch(:in, nil).to_s
hidden_option = documentation_options.fetch(:hidden, nil)

in_option == 'header' || hidden_option == true
end

def parse_entity_options(entity_options, entity_name, parent_model)
if entity_options[:nesting]
parse_nested(entity_name, entity_options, parent_model)
else
attribute_parser.call(entity_options)
end
end

def add_documentation_to_memo(memo_entry, documentation)
return unless documentation

memo_entry[:readOnly] = documentation[:read_only].to_s == 'true' if documentation[:read_only]
memo_entry[:description] = documentation[:desc] if documentation[:desc]
end

def handle_discriminator(parsed, required)
discriminator = GrapeSwagger::Entity::Helper.discriminator(model)
if discriminator
respond_with_all_of(parsed, params, discriminator)
respond_with_all_of(parsed, required, discriminator)
else
[parsed, required_params(params)]
[parsed, required]
end
end

def respond_with_all_of(parsed, params, discriminator)
def respond_with_all_of(parsed, required, discriminator)
parent_name = GrapeSwagger::Entity::Helper.model_name(model.superclass, endpoint)

{
Expand All @@ -83,7 +103,7 @@ def respond_with_all_of(parsed, params, discriminator)
},
[
add_discriminator(parsed, discriminator),
required_params(params).push(discriminator.attribute)
required.push(discriminator.attribute)
]
]
}
Expand Down
2 changes: 2 additions & 0 deletions spec/grape-swagger/entities/response_model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ def app
JSON.parse(last_response.body)['definitions']
end

include_context 'this api'

before :all do
module TheseApi
module Entities
Expand Down
122 changes: 122 additions & 0 deletions spec/issues/962_polymorphic_entity_with_custom_documentation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# frozen_string_literal: true

describe '#962 empty entity with custom documentation type' do
context "when entity has no properties" do
let(:app) do
Class.new(Grape::API) do
namespace :issue962 do
class Foo < Grape::Entity
end

class Report < Grape::Entity
expose :foo,
as: :bar,
using: Foo,
documentation: {
type: 'Array[object]',
desc: 'The bar in your report',
example: {
'id' => 'string',
'status' => 'string',
}
}
end

desc 'Get a report', success: Report
get '/' do
present({ foo: [] }, with: Report)
end
end

add_swagger_documentation format: :json
end
end

subject(:swagger_doc) do
get '/swagger_doc'
JSON.parse(last_response.body)
end

specify do
expect(swagger_doc['definitions']['Report']['properties']['bar']).to eql({
'type' => 'array',
'description' => 'The bar in your report',
'items' => {
'$ref' => '#/definitions/Foo'
},
'example' => {
'id' => 'string',
'status' => 'string'
}
})
end

specify do
expect(swagger_doc['definitions']['Foo']).to eql({
'type' => 'object',
'properties' => {},
})
end
end

context "when entity has only hidden properties" do
let(:app) do
Class.new(Grape::API) do
namespace :issue962 do
class Foo < Grape::Entity
expose :required_prop, documentation: { hidden: true }
expose :optional_prop, documentation: { hidden: true }, if: ->() { true }
end

class Report < Grape::Entity
expose :foo,
as: :bar,
using: Foo,
documentation: {
type: 'Array[object]',
desc: 'The bar in your report',
example: {
'id' => 'string',
'status' => 'string',
}
}
end

desc 'Get a report', success: Report
get '/' do
present({ foo: [] }, with: Report)
end
end

add_swagger_documentation format: :json
end
end

subject(:swagger_doc) do
get '/swagger_doc'
JSON.parse(last_response.body)
end

specify do
expect(swagger_doc['definitions']['Report']['properties']['bar']).to eql({
'type' => 'array',
'description' => 'The bar in your report',
'items' => {
'$ref' => '#/definitions/Foo'
},
'example' => {
'id' => 'string',
'status' => 'string'
}
})
end

it "hides optional properties only" do
expect(swagger_doc['definitions']['Foo']).to eql({
'type' => 'object',
'properties' => {},
'required' => ['required_prop'],
})
end
end
end
Loading