-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for discriminator #32
Changes from 4 commits
c844e37
10e9070
c5fc10c
7c829c0
07d8832
648ce67
0ac87ef
3534db6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# TODO: support 'not' because I need check reference... | ||
# TODO: support 'discriminator', 'xml', 'externalDocs' | ||
# TODO: support 'xml', 'externalDocs' | ||
# TODO: support extended property | ||
|
||
module OpenAPIParser::Schemas | ||
|
@@ -73,6 +73,7 @@ class Schema < Base | |
:example, | ||
:deprecated | ||
|
||
|
||
# @!attribute [r] read_only | ||
# @return [Boolean, nil] | ||
openapi_attr_value :read_only, schema_key: :readOnly | ||
|
@@ -101,6 +102,18 @@ class Schema < Base | |
# @return [Hash{String => Schema}, nil] | ||
openapi_attr_hash_object :properties, Schema, reference: true | ||
|
||
# @!attribute [r] discriminator | ||
# @return [Hash{String => Schema}, nil] | ||
openapi_attr_objects :discriminator, Schema | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, we defines schemas to be the same attribute in OpenAPI 3 specification as much as possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that make sense, |
||
|
||
# @!attribute [r] property_name | ||
# @return [String, nil] | ||
openapi_attr_value :property_name, schema_key: :propertyName | ||
|
||
# @!attribute [r] mapping | ||
# @return [Hash{String => String] | ||
openapi_attr_value :mapping | ||
|
||
# @!attribute [r] additional_properties | ||
# @return [Boolean, Schema, Reference, nil] | ||
openapi_attr_object :additional_properties, Schema, reference: true, allow_data_type: true, schema_key: :additionalProperties | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
openapi: "3.0.0" | ||
info: | ||
version: 1.0.0 | ||
title: Swagger Petstore | ||
description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification | ||
termsOfService: http://swagger.io/terms/ | ||
contact: | ||
name: Swagger API Team | ||
email: [email protected] | ||
url: http://swagger.io | ||
license: | ||
name: Apache 2.0 | ||
url: https://www.apache.org/licenses/LICENSE-2.0.html | ||
servers: | ||
- url: http://petstore.swagger.io/api | ||
paths: | ||
/save_the_pets: | ||
post: | ||
description: Creates a new pet in the store. Duplicates are allowed | ||
operationId: addPet | ||
requestBody: | ||
description: Pet to add to the store | ||
required: true | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/PetBaskets' | ||
responses: | ||
'200': | ||
description: pet response | ||
content: | ||
application/json: | ||
schema: | ||
type: object | ||
components: | ||
schemas: | ||
PetBaskets: | ||
type: object | ||
properties: | ||
baskets: | ||
type: array | ||
items: | ||
anyOf: | ||
- "$ref": "#/components/schemas/SquirrelBasket" | ||
- "$ref": "#/components/schemas/CatBasket" | ||
discriminator: | ||
propertyName: name | ||
mapping: | ||
cats: "#/components/schemas/CatBasket" | ||
squirrels: "#/components/schemas/SquirrelBasket" | ||
SquirrelBasket: | ||
type: object | ||
required: | ||
- name | ||
properties: | ||
name: | ||
type: string | ||
content: | ||
type: array | ||
items: | ||
"$ref": "#/components/schemas/Squirrel" | ||
Squirrel: | ||
type: object | ||
required: | ||
- name | ||
- nut_stock | ||
properties: | ||
name: | ||
type: string | ||
born_at: | ||
format: date-time | ||
nullable: true | ||
type: string | ||
description: | ||
nullable: true | ||
type: string | ||
nut_stock: | ||
nullable: true | ||
type: integer | ||
CatBasket: | ||
type: object | ||
required: | ||
- name | ||
properties: | ||
name: | ||
type: string | ||
content: | ||
type: array | ||
items: | ||
"$ref": "#/components/schemas/Cat" | ||
Cat: | ||
type: object | ||
required: | ||
- name | ||
- milk_stock | ||
properties: | ||
name: | ||
type: string | ||
born_at: | ||
format: date-time | ||
nullable: true | ||
type: string | ||
description: | ||
nullable: true | ||
type: string | ||
milk_stock: | ||
nullable: true | ||
type: integer | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
require_relative '../../spec_helper' | ||
|
||
RSpec.describe OpenAPIParser::Schemas::RequestBody do | ||
let(:root) { OpenAPIParser.parse(petstore_with_discriminator_schema, {}) } | ||
|
||
describe 'discriminator' do | ||
let(:content_type) { 'application/json' } | ||
let(:http_method) { :post } | ||
let(:request_path) { '/save_the_pets' } | ||
let(:request_operation) { root.request_operation(http_method, request_path) } | ||
let(:params) { {} } | ||
|
||
it 'picks correct object based on mapping and succeeds' do | ||
body = { | ||
"baskets" => [ | ||
{ | ||
"name" => "cats", | ||
"content" => [ | ||
{ | ||
"name" => "Mr. Cat", | ||
"born_at" => "2019-05-16T11 =>37 =>02.160Z", | ||
"description" => "Cat gentleman", | ||
"milk_stock" => 10 | ||
} | ||
] | ||
}, | ||
] | ||
} | ||
|
||
request_operation.validate_request_body(content_type, body) | ||
end | ||
|
||
it 'picks correct object based on mapping and fails' do | ||
body = { | ||
"baskets" => [ | ||
{ | ||
"name" => "cats", | ||
"content" => [ | ||
{ | ||
"name" => "Mr. Cat", | ||
"born_at" => "2019-05-16T11 =>37 =>02.160Z", | ||
"description" => "Cat gentleman", | ||
"nut_stock" => 10 # passing squirrel attribute here, but discriminator still picks cats and fails | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perfect!! 🙆 🙆 🙆 🙆 |
||
} | ||
] | ||
}, | ||
] | ||
} | ||
expect { request_operation.validate_request_body(content_type, body) }.to raise_error do |e| | ||
expect(e.kind_of?(OpenAPIParser::NotExistRequiredKey)).to eq true | ||
expect(e.message).to match("^required parameters milk_stock not exist.*?$") | ||
end | ||
end | ||
|
||
it "throws error when discriminator mapping is not found" do | ||
body = { | ||
"baskets" => [ | ||
{ | ||
"name" => "dogs", | ||
"content" => [ | ||
{ | ||
"name" => "Mr. Dog", | ||
"born_at" => "2019-05-16T11 =>37 =>02.160Z", | ||
"description" => "Dog bruiser", | ||
"nut_stock" => 10 # passing squirrel attribute here, but discriminator still picks cats and fails | ||
} | ||
] | ||
}, | ||
] | ||
} | ||
|
||
expect { request_operation.validate_request_body(content_type, body) }.to raise_error do |e| | ||
expect(e.kind_of?(OpenAPIParser::NotExistDiscriminatorMappingTarget)).to eq true | ||
expect(e.message).to match("^discriminator mapping key dogs does not exist.*?$") | ||
end | ||
end | ||
|
||
it "throws error if discriminator propertyName is not present on object" do | ||
body = { | ||
"baskets" => [ | ||
{ | ||
"content" => [ | ||
{ | ||
"name" => "Mr. Dog", | ||
"born_at" => "2019-05-16T11 =>37 =>02.160Z", | ||
"description" => "Dog bruiser", | ||
"milk_stock" => 10 | ||
} | ||
] | ||
}, | ||
] | ||
} | ||
|
||
expect { request_operation.validate_request_body(content_type, body) }.to raise_error do |e| | ||
expect(e.kind_of?(OpenAPIParser::NotExistDiscriminatorPropertyName)).to eq true | ||
expect(e.message).to match("^discriminator propertyName name does not exist in value.*?$") | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ota42y so I am trying to move this to https://github.com/Ladas/openapi_parser/blob/c844e37dba4cbe605f9a3f8d565f84843acfe5bb/lib/openapi_parser/schemas/schema.rb#L115, to my own parser, but after spending several hours there I didn't figure out how it works. :-) Could you give me some hints? Is it even possible to do the lookup there?
If the
discriminator.root.find_object(mapping_target)
is or would be O(1), I don't really need to do that. :-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like the find_object does O(n) lookup at worst, then caches the result, so this should be ok performance wise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes,
find_object
method dose O(n) lookup at worst, but we cached result so I think there is no problem 👍https://github.com/ota42y/openapi_parser/blob/master/lib/openapi_parser/concerns/findable.rb#L4