Skip to content

Commit e86c9ba

Browse files
MaximeRVYLeFnord
andauthored
Features/inheritance and discriminator (#793)
* Add Discrimator * Fix rubocop and add test * Add Changelog * Fix spec_helper and move test * Add Readme * Point grape-swagger-entity to rubygems again * Force new test * Fix rubocop Co-authored-by: peter scholz <[email protected]>
1 parent ff5b610 commit e86c9ba

File tree

6 files changed

+232
-25
lines changed

6 files changed

+232
-25
lines changed

CHANGELOG.md

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

55
* Your contribution here.
6+
* [#793](https://github.com/ruby-grape/grape-swagger/pull/793): Features/inheritance and discriminator - [@MaximeRDY](https://github.com/MaximeRDY).
67

78
#### Fixes
89

README.md

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ This screenshot is based on the [Hussars](https://github.com/LeFnord/hussars) sa
4444
The following versions of grape, grape-entity and grape-swagger can currently be used together.
4545

4646
| grape-swagger | swagger spec | grape | grape-entity | representable |
47-
|---------------|--------------|-------------------------|--------------|---------------|
48-
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
49-
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
50-
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
51-
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
52-
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
53-
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
54-
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
55-
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |
47+
| ------------- | ------------ | ----------------------- | ------------ | ------------- |
48+
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
49+
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
50+
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
51+
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
52+
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
53+
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
54+
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
55+
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |
5656

5757

5858
## Swagger-Spec <a name="swagger-spec"></a>
@@ -1381,6 +1381,94 @@ module API
13811381
end
13821382
```
13831383

1384+
#### Inheritance with allOf and discriminator
1385+
```ruby
1386+
module Entities
1387+
class Pet < Grape::Entity
1388+
expose :type, documentation: {
1389+
type: 'string',
1390+
is_discriminator: true,
1391+
required: true
1392+
}
1393+
expose :name, documentation: {
1394+
type: 'string',
1395+
required: true
1396+
}
1397+
end
1398+
1399+
class Cat < Pet
1400+
expose :huntingSkill, documentation: {
1401+
type: 'string',
1402+
description: 'The measured skill for hunting',
1403+
default: 'lazy',
1404+
values: %w[
1405+
clueless
1406+
lazy
1407+
adventurous
1408+
aggressive
1409+
]
1410+
}
1411+
end
1412+
end
1413+
```
1414+
1415+
Should generate this definitions:
1416+
```JSON
1417+
{
1418+
"definitions": {
1419+
"Pet": {
1420+
"type": "object",
1421+
"discriminator": "petType",
1422+
"properties": {
1423+
"name": {
1424+
"type": "string"
1425+
},
1426+
"petType": {
1427+
"type": "string"
1428+
}
1429+
},
1430+
"required": [
1431+
"name",
1432+
"petType"
1433+
]
1434+
},
1435+
"Cat": {
1436+
"description": "A representation of a cat",
1437+
"allOf": [
1438+
{
1439+
"$ref": "#/definitions/Pet"
1440+
},
1441+
{
1442+
"type": "object",
1443+
"properties": {
1444+
"huntingSkill": {
1445+
"type": "string",
1446+
"description": "The measured skill for hunting",
1447+
"default": "lazy",
1448+
"enum": [
1449+
"clueless",
1450+
"lazy",
1451+
"adventurous",
1452+
"aggressive"
1453+
]
1454+
},
1455+
"petType": {
1456+
"type": "string",
1457+
"enum": ["Cat"]
1458+
}
1459+
},
1460+
"required": [
1461+
"huntingSkill",
1462+
"petType"
1463+
]
1464+
}
1465+
]
1466+
}
1467+
}
1468+
}
1469+
```
1470+
1471+
13841472

13851473

13861474
## Securing the Swagger UI <a name="oauth"></a>

lib/grape-swagger/doc_methods/build_model_definition.rb

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ module GrapeSwagger
44
module DocMethods
55
class BuildModelDefinition
66
class << self
7-
def build(model, properties, required)
8-
definition = { type: 'object', properties: properties }
7+
def build(model, properties, required, other_def_properties = {})
8+
definition = { type: 'object', properties: properties }.merge(other_def_properties)
99

1010
if required.nil?
1111
required_attrs = required_attributes(model)
@@ -17,6 +17,57 @@ def build(model, properties, required)
1717
definition
1818
end
1919

20+
def parse_params_from_model(parsed_response, model, model_name)
21+
if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
22+
refs_or_models = parsed_response[:allOf]
23+
parsed = parse_refs_and_models(refs_or_models, model)
24+
25+
{
26+
allOf: parsed
27+
}
28+
else
29+
properties, required = parsed_response
30+
unless properties&.any?
31+
raise GrapeSwagger::Errors::SwaggerSpec,
32+
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
33+
end
34+
properties, other_def_properties = parse_properties(properties)
35+
36+
build(
37+
model, properties, required, other_def_properties
38+
)
39+
end
40+
end
41+
42+
def parse_properties(properties)
43+
other_properties = {}
44+
45+
discriminator_key, discriminator_value =
46+
properties.find do |_key, value|
47+
value[:documentation].try(:[], :is_discriminator)
48+
end
49+
50+
if discriminator_key
51+
discriminator_value.delete(:documentation)
52+
properties[discriminator_key] = discriminator_value
53+
54+
other_properties[:discriminator] = discriminator_key
55+
end
56+
57+
[properties, other_properties]
58+
end
59+
60+
def parse_refs_and_models(refs_or_models, model)
61+
refs_or_models.map do |ref_or_models|
62+
if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
63+
ref_or_models
64+
else
65+
properties, required = ref_or_models
66+
GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
67+
end
68+
end
69+
end
70+
2071
private
2172

2273
def required_attributes(model)

lib/grape-swagger/doc_methods/parse_params.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ def document_array_param(value_type, definitions)
7777

7878
param_type ||= value_type[:param_type]
7979

80+
array_items = parse_array_item(
81+
definitions,
82+
type,
83+
value_type
84+
)
85+
86+
@parsed_param[:in] = param_type || 'formData'
87+
@parsed_param[:items] = array_items
88+
@parsed_param[:type] = 'array'
89+
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
90+
end
91+
92+
def parse_array_item(definitions, type, value_type)
8093
array_items = {}
8194
if definitions[value_type[:data_type]]
8295
array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
@@ -91,10 +104,7 @@ def document_array_param(value_type, definitions)
91104

92105
array_items[:default] = value_type[:default] if value_type[:default].present?
93106

94-
@parsed_param[:in] = param_type || 'formData'
95-
@parsed_param[:items] = array_items
96-
@parsed_param[:type] = 'array'
97-
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
107+
array_items
98108
end
99109

100110
def document_additional_properties(settings)

lib/grape-swagger/endpoint.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,7 @@ def params_object(route, options, path)
196196
end
197197

198198
def response_object(route, options)
199-
codes = http_codes_from_route(route)
200-
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
201-
202-
codes.each_with_object({}) do |value, memo|
199+
codes(route).each_with_object({}) do |value, memo|
203200
value[:message] ||= ''
204201
memo[value[:code]] = { description: value[:message] }
205202

@@ -225,6 +222,12 @@ def response_object(route, options)
225222
end
226223
end
227224

225+
def codes(route)
226+
http_codes_from_route(route).map do |x|
227+
x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
228+
end
229+
end
230+
228231
def success_code?(code)
229232
status = code.is_a?(Array) ? code.first : code[:code]
230233
status.between?(200, 299)
@@ -340,12 +343,10 @@ def expose_params_from_model(model)
340343
parser = GrapeSwagger.model_parsers.find(model)
341344
raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
342345

343-
properties, required = parser.new(model, self).call
344-
unless properties&.any?
345-
raise GrapeSwagger::Errors::SwaggerSpec,
346-
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
347-
end
348-
@definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
346+
parsed_response = parser.new(model, self).call
347+
348+
@definitions[model_name] =
349+
GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
349350

350351
model_name
351352
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'Inheritance and Discriminator' do
6+
before :all do
7+
module InheritanceTest
8+
module Entities
9+
# example from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#models-with-polymorphism-supports
10+
class Pet < Grape::Entity
11+
expose :type, documentation: {
12+
type: 'string',
13+
is_discriminator: true,
14+
required: true
15+
}
16+
expose :name, documentation: {
17+
type: 'string',
18+
required: true
19+
}
20+
end
21+
22+
class Cat < Pet
23+
expose :huntingSkill, documentation: {
24+
type: 'string',
25+
description: 'The measured skill for hunting',
26+
default: 'lazy',
27+
values: %w[
28+
clueless
29+
lazy
30+
adventurous
31+
aggressive
32+
]
33+
}
34+
end
35+
end
36+
class NameApi < Grape::API
37+
add_swagger_documentation models: [Entities::Pet, Entities::Cat]
38+
end
39+
end
40+
end
41+
42+
context 'Parent model' do
43+
let(:app) { InheritanceTest::NameApi }
44+
45+
subject do
46+
get '/swagger_doc'
47+
JSON.parse(last_response.body)['definitions']
48+
end
49+
50+
specify do
51+
subject['InheritanceTest::Entities::Pet'].key?('discriminator')
52+
subject['InheritanceTest::Entities::Pet']['discriminator'] = 'type'
53+
subject['InheritanceTest::Entities::Cat'].key?('allOf')
54+
end
55+
end
56+
end

0 commit comments

Comments
 (0)