Skip to content

Commit 7f290cd

Browse files
authored
Merge pull request #50 from MaximeRDY/features/inheritance_and_discriminator
Features/inheritance and discriminator
2 parents dfd2eb0 + 0422d68 commit 7f290cd

File tree

7 files changed

+155
-26
lines changed

7 files changed

+155
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
#### Features
44

55
* Your contribution here.
6-
7-
#### Features
8-
9-
* Your contribution here.
6+
* [#50](https://github.com/ruby-grape/grape-swagger-entity/pull/50): Features/inheritance and discriminator - [@MaximeRDY](https://github.com/MaximeRDY).
107

118
### 0.4.0 (May 30, 2020)
129

lib/grape-swagger/entity.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'grape-entity'
33

44
require 'grape-swagger/entity/version'
5+
require 'grape-swagger/entity/helper'
56
require 'grape-swagger/entity/attribute_parser'
67
require 'grape-swagger/entity/parser'
78

lib/grape-swagger/entity/attribute_parser.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def call(entity_options)
1212
entity_model = model_from(entity_options)
1313

1414
if entity_model
15-
name = endpoint.nil? ? entity_model.to_s.demodulize : endpoint.send(:expose_params_from_model, entity_model)
15+
name = GrapeSwagger::Entity::Helper.model_name(entity_model, endpoint)
1616

1717
entity_model_type = entity_model_type(name, entity_options)
1818
return entity_model_type unless documentation
@@ -39,7 +39,7 @@ def call(entity_options)
3939
add_attribute_documentation(param, documentation)
4040

4141
add_extension_documentation(param, documentation)
42-
42+
add_discriminator_extension(param, documentation)
4343
param
4444
end
4545
end
@@ -128,6 +128,10 @@ def add_array_documentation(param, documentation)
128128
def add_extension_documentation(param, documentation)
129129
GrapeSwagger::DocMethods::Extensions.add_extensions_to_root(documentation, param)
130130
end
131+
132+
def add_discriminator_extension(param, documentation)
133+
param[:documentation] = { is_discriminator: true } if documentation.key?(:is_discriminator)
134+
end
131135
end
132136
end
133137
end

lib/grape-swagger/entity/helper.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module GrapeSwagger
2+
module Entity
3+
# Helper methods for DRY
4+
class Helper
5+
class << self
6+
def model_name(entity_model, endpoint)
7+
if endpoint.nil?
8+
entity_model.to_s.demodulize
9+
else
10+
endpoint.send(:expose_params_from_model, entity_model)
11+
end
12+
end
13+
14+
def discriminator(entity_model)
15+
entity_model.superclass.root_exposures.detect do |value|
16+
value.documentation.dig(:is_discriminator)
17+
end
18+
end
19+
20+
def root_exposures_without_parent(entity_model)
21+
entity_model.root_exposures.select do |value|
22+
entity_model.superclass.root_exposures.find_by(value.attribute).nil?
23+
end
24+
end
25+
26+
def root_exposure_with_discriminator(entity_model)
27+
if discriminator(entity_model)
28+
root_exposures_without_parent(entity_model)
29+
else
30+
entity_model.root_exposures
31+
end
32+
end
33+
end
34+
end
35+
end
36+
end

lib/grape-swagger/entity/parser.rb

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def initialize(original, renamed)
2626
end
2727

2828
def extract_params(exposure)
29-
exposure.root_exposures.each_with_object({}) do |value, memo|
29+
GrapeSwagger::Entity::Helper.root_exposure_with_discriminator(exposure).each_with_object({}) do |value, memo|
3030
if value.for_merge && (value.respond_to?(:entity_class) || value.respond_to?(:using_class_name))
3131
entity_class = value.respond_to?(:entity_class) ? value.entity_class : value.using_class_name
3232

@@ -64,7 +64,39 @@ def parse_grape_entity_params(params, parent_model = nil)
6464
memo[final_entity_name][:description] = documentation[:desc] if documentation[:desc]
6565
end
6666

67-
[parsed, required_params(params)]
67+
discriminator = GrapeSwagger::Entity::Helper.discriminator(model)
68+
if discriminator
69+
respond_with_all_of(parsed, params, discriminator)
70+
else
71+
[parsed, required_params(params)]
72+
end
73+
end
74+
75+
def respond_with_all_of(parsed, params, discriminator)
76+
parent_name = GrapeSwagger::Entity::Helper.model_name(model.superclass, endpoint)
77+
78+
{
79+
allOf: [
80+
{
81+
'$ref' => "#/definitions/#{parent_name}"
82+
},
83+
[
84+
add_discriminator(parsed, discriminator),
85+
required_params(params).push(discriminator.attribute)
86+
]
87+
]
88+
}
89+
end
90+
91+
def add_discriminator(parsed, discriminator)
92+
model_name = GrapeSwagger::Entity::Helper.model_name(model, endpoint)
93+
94+
parsed.merge(
95+
discriminator.attribute => {
96+
type: 'string',
97+
enum: [model_name]
98+
}
99+
)
68100
end
69101

70102
def parse_nested(entity_name, entity_options, parent_model = nil)

spec/grape-swagger/entity/parser_spec.rb

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,74 @@
22
require_relative '../../../spec/support/shared_contexts/this_api'
33

44
describe GrapeSwagger::Entity::Parser do
5-
include_context 'this api'
5+
context 'this api' do
6+
include_context 'this api'
67

7-
describe '#call' do
8-
let(:parsed_entity) { described_class.new(ThisApi::Entities::Something, endpoint).call }
9-
let(:properties) { parsed_entity.first }
10-
let(:required) { parsed_entity.last }
8+
describe '#call' do
9+
let(:parsed_entity) { described_class.new(ThisApi::Entities::Something, endpoint).call }
10+
let(:properties) { parsed_entity.first }
11+
let(:required) { parsed_entity.last }
1112

12-
context 'when no endpoint is passed' do
13-
let(:endpoint) { nil }
13+
context 'when no endpoint is passed' do
14+
let(:endpoint) { nil }
1415

15-
it 'parses the model with the correct :using definition' do
16-
expect(properties[:kind]['$ref']).to eq('#/definitions/Kind')
17-
expect(properties[:kind2]['$ref']).to eq('#/definitions/Kind')
18-
expect(properties[:kind3]['$ref']).to eq('#/definitions/Kind')
16+
it 'parses the model with the correct :using definition' do
17+
expect(properties[:kind]['$ref']).to eq('#/definitions/Kind')
18+
expect(properties[:kind2]['$ref']).to eq('#/definitions/Kind')
19+
expect(properties[:kind3]['$ref']).to eq('#/definitions/Kind')
20+
end
21+
22+
it 'merges attributes that have merge: true defined' do
23+
expect(properties[:merged_attribute]).to be_nil
24+
expect(properties[:code][:type]).to eq('string')
25+
expect(properties[:message][:type]).to eq('string')
26+
expect(properties[:attr][:type]).to eq('string')
27+
end
28+
29+
it 'hides hidden attributes' do
30+
expect(properties).to_not include(:hidden_attr)
31+
end
1932
end
33+
end
34+
end
35+
context 'inheritance api' do
36+
include_context 'inheritance api'
2037

21-
it 'merges attributes that have merge: true defined' do
22-
expect(properties[:merged_attribute]).to be_nil
23-
expect(properties[:code][:type]).to eq('string')
24-
expect(properties[:message][:type]).to eq('string')
25-
expect(properties[:attr][:type]).to eq('string')
38+
describe '#call for Parent' do
39+
let(:parsed_entity) do
40+
described_class.new(InheritanceApi::Entities::Parent, endpoint).call
2641
end
42+
let(:properties) { parsed_entity.first }
43+
44+
context 'when no endpoint is passed' do
45+
let(:endpoint) { nil }
46+
47+
it 'parses the model with discriminator' do
48+
expect(properties[:type][:documentation]).to eq(is_discriminator: true)
49+
end
50+
end
51+
end
52+
53+
describe '#call for Child' do
54+
let(:parsed_entity) do
55+
described_class.new(InheritanceApi::Entities::Child, endpoint).call
56+
end
57+
let(:properties) { parsed_entity }
58+
59+
context 'when no endpoint is passed' do
60+
let(:endpoint) { nil }
2761

28-
it 'hides hidden attributes' do
29-
expect(properties).to_not include(:hidden_attr)
62+
it 'parses the model with allOf' do
63+
expect(properties).to include(:allOf)
64+
all_of = properties[:allOf]
65+
child_property = all_of.last.first
66+
child_required = all_of.last.last
67+
expect(all_of.first['$ref']).to eq('#/definitions/Parent')
68+
expect(child_property[:name][:type]).to eq('string')
69+
expect(child_property[:type][:type]).to eq('string')
70+
expect(child_property[:type][:enum]).to eq(['Child'])
71+
expect(child_required).to include(:type)
72+
end
3073
end
3174
end
3275
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
shared_context 'inheritance api' do
2+
before :all do
3+
module InheritanceApi
4+
module Entities
5+
class Parent < Grape::Entity
6+
expose :type, documentation: { type: 'string', is_discriminator: true, required: true }
7+
expose :id, documentation: { type: 'integer' }
8+
end
9+
10+
class Child < Parent
11+
expose :name, documentation: { type: 'string', desc: 'Name' }
12+
end
13+
end
14+
end
15+
end
16+
end

0 commit comments

Comments
 (0)