openapi_first
openapi_first copied to clipboard
Cannot validate responses that use `oneOf` unions with `discriminator`.
Ran into an issue with oneOf
unions, but only when using a discriminator
property. I am not sure if this is an openapi_first
issue or a json_schemer
issue, so I'd though I'd try here first.
Given a simple YAML configuration like
---
openapi: 3.1.0
info:
title: API V1
version: v1
paths:
"/pets":
get:
summary: Pets
responses:
'200':
description: successful
content:
application/json:
schema:
items:
oneOf:
- '$ref': '#/components/schemas/Cat'
- '$ref': '#/components/schemas/Dog'
discriminator:
propertyName: petType
type: array
components:
schemas:
Cat:
type: object
properties:
id:
type: integer
petType:
type: string
enum:
- cat
meow:
type: string
Dog:
type: object
properties:
id:
type: integer
petType:
type: string
enum:
- dog
bark:
type: string
and using the ResponseValidation
middleware
config.middleware.use OpenapiFirst::Middlewares::ResponseValidation, spec: 'swagger/v1/swagger.yaml', raise_error: true
and responding with some simple hardcoded data
class PetsController < ApplicationController
def index
# Hardcode a response
render json: [
{ id: 1, petType: 'cat', meow: 'Prrr' },
{ id: 2, petType: 'dog', bark: 'Woof' }
], status: :ok
end
end
The following error occurs
{
"status": 500,
"error": "Internal Server Error",
"exception": "#<KeyError: key not found: \"$ref\">",
"traces": {
"Application Trace": [],
"Framework Trace": [
{
"exception_object_id": 9400,
"id": 0,
"trace": "json_schemer (2.3.0) lib/json_schemer/openapi31/vocab/base.rb:59:in `fetch'"
},
{
"exception_object_id": 9400,
"id": 1,
"trace": "json_schemer (2.3.0) lib/json_schemer/openapi31/vocab/base.rb:59:in `block in subschemas_by_property_value'"
},
{
"exception_object_id": 9400,
"id": 2,
"trace": "json_schemer (2.3.0) lib/json_schemer/openapi31/vocab/base.rb:58:in `each'"
},
{
"exception_object_id": 9400,
"id": 3,
"trace": "json_schemer (2.3.0) lib/json_schemer/openapi31/vocab/base.rb:58:in `subschemas_by_property_value'"
},
{
"exception_object_id": 9400,
"id": 4,
"trace": "json_schemer (2.3.0) lib/json_schemer/openapi31/vocab/base.rb:110:in `validate'"
},
{
"exception_object_id": 9400,
"id": 5,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:141:in `block in validate_instance'"
},
{
"exception_object_id": 9400,
"id": 6,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:140:in `each'"
},
{
"exception_object_id": 9400,
"id": 7,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:140:in `validate_instance'"
},
{
"exception_object_id": 9400,
"id": 8,
"trace": "json_schemer (2.3.0) lib/json_schemer/draft202012/vocab/applicator.rb:184:in `block in validate'"
},
{
"exception_object_id": 9400,
"id": 9,
"trace": "json_schemer (2.3.0) lib/json_schemer/draft202012/vocab/applicator.rb:183:in `map'"
},
{
"exception_object_id": 9400,
"id": 10,
"trace": "json_schemer (2.3.0) lib/json_schemer/draft202012/vocab/applicator.rb:183:in `with_index'"
},
{
"exception_object_id": 9400,
"id": 11,
"trace": "json_schemer (2.3.0) lib/json_schemer/draft202012/vocab/applicator.rb:183:in `validate'"
},
{
"exception_object_id": 9400,
"id": 12,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:141:in `block in validate_instance'"
},
{
"exception_object_id": 9400,
"id": 13,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:140:in `each'"
},
{
"exception_object_id": 9400,
"id": 14,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:140:in `validate_instance'"
},
{
"exception_object_id": 9400,
"id": 15,
"trace": "json_schemer (2.3.0) lib/json_schemer/schema.rb:106:in `validate'"
},
{
"exception_object_id": 9400,
"id": 16,
"trace": "openapi_first (2.1.0) lib/openapi_first/schema.rb:29:in `validate'"
},
{
"exception_object_id": 9400,
"id": 17,
"trace": "openapi_first (2.1.0) lib/openapi_first/validators/response_body.rb:25:in `call'"
},
{
"exception_object_id": 9400,
"id": 18,
"trace": "openapi_first (2.1.0) lib/openapi_first/response_validator.rb:22:in `block (2 levels) in call'"
},
{
"exception_object_id": 9400,
"id": 19,
"trace": "openapi_first (2.1.0) lib/openapi_first/response_validator.rb:22:in `each'"
},
{
"exception_object_id": 9400,
"id": 20,
"trace": "openapi_first (2.1.0) lib/openapi_first/response_validator.rb:22:in `block in call'"
},
{
"exception_object_id": 9400,
"id": 21,
"trace": "openapi_first (2.1.0) lib/openapi_first/response_validator.rb:21:in `catch'"
},
{
"exception_object_id": 9400,
"id": 22,
"trace": "openapi_first (2.1.0) lib/openapi_first/response_validator.rb:21:in `call'"
},
{
"exception_object_id": 9400,
"id": 23,
"trace": "openapi_first (2.1.0) lib/openapi_first/response.rb:29:in `validate'"
},
{
"exception_object_id": 9400,
"id": 24,
"trace": "openapi_first (2.1.0) lib/openapi_first/definition.rb:79:in `validate_response'"
},
{
"exception_object_id": 9400,
"id": 25,
"trace": "openapi_first (2.1.0) lib/openapi_first/middlewares/response_validation.rb:28:in `call'"
},
{
"exception_object_id": 9400,
"id": 26,
"trace": "openapi_first (2.1.0) lib/openapi_first/middlewares/request_validation.rb:36:in `call'"
},
# Snipped for brevity
]
}
}
If I remove the discriminator
property and just have
Pet:
oneOf:
- '$ref': '#/components/schemas/Cat'
- '$ref': '#/components/schemas/Dog'
Everything works.
[
{
"id": 1,
"petType": "cat",
"meow": "Prrr"
},
{
"id": 2,
"petType": "dog",
"bark": "Woof"
}
]
I dug into the openapi_first
and json_schemer
source a bit and what I think is happening is that json_schemer
expects those $ref
s to be there, but openapi_first
dereferences them out when creating the schemas. I can see a response schema being created with the spec doc loaded as
{"items"=>
{"oneOf"=>
[{"type"=>"object", "properties"=>{"id"=>{"type"=>"integer"}, "petType"=>{"type"=>"string", "enum"=>["cat"]}, "meow"=>{"type"=>"string"}}},
{"type"=>"object", "properties"=>{"id"=>{"type"=>"integer"}, "petType"=>{"type"=>"string", "enum"=>["dog"]}, "bark"=>{"type"=>"string"}}}],
"discriminator"=>{"propertyName"=>"petType"}},
"type"=>"array"}
The presence of the discriminator
seems to trigger json_schemer
to look for the $refs
. The tests seem to indicate that json_schemer
supports oneOf
unions, but they suggest that it needs to be aware of the entire schema (or at least the ref
s), where as openapi_first
takes a modular approach and creates separate request and response schemas for each path.
Again, don't know if this is an openapi_first
problem or a json_schemer
problem. It does seem like json_schemer
expects the entire specification to be given to it, which is not how openapi_first
is currently using it. That being said, this is the only issue I have run into thus far in a project that is making heavy use of $ref
s, so perhaps it's a json_schemer
bug with discriminator
s because that shouldn't assume that $ref
s are being used.
Perhaps instead of dereferencing, a ref map is created that can then be provided to json_schemer
schema = {
'$id' => 'http://example.com/schema',
'allOf' => [
{ '$ref' => 'schema/one' },
{ '$ref' => 'schema/two' }
]
}
refs = {
URI('http://example.com/schema/one') => {
'type' => 'integer'
},
URI('http://example.com/schema/two') => {
'minimum' => 11
}
}
schemer = JSONSchemer.schema(schema, :ref_resolver => refs.to_proc)