I'm using an OpenApi generator setup on Maven + Spring Boot (2.7.7) to generate API interfaces to implement in my software. This includes Validation.
I'm trying to understand if there is a way to specify validation in an OpenApi (3.0.1 atm but I'm flexible) yaml in such a way that I can have an object not be mandatory, but if ANY fields are included, then all of it must be included.
For example:
paths:
/api/complexobject:
get:
tags:
- complexobject
summary: generic search
operationId: findAll
parameters:
- in: query
name: complexobject
schema:
$ref: '#/components/schemas/ComplexObject'
explode: true
- in: query
name: sort
schema:
$ref: '#/components/schemas/Sorting'
- in: query
name: page
schema:
$ref: '#/components/schemas/Pagination'
responses:
"200":
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ComplexObject'
components:
schemas:
ComplexObject:
type: object
<cut>
Pagination:
type: object
properties:
page:
type: integer
minimum: 0
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
description: Number of returned records (page size). Suggested default is 20
required:
- page
- size
Sorting:
type: object
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
enum: ["asc", "desc"]
The intention here is that the Sorting and the Pagination objects can be omitted from the query, however IF they're included, they must be wholly included (and with valid inputs too).
However it seems that at runtime the Pagination and Sorting Java Objects are instanced even without any parameters being sent to the Controller, which makes them fail the validation.
Is there some different validation setup I can use in the yaml, or do I need to change something in Java code?
Or, just remove the requirements altogether from the OpenAPI field declation and do programmatic validation directly instead?
As far as I can tell the specification I wrote is correct, however the generator (the most recent one available at the time of writing) doesn't support this kind of behaviour.
However what it does support is the use of defaults:
Pagination:
type: object
nullable: true
properties:
page:
type: integer
minimum: 0
default: 0 # Added default
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
default: 20 # Added default
description: Number of returned records (page size).
required:
- page
- size
Sorting:
type: object
nullable: true
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array # No default needed, see below!
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
default: "asc" # Added default
enum: ["asc", "desc"]
In this case you'll actually be bypassing the problem a bit, in that you'll never end up in a situation when any of the required fields will ever be null, see the generated code for reference:
#Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-01-30T16:25:58.375+01:00[Europe/Berlin]")
public class Pagination {
#JsonProperty("page")
private Integer page = 0;
#JsonProperty("size")
private Integer size = 20;
As a side note, the validation for the "sorted" property actually works as expected, even though the way the codegen attains that is weird. It'll work the same way whether you specify it as required or not AND without specifying defaults.
However we have the two following codegens:
Without required:
#JsonProperty("sorted")
#Valid
private List<String> sorted = null;
// Cut for brevity
/**
* Get sorted
* #return sorted
*/
#NotNull
#Schema(name = "sorted", required = false)
public List<String> getSorted() {
return sorted;
}
WITH required:
#JsonProperty("sorted")
#Valid
private List<String> sorted = new ArrayList<>();
// Cut for brevity
/**
* Get sorted
* #return sorted
*/
#NotNull
#Schema(name = "sorted", required = true)
public List<String> getSorted() {
return sorted;
}
This also means that the validation in the second case WILL be active... but never fail.
Related
I need to document a REST endpoint that takes the following request body:
{
"variables": [
{
"name": "groupGuid",
"value": "...",
"typeConstraint": "string",
},
{
"name": "addMembership",
"value": "...",
"typeConstraint": "boolean",
}
]
}
The variables array must take exactly 2 objects:
one with the required name groupGuid and typeConstraint string,
and the other with the required name addMembership and typeConstraint boolean.
The type of their respective values are specified by typeConstraint, but the actual values of the value properties are otherwise unconstrained.
Currently, I've got this, which is very underspecified (and possibly wrong) and relies on notes I've manually included:
'/test':
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
variables:
type: array
uniqueItems: true
description: 'Must contain exactly 2 objects, with the following `name`s: (1) `groupGuid`: GUID of the group, (2) `addMembership`: Whether the task will add (true) or remove (false) members from the group.'
items:
type: object
additionalProperties: false
properties:
name:
enum:
- groupGuid
- addMembership
type: string
value:
type:
- string
- boolean
typeConstraint:
type: string
enum:
- string
- boolean
description: The type of `value`.
Is it possible to properly spec these requirements in YAML / OpenAPI 3.1.0, and if so, how? Thanks.
If what you want is to show both objects in the example body and accept either or both of them, perhaps you could use anyOf.
Below an example
paths:
/test:
post:
tags:
- Test
description: "Must contain exactly 2 objects, with the following `name`s: (1) `groupGuid`: GUID of the group, (2) `addMembership`: Whether the task will add (true) or remove (false) members from the group."
requestBody:
required: true
content:
application/json:
schema:
properties:
variables:
type: array
uniqueItems: true
items:
anyOf:
- $ref: '#/components/schemas/Object1'
- $ref: '#/components/schemas/Object2'
responses:
'200':
description: Sucess
components:
schemas:
Object1:
type: object
description: "The value here is string"
properties:
name:
type: string
value:
type: string
typeConstraint:
type: string
Object2:
type: object
description: "The value here is boolean"
properties:
name:
type: string
value:
type: string
typeConstraint:
type: boolean
openapi minimum/maximum
put:
summary: add
operationId: add
requestBody:
description: value
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Value'
components:
schemas:
Value:
type: object
required:
- value
properties:
value:
type: integer
format: int64
minimum: 1
maximum: 999
generates
#Min(1L) #Max(999L)
public Long getValue() {
return value;
}
Which is not working properly. I try
mockMvc.perform(
put(RESOURCE_URL + "/1/add")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"value\":0}"))
I've no idea is it API problem or spring #Min(1L) #Max(999L) validator problem?
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
I have following service in swagger.yml. The service is written so that page_id can be passed multiple times. e.g /pages?page_id[]=123&page_id[]=542
I checked this link https://swagger.io/specification/ but couldnt understand how could i update yml so i could pass id multiple times.
I see that i have to set collectionFormat but dont know how.
I tried updating it like below but no luck https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md.
it generates url like 'http://localhost:0000/pages?page_id=123%2C%20542`
'/pages':
get:
tags:
-
summary: get the list of pages
operationId: getPages
produces:
- application/json
parameters:
- name: page_id
in: query
description: some description
required: false
type: string
collectionFormat: multi
- name: page_detail
in: query
description: some description
required: false
type: string
responses:
'200':
description: OK
'401':
description: Authentication Failed
'404':
description: Not Found
'503':
description: Service Not Available
You are almost there. Name the parameter page_id[], make it type: array and use collectionFormat: multi:
parameters:
- name: page_id[]
in: query
description: some description
required: false
type: array
items:
type: string # or type: integer or whatever the type is
collectionFormat: multi
Note that the requests will be sent with the [ and ] characters percent-encoded as %5B and %5D, because they are reserved characters according to RFC 3986.
http://example.com/pages?page_id%5B%5D=123&page_id%5B%5D=456
From the docs:
parameters:
- name: id
in: path
description: ID of pet to use
required: true
schema:
type: array
style: simple
items:
type: string
You have to define the parameter as array.
How to add default values to the query parameter of type array:
parameters:
- name: 'liabilityType[]'
in: query
description: liabilityType filters the servicers list according to liability types.
required: false
schema:
type: array
items:
type: string
collectionFormat: multi
value:
- CAR
- HOUSE
I have attached a picture of how this code would look like in Swagger UI
[1]: https://i.stack.imgur.com/MSSaJ.png
I am currently learning how to document using Swagger because my company is evaluating using it as a standard way of documenting for upcoming projects.
I read online that using YAML is easier to read than using JSON, and since YAML is a subset of JSON I figured it would be alright.
I'm working on the response for the 200 code, I would like to represent something similar to the following structure:
responses:
200:
description: OK.
schema:
title: response
type: object
items:
properties:
title: user
type: array
items:
id:
type: string
name:
type: string
status:
type: integer
Basically I return an object called "response" that contains two variables: An array called "user" that contains several strings (I included just two for the sake of clarity) and another variable (outside of the "user" array) called "status" that contains an integer.
The above code doesn't work, and the editor notifies me that it isn't a "valid response definition".
I'm not sure how to tackle this. I'd appreciate some help on what I'm doing wrong.
Basically I return an object called "response" that contains two variables: An array called "user" that contains several strings (I included just two for the sake of clarity) and another variable (outside of the "user" array) called "status" that contains an integer.
Based on your description, the response is supposed to be as follows (assuming the response is JSON). Basically, you have an object with a nested object:
{
"user": {
"id": "12345",
"name": "Alice"
},
"status": 0
}
This response can be defined as follows:
responses:
200:
description: OK.
schema:
title: response
type: object
required: [user, status]
properties:
user:
type: object
required: [id, name]
properties:
id:
type: string
name:
type: string
status:
type: integer
For convenience, complex schemas with nested objects can be broken down into individual object schemas. Schemas can be written in the global definitions section and referenced from other places via $ref. This way, for example, you can reuse the same schema in multiple operations/responses.
responses:
200:
description: OK.
schema:
$ref: "#/definitions/ResponseModel"
definitions:
ResponseModel:
title: response
type: object
properties:
user:
$ref: "#/definitions/User"
status:
type: integer
required:
- user
- status
User:
type: object
properties:
id:
type: string
name:
type: string
required:
- id
- name
I am trying to create a Response Object that looks like this:
(simplified for example)
{
"data1": "1234"
"data2" : "445"
}
I have 2 definitions:
obj1:
type: object
properties:
data1:
type: string
obj2:
type: object
properties:
data2:
type: string
Then a 3rd definition that looks like this:
Main:
type: object
allOf:
- $ref: "#/definitions/ob1"
- $ref: "#/definitions/obj2"
I'm not sure if this is the correct way to merge both Obj1 and Obj 2 at the base of the Main object
The Swagger UI show the following
Responses
Code Description Schema
200 Success ⇄
Main {
all of:
obj1 { }
obj2 { }
}
What I'm unclear about is if it will set these objects at the root or if its stating 2 objects will be in the response???
I'm assuming its correct, hoping someone can confirm.
Swagger 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.
According to this - Swagger UI shows two objects which in fact be composed into one object with combined properties. You can try it by yourself:
StringObj:
type: object
properties:
stringId:
type: string
IntegerObj:
type: object
properties:
integerId:
type: integer
Composed:
description: A representation of a dog
allOf:
- $ref: '#/definitions/StringObj'
- $ref: '#/definitions/IntegerObj'
If you use Composed in - for example - body of your method and use try this operation Swagger creates json:
{
"stringId": "aaa",
"integerId": 123
}