Skip to content

Commit

Permalink
Features/inheritance and discriminator (#793)
Browse files Browse the repository at this point in the history
* 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 <pscholz.le@gmail.com>
  • Loading branch information
MaximeRVY and LeFnord committed Jun 30, 2020
1 parent ff5b610 commit e86c9ba
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### Features

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

#### Fixes

Expand Down
106 changes: 97 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ This screenshot is based on the [Hussars](https://github.com/LeFnord/hussars) sa
The following versions of grape, grape-entity and grape-swagger can currently be used together.

| grape-swagger | swagger spec | grape | grape-entity | representable |
|---------------|--------------|-------------------------|--------------|---------------|
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |
| ------------- | ------------ | ----------------------- | ------------ | ------------- |
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |


## Swagger-Spec <a name="swagger-spec"></a>
Expand Down Expand Up @@ -1381,6 +1381,94 @@ module API
end
```

#### Inheritance with allOf and discriminator
```ruby
module Entities
class Pet < Grape::Entity
expose :type, documentation: {
type: 'string',
is_discriminator: true,
required: true
}
expose :name, documentation: {
type: 'string',
required: true
}
end

class Cat < Pet
expose :huntingSkill, documentation: {
type: 'string',
description: 'The measured skill for hunting',
default: 'lazy',
values: %w[
clueless
lazy
adventurous
aggressive
]
}
end
end
```

Should generate this definitions:
```JSON
{
"definitions": {
"Pet": {
"type": "object",
"discriminator": "petType",
"properties": {
"name": {
"type": "string"
},
"petType": {
"type": "string"
}
},
"required": [
"name",
"petType"
]
},
"Cat": {
"description": "A representation of a cat",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"huntingSkill": {
"type": "string",
"description": "The measured skill for hunting",
"default": "lazy",
"enum": [
"clueless",
"lazy",
"adventurous",
"aggressive"
]
},
"petType": {
"type": "string",
"enum": ["Cat"]
}
},
"required": [
"huntingSkill",
"petType"
]
}
]
}
}
}
```




## Securing the Swagger UI <a name="oauth"></a>
Expand Down
55 changes: 53 additions & 2 deletions lib/grape-swagger/doc_methods/build_model_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module GrapeSwagger
module DocMethods
class BuildModelDefinition
class << self
def build(model, properties, required)
definition = { type: 'object', properties: properties }
def build(model, properties, required, other_def_properties = {})
definition = { type: 'object', properties: properties }.merge(other_def_properties)

if required.nil?
required_attrs = required_attributes(model)
Expand All @@ -17,6 +17,57 @@ def build(model, properties, required)
definition
end

def parse_params_from_model(parsed_response, model, model_name)
if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
refs_or_models = parsed_response[:allOf]
parsed = parse_refs_and_models(refs_or_models, model)

{
allOf: parsed
}
else
properties, required = parsed_response
unless properties&.any?
raise GrapeSwagger::Errors::SwaggerSpec,
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
end
properties, other_def_properties = parse_properties(properties)

build(
model, properties, required, other_def_properties
)
end
end

def parse_properties(properties)
other_properties = {}

discriminator_key, discriminator_value =
properties.find do |_key, value|
value[:documentation].try(:[], :is_discriminator)
end

if discriminator_key
discriminator_value.delete(:documentation)
properties[discriminator_key] = discriminator_value

other_properties[:discriminator] = discriminator_key
end

[properties, other_properties]
end

def parse_refs_and_models(refs_or_models, model)
refs_or_models.map do |ref_or_models|
if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
ref_or_models
else
properties, required = ref_or_models
GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
end
end
end

private

def required_attributes(model)
Expand Down
18 changes: 14 additions & 4 deletions lib/grape-swagger/doc_methods/parse_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ def document_array_param(value_type, definitions)

param_type ||= value_type[:param_type]

array_items = parse_array_item(
definitions,
type,
value_type
)

@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 parse_array_item(definitions, type, value_type)
array_items = {}
if definitions[value_type[:data_type]]
array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
Expand All @@ -91,10 +104,7 @@ def document_array_param(value_type, definitions)

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)
array_items
end

def document_additional_properties(settings)
Expand Down
21 changes: 11 additions & 10 deletions lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,7 @@ def params_object(route, options, path)
end

def response_object(route, options)
codes = http_codes_from_route(route)
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }

codes.each_with_object({}) do |value, memo|
codes(route).each_with_object({}) do |value, memo|
value[:message] ||= ''
memo[value[:code]] = { description: value[:message] }

Expand All @@ -225,6 +222,12 @@ def response_object(route, options)
end
end

def codes(route)
http_codes_from_route(route).map do |x|
x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
end
end

def success_code?(code)
status = code.is_a?(Array) ? code.first : code[:code]
status.between?(200, 299)
Expand Down Expand Up @@ -340,12 +343,10 @@ def expose_params_from_model(model)
parser = GrapeSwagger.model_parsers.find(model)
raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser

properties, required = parser.new(model, self).call
unless properties&.any?
raise GrapeSwagger::Errors::SwaggerSpec,
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
end
@definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
parsed_response = parser.new(model, self).call

@definitions[model_name] =
GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)

model_name
end
Expand Down
56 changes: 56 additions & 0 deletions spec/swagger_v2/inheritance_and_discriminator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'Inheritance and Discriminator' do
before :all do
module InheritanceTest
module Entities
# example from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#models-with-polymorphism-supports
class Pet < Grape::Entity
expose :type, documentation: {
type: 'string',
is_discriminator: true,
required: true
}
expose :name, documentation: {
type: 'string',
required: true
}
end

class Cat < Pet
expose :huntingSkill, documentation: {
type: 'string',
description: 'The measured skill for hunting',
default: 'lazy',
values: %w[
clueless
lazy
adventurous
aggressive
]
}
end
end
class NameApi < Grape::API
add_swagger_documentation models: [Entities::Pet, Entities::Cat]
end
end
end

context 'Parent model' do
let(:app) { InheritanceTest::NameApi }

subject do
get '/swagger_doc'
JSON.parse(last_response.body)['definitions']
end

specify do
subject['InheritanceTest::Entities::Pet'].key?('discriminator')
subject['InheritanceTest::Entities::Pet']['discriminator'] = 'type'
subject['InheritanceTest::Entities::Cat'].key?('allOf')
end
end
end

0 comments on commit e86c9ba

Please sign in to comment.