This question relates to Open API spec 3 and writing API specifications.
I have been trying to work out how to set fields as required for a specific method requestBody when I am using a "oneOf" in a schema object.
I know how to set required fields of a referenced object, but when you try set the required properties of oneOf objects the same way, it does not work.
I also know that I can set the required fields in the schema object itself, however I do not want to do this as the required fields differ on each method (i.e all fields required for a post, but only some required for a patch).
So essentially, how do I set the required fields in a request body of a schema used in oneOf?
I have created an example below, in it, I want to:
Set the fields Bird - wingSpan and beakLength as required in the Post method
Set the fields Cat - tailLength as required in the Post method
Not have any required fields for Bird and Cat in the Patch method.
openapi: 3.0.3
servers:
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/Enable-Networks/TroubleTicket/1.0.0
info:
title: Pet API
version: 1.0.0
paths:
/pet:
patch:
summary: Update a pet
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Pet"
- type: object
required:
- age
responses:
"204":
description: The request was a success and the pet was successfully created.
post:
summary: Create a pet
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Pet"
- type: object
required:
- name
- age
- species
properties:
species:
type: object
required:
- ???
responses:
"204":
description: The request was a success and the pet was successfully created.
components:
schemas:
Cat:
type: object
properties:
tailLength:
type: integer
whiskerLength:
type: integer
Bird:
type: object
properties:
wingSpan:
type: integer
beakLength:
type: integer
highestAltitude:
type: integer
Pet:
type: object
properties:
name:
type: string
age:
type: string
species:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Bird'
Any help on this would be appreciated!
I have read the documentation 10x, tried everything from discriminators to not using refs.
I have tried many different combinations of the below and still nothing works.
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Pet"
- type: object
required:
- name
- age
- species
properties:
species:
type: object
required:
- ???
Is it possible to assign a customised name to a Components object in OpenAPI?
I currently have two Component objects that specify a request schema:
Request1:
type: object
description: Request 1
properties:
a:
description: Filter 1 a
$ref: '#/definitions/Filter1a'
b:
description: Filter 1 b.
$ref: '#/definitions/Filter1b'
Request2:
type: object
description: Request 2
properties:
query:
type: object
description: Filter 2.
properties:
bool:
type: object
properties:
must:
type: array
items:
type: object
properties:
match_all:
type: object
In the endpoint description I refer to these schemas as follows:
/v2/myEndpoint:
post:
tags:
- some tags
operationId: someId
summary: Some summary
description: Some description
produces:
- application/json
parameters:
- in: body
name: body
required: false
schema:
oneOf:
- $ref: '#/definitions/Request1'
- $ref: '#/definitions/Request2'
When I publish the yaml file, the UI shows Request1 and Request2 in selection tabs, which carry the names 'Request1' and 'Request2'. Is it possible to assign custom names to them, so that the UI will show the custom names instead? For example 'Custom name request 1' and 'Custom name request 2'?
Many thanks!
I assume you meant Swagger UI.
title would do that. Link to Open Api Spec.
Request1:
title: Custom name request 1
type:...
Request2:
title: Custom name request 2
type:...
I have the following swagger.yaml that uses the oneOf keyword.
openapi: 3.0.0
info:
version: 0.0.1
title: Polymorphism example
description: ''
paths:
/animal:
post:
summary: Create a new animal
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Animal'
responses:
'201':
description: Created
components:
schemas:
Animal:
type: object
properties:
name:
type: string
props:
oneOf:
- $ref: '#/components/schemas/Fish'
- $ref: '#/components/schemas/Dog'
Fish:
type: object
properties:
depth:
type: integer
Dog:
type: object
properties:
country:
type: string
If used the latest Swagger codegen tools (3.0.22) to create a server stub for JAX-RS Jersey.
I am trying to post the following example object:
{
"name":"Foo",
"props":{
"country":"Bar"
}
}
But this results in the following exception:
javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `io.swagger.model.OneOfAnimalProps` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 30] (through reference chain: io.swagger.model.Animal["props"])
at org.glassfish.jersey.servlet.WebComponent.serviceImpl (WebComponent.java:432)
at org.glassfish.jersey.servlet.WebComponent.service (WebComponent.java:370)
at org.glassfish.jersey.servlet.ServletContainer.service (ServletContainer.java:389)
at org.glassfish.jersey.servlet.ServletContainer.service (ServletContainer.java:342)
at org.glassfish.jersey.servlet.ServletContainer.service (ServletContainer.java:229)
at org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:808)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter (ServletHandler.java:1669)
at io.swagger.api.ApiOriginFilter.doFilter (ApiOriginFilter.java:15)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter (ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle (SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle (SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope (SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle (ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerCollection.handle (HandlerCollection.java:110)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle (Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run (AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run (QueuedThreadPool.java:555)
at java.lang.Thread.run (Thread.java:748)
Does this error relate to a bug in the JAX-RS codegen part?
Is there any way to fix this on server stub side?
I'm trying to write my first Asyncapi documentation file. I'd like to perform reusability of my schema between a reusableModel and others. However, the html documentation preview gives me an array with separate objects instead of one unified object. Here is my problematic yaml content:
asyncapi: 2.0.0
info:
title: woaw
version: 0.1.0
description: >
blabla
license:
name: UNLICENSED
defaultContentType: application/json
channels:
myChannel:
subscribe:
message:
oneOf:
- $ref: '#/components/messages/new'
- $ref: '#/components/messages/deleted'
components:
messages:
new:
payload:
$ref: '#/components/schemas/new'
deleted:
payload:
$ref: '#/components/schemas/deleted'
schemas:
reusableModel:
type: object
properties:
id:
type: string
format: uuid
example: 37a2005e-70e6-4cf3-b7e3-19e087879e50
new:
allOf:
- $ref: '#/components/schemas/reusableModel'
- type: object
properties:
type:
type: string
enum: ["extension.new"]
data:
type: object
properties:
key1:
type: object
properties:
users:
type: array
items:
type: string
deleted:
allOf:
- $ref: '#/components/schemas/reusableModel'
- type: object
properties:
type:
type: string
enum: ["extension.deleted"]
data:
type: object
properties:
key1:
type: string
You might copy/paste it to https://playground/asyncapi.io to see the rendering problem. The allOf object defined in the payload of the first messageType (new) appears as an array with in part 0 the reusableModel and in part 1 the rest of my properties (type + data) and not as unified object. The documentation involves the following:
The AsyncAPI Specification allows combining and extending model definitions using the allOf property of JSON Schema, in effect offering model composition. allOf takes in an array of object definitions that are validated independently but together compose a single object.
I think I misunderstand some part of the documentation, can you explain to me ?
As I've commented, it looks like a known issue:
https://github.com/asyncapi/html-template/issues/11
I am learning OpenAPI recently, and would like to know the best practices.
Let's say I have a resource called Person, and it is defined in components/schemas as follows:
Person:
type: object
required:
- id
- name
- age
properties:
id:
readOnly: true
type: integer
name:
type: string
age:
type: integer
I've made id readOnly because when I do post or patch, the ID will be passed as part of the URL. See https://swagger.io/docs/specification/data-models/data-types/
name and age must present when the client tries to create a new person using post method, or get a person, therefore they are defined as required.
My question is about patch: what if I only want to update a person's age or name independently? Ideally I would like to do something like
PATCH /person/1
{"age": 40}
However, since I've defined name as required, I can't do it. I can think of several solutions, but they all have flaws:
Remove the required restriction. But if I do that, I lose the validation on post and get.
Use a separate schema for patch, e.g. PersonUpdate, with required removed. Apparently that leads to redundancy.
When I do patch, I do pass all the fields, but for the ones I don't want to update, I pass an invalid value, e.g.
PATCH /Person/1
{"age": 40, "name": null}
And make all the fields nullable, and let the server ignore these values. But what if I do want to set name to null in DB?
I use PUT for update, and always pass all the required fields. But what if my data is outdated? E.g. when I do
PUT /Person/1
{"age": 40, "name": "Old Name"}
Some other client has already changed name to "New Name", and I am overriding it.
Like method 3, but I pass additional fields when doing patch to indicate the fields the server should care, whether using query parameters like ?fields=age, or add it to the JSON body. So I can change the requestBody to something like
requestBody:
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Person'
- type: object
properties:
_fields:
type: array
items:
type: string
Then I can do this
PATCH /Person/1
{"age": 40, "name": null, _fields: ["age"]}
In this way, I can update name to null as well
PATCH /Person/1
{"age": 40, "name": null, _fields: ["age", "name"]}
This method seems can work, but is there a better or widely accepted practice?
I came up with the following solution:
Person:
type: object
allOf:
- $ref: '#/components/schemas/PersonProperties'
- required:
- id
- name
- age
UpdatePerson:
type: object
allOf:
- $ref: '#/components/schemas/PersonProperties'
PersonProperties:
type: object
properties:
id:
readOnly: true
type: integer
name:
type: string
age:
type: integer
PersonProperties acts as a simple collection of properties which make up a the person model. It doesn't specify any required fields.
Person re-uses all properties of PersonProperties and, in addition, specifies which one are required. PersonUpdate re-uses all properties of PersonProperties and allows for partial updates.
This solution still feels hacky. Also, it doesn't work for partial updates of nested objects and nullable properties.
(In fact, since the id is readOnly, you could also add required: [id] on PersonProperties and remove id from the required list on Person)
By splitting the schemas up into multiple, composable schemas, we can layer the schemas on top of one another without repeating ourselves. We can even support removal of properties via PATCH by making optional properties nullable.
Example OpenAPI definition allowing partial updates via PATCH endpoints (validated):
openapi: 3.0.0
info:
title: Sample API with reusable schemas and partial updates (PATCH)
version: 1.0.0
paths:
/customers:
post:
tags:
- Customer
requestBody:
$ref: '#/components/requestBodies/CreateCustomer'
responses:
201:
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerId'
get:
tags:
- Customer
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Customer'
/customers/{CustomerId}:
get:
tags:
- Customer
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
put:
tags:
- Customer
requestBody:
$ref: '#/components/requestBodies/CreateCustomer'
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
204:
description: Updated
patch:
tags:
- Customer
requestBody:
description: Update customer with properties to be changed
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CustomerProperties'
- type: object
properties:
Segment:
nullable: true
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
204:
description: Updated
components:
schemas:
CustomerProperties:
type: object
properties:
FirstName:
type: string
LastName:
type: string
DOB:
type: string
format: date-time
Segment:
type: string
enum:
- Young
- MiddleAged
- Old
- Moribund
CustomerRequiredProperties:
type: object
required:
- FirstName
- LastName
- DOB
Id:
type: integer
CustomerId:
type: object
properties:
Id:
$ref: '#/components/schemas/Id'
Customer:
allOf:
- $ref: '#/components/schemas/CustomerId'
- $ref: '#/components/schemas/CustomerProperties'
- $ref: '#/components/schemas/CustomerRequiredProperties'
parameters:
CustomerId:
name: CustomerId
in: path
required: true
schema:
$ref: '#/components/schemas/Id'
requestBodies:
CreateCustomer:
description: Create a new customer
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CustomerProperties'
- $ref: '#/components/schemas/CustomerRequiredProperties'
Adapted from: https://stoplight.io/blog/reuse-openapi-descriptions/
It's weird to talk about the OpenAPI / Json schema structure because it tells you something about what you are sending, but not about the meaning of the properties.
To do what you want, you need to come up with a format with a set of rules that can make these kind of modifications.
One (more complex) example is JSON Patch.
Once you figured out what your rules around patching JSON is going to be, that's when a good time might be to define specific cases for this (like the case for Person).