Why are the OpenAPI 3 oneOf example objects not always valid against both schemas - openapi

The specification for the oneOf keyword in https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#oneof has an example schema as well as three example objects. The request body is oneOf the Dog or Cat schema:
Dog:
type: object
properties:
bark:
type: boolean
breed:
type: string
enum: [Dingo, Husky, Retriever, Shepherd]
Cat:
type: object
properties:
hunts:
type: boolean
age:
type: integer
And the three JSON objects:
Object 1
{
"bark": true,
"breed": "Dingo"
}
Object 2
{
"bark": true,
"hunts": true
}
Object 3
{
"bark": true,
"hunts": true,
"breed": "Husky",
"age": 3
}
They say, the first object is valid against one of the schemas (I assume they mean the Dog schema), the second one is not valid against both schemas and the third is valid against both schemas. This means the second and third object are not correct request bodies.
But why are not all three objects valid against both schemas? None of the keywords for objects mentioned here http://json-schema.org/draft/2020-12/json-schema-validation.html#name-validation-keywords-for-obj are used. And the types of the properties match for all the objects.
I played with this validator https://www.jsonschemavalidator.net/ and wrote also a simple schema with an object with some properties and even an empty json validates against the schema.

It seems that my assumptions are correct and all objects are valid against the oneOf schema. I found an issue reporting this: https://github.com/swagger-api/swagger.io/issues/253.

Related

How to specify an unknown number of a parameter in the response Open API

I'm trying to model my API using swagger and the Open API 3.0 specification. I have made some schemas and now I'm modeling the response of my endpoints. The problem is that they return something like this:
[
{
"name": "this attribute is always here"
"type1": { "description": "this maybe appear or not" },
"type2": { "description": "this maybe appear or not" },
...
"typeN": { "description": "N is not a fixed number, it may range from 0 to another positive integer" },
}
]
I know how to model the array and the object (with the name property). The problem comes when I have to model the typeX properties, I do not know how to specify that they are optional and the number of ocurrences is variable. Any idea?
This object is basically a string-to-object dictionary/hashmap with an extra name property. Fixed properties are defined in properties, and the dictionary part can be defined using either patternProperties (in OpenAPI 3.1) or additionalProperties (in OpenAPI 3.0 and 2.0).
OpenAPI 3.1
In OAS 3.1 your object can be defined as follows. Since the optional property names all follow the typeX format, the schema uses patternProperties to define the regex for the property names.
MyObject:
type: object
required: [name]
properties:
name:
type: string
patternProperties: # <-- This part defines the "typeX" properties
^type\d+$: # <-- Property name regex
type: object # <-- Property value
properties:
description:
type: string
additionalProperties: false # No other properties other than "name" and "typeX"
OpenAPI 3.0 and 2.0
In earlier OAS versions, you use additionalProperties to define "may have extra properties with <such> values", but there's no way to define the format of those property names. You can however mention the property name format in the schema description and also add a schema example for documentation purposes.
MyObject:
type: object
description: >-
In addition to the `name` property, this object may have an arbitrary
number of properties named `typeX` where X is a positive integer.
required: [name]
properties:
name:
type: string
additionalProperties:
# This part defines the *value* of the typeX properties
type: object
properties:
description:
type: string
# Optional schema example
name: something
type1:
description: ....
type2:
description: ....

Define Kubernetes Custom Resource requiring one of the following fields or no fields

I try to define a new Custom Resource, requiring it to have one of (1) field A, (2) field B, or (3) empty body.
For example: {A: 1}, {B: 1}, {} are OK, but {A:1, B:2} is not.
Here is the definition of my Custom Resource in form of OpenApi schema:
foo:
type: object
properties:
a:
type: int
b:
type: int
oneOf:
- required: ["a"]
- required: ["b"]
# no sure how to include the empty body
How should I include the empty body in the oneOf constraint?
OpenAPI 3.1
You can use 'null' (with quotes).
oneOf:
- type: 'null'
Or an object with a property of type null.
oneOf:
- NullObjectExample:
type: object
properties:
prop1:
type: 'null'
OpenAPI 3.0
There is no null type, but you can use a nullable string. You may want to add a description stating this is expected to be null at all times.
oneOf:
- type: string
nullable: true
Or again, an object with a nullable string property.
oneOf:
- NullObjectExample:
type: object
properties:
prop1:
type: string
nullable: true
Why No Empty Object
In addition to not making intent clear, this presents a possible security vulnerability. See this page for an explanation. In short:
If you do not clearly define the schema and you leave properties of a JSON payload empty, you effectively allow attackers to pass in any data. This means that you are opening your backend to various attacks, such as SQL injection.

Freeform subobject in json-schema

I am drafting an API documentation with swagger.io and is trying to make it fit to our use case. The system is going to receive and process data from all sources, and they would each have different sets of fields.
While the product of the processing share the same schema, we want to include the input in the schema too for reference purpose. For instance, given
{
"foo": "bar"
"bar": "baz"
}
The product of the processing is
{
"original": {
"foo": "bar",
"bar": "baz"
}
"processed": {
"stdFieldA": "bar",
"stdFieldB": "baz"
}
}
Assuming for each input from different sources, we end up having stdFieldA and stdFieldB. So the response schema object we have
type: object
properties:
processed:
type: object
properties:
stdFieldA:
type: string
stdFieldB:
type: string
now that we have the processed subobject defined, can we define a freeform object for the original input, so that this object coming from another source is valid
{
"alpha": "lorem",
"beta": "ipsum"
}
If I don't get any answer to this, my workaround to the problem would be storing the original input as string (convert the original input into JSON string).
type: object without properties describes a free-form object. So the response schema can be:
type: object
properties:
original:
type: object # <----------
processed:
type: object
properties:
stdFieldA:
type: string
stdFieldB:
type: string

Deserialise JSON to polymorphic types based on a type field

I am using lift-json 2.6 and Scala 2.11.
I want to deserialise a JSON string representing a Map of 'sensors' to case classes (I don't care about serialisation back to JSON at all):
case class TemperatureSensor(
name: String, sensorType: String, state: TemperatureState)
case class TemperatureState(
on: Boolean, temperature: Float)
case class LightSensor(
name: String, sensorType: String, state: LightState)
case class LightState(
on: Boolean, daylight: Boolean)
What I have here are some common fields in each sensor class, with a type-dependant state field, discriminated by the sensorType property
The idea is I invoke a web service and get a map of sensor information back, this can be any number of any type of different sensors. I know the set of possible types in advance, but I do not know in advance which particular sensors will be returned.
The JSON looks like this:
{
"1":
{
name: "Temp1",
sensorType: "Temperature",
state:
{
on: true,
temperature: 19.4
}
},
"2":
{
name: "Day",
sensorType: "Daylight",
state:
{
on: true,
daylight: false
}
}
}
(The real data has many more fields, the above case classes and JSON is a cut-down version.)
To consume the JSON I start with:
val map = parse(jsonString).extract[Map[String,Sensor]]
This works when I omit the state fields of course.
How can the extraction process be told which type of state to choose at run-time, based on the value of the sensorType field? Or do I have to write a custom deserialiser?
This question relates specifically to lift-json, not any other JSON library.
Unfortunately, I have not used lift-json... But I recently tackled the same problem using play-json. Perhaps some of what I have done could be useful to you as well.
See my github page for code: DiscriminatedCombinators.scala

MongoDB Object data type won't save with defined schema

Using Mongo and Meteor with CoffeeScript, I'm trying to save a document with one Object:
Test = new SimpleSchema(
tag:
type: Object
)
And the insert:
test1 = new Meteor.Collection("test", { schema: Test})
test1.insert({ tag: {"name": "campus"} })
Result: a document gets saved in the database but the "tag" field is never set.
Couple of different troubleshooting steps I've taken:
Changing the data type to String works and the "tag" field gets set. However, I want to reference a tag property without having to parse the string every time.
Adding a collection without the schema saves the Object exactly how I want:
test2 = new Meteor.Collection("test2")
test2.insert({ tag: {"name": "campus"} })
EDIT: Fixed using the blackbox: true flag. See below answer for clarification.
Test = new SimpleSchema(
tag:
type: Object
blackbox: true
)
According to SimpleSchema docs, all defined properties must pass validation. So any Object data type without properties is treated as an empty Object unless you add the blackbox: true flag.
Source: http://atmospherejs.com/aldeed/simple-schema#blackbox
If you have a key with type Object, the properties of the object will be validated as well, so you must define all allowed properties in the schema. If this is not possible or you don't care to validate the object's properties, use the blackbox: true option to skip validation for everything within the object.
I use simple schema and create my models of the following way, and I don't have any problem.
Test = new Meteor.Collection("test", {
schema: new SimpleSchema({
ownerId: {
type: String,
},
dateAdd: {
type: Date,
}
})
})
Test.insert({ownerId:"123",dateAdd:"..."})
In Coffee Script
Test = new Meteor.Collection("test",
schema: new SimpleSchema(
ownerId:
type: String
dateAdd:
type: Date
)
)