Should a REST API parameter ever influence which other parameters are required? - rest

For instance, I can create a vehicle, and must give it a type which can be "automobile" or "airplane". Automobiles require a tire size parameter and airplanes require a wingspan parameter. Similar question regarding whether a REST API parameter should ever influence which properties are required in the response.
/vehicles:
post:
summary: Creates a vehicle
description: Adds a new vehicle of given type.
parameters:
- name: vehicle (what is the purpose of this????)
in: body
description: The vehicle to create.
schema:
required:
- type
- speed
- color
properties:
type:
type: string
speed:
type: interger
color:
type: string
wingspan:
type: string
tiresize:
type: string
responses:
204:
description: Vehicle succesfully created.
400:
description: Vehicle couldn't have been created.

While this happens in practice, it is best avoided as it makes it difficult to document your API. This is particularly true if you are using a documentation technology such as Swagger which does not allow these "conditionally required" parameters. By adding them, you are actually adding extra semantics to your API which are not documented in the Swagger docs.
A better approach is, instead of having a "type" parameter of different vehicle types, just use a separate URL for each type. This will allow you to properly document the required/optional parameters for each vehicle type.
/vehicles/automobile:
post:
parameters:
schema:
required:
- tyresize
properties:
tyresize:
type: string
/vehicles/airplane:
post:
parameters:
schema:
required:
- wingspan
properties:
wingspan:
type: string

Related

Define OpenAPI 2.0 returning JSON object of unknown type

I want to describe OpenAPI that returns JSON object of unknown/any type.
If I define return type in the yaml below I still see generated client returning just a raw string.
responses:
200:
description: Returns any JSON object
schema:
type: string
format: object
Is there a way to describe the return type as a JSON object without describing its schema?
An arbitrary object is defined as type: object, so the correct definition is:
responses:
200:
description: Returns any JSON object
schema:
type: object
Based on Helen's answer you can also explicitly define the content type besides the actual payload being unknown. This is for OpenAPI 3.0.
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
See a full example here.

OpenAPI: Design a reusable schema definition for GET, PUT, POST

I am using OpenAPI 3.0 to design and implement a simple API to allow basic CRUD operations on database entities.
Let's assume an entity Pet with some client-given properties (name & age) and some generated properties (id & created):
components:
schemas:
Pet:
type: object
properties:
id:
type: string
created:
type: string
format: date-time
name:
type: string
age:
type: integer
I want to specify REST endpoints to POST, GET, PUT (and if possible PATCH) a pet:
paths:
/pets:
post:
operationId: createPet
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
content:
application/json:
schema:
type: boolean
/pets/{id}:
parameters:
- name: id
schema:
type: string
in: path
required: true
get:
operationId: getPet
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
put:
operationId: updatePet
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
content:
application/json:
schema:
type: boolean
patch:
operationId: alterPet
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
content:
application/json:
schema:
type: boolean
From a logical point of view, the following properties are required per endpoint:
GET: id, created, name, age
POST: name, age
PUT: name, age
PATCH: name or age (or none depending on implementation)
I see two major approaches here:
Approach 1: Leave all properties optional
The Pet DTO is used as a shell where all properties are optional as defined above. It is left to the server & client to check if the required properties are filled on an endpoint call. If non-required properties are set in a POST/PUT/PATCH request body, they will be ignored.
pros:
Single, simple, and symmetric DTO schema definition in the API specification.
Smooth integration of the "GET -> modify -> PUT" workflow on the client side (e.g., a React frontend with Formik).
Supports the PATCH endpoint out-of-the-box.
cons:
All properties are optional and it is very tedious to handle these string | undefined types in Typescript. This is especially annoying in the GET direction as we know that all properties will be filled as long as we retrieve the Pet DTO.
The implementational "pain" is spread over the server and all possible clients.
Approach 2: Define separate DTO schemas for all operations
We introduce a GetPet, PutPet, PostPet, and PatchPet with corresponging required lists in the API specification. Yes, PutPet and PostPet can be identical, but maybe we want to allow a modified GetPet to be used as PutPet to streamline the "GET -> modify -> PUT" workflow for the client.
Imagine the Pet entity having 5+ generated properties and 20+ client-given properties. We do not want to flat define 4 variations of each entity, but use some kind of inheritance instead. Is there a way to introduce a BasePet bearing all properties (set to optional) and let the 4 operation-specific DTOs extend it with just overriding the required list?
pros:
Easier implementation for client & server as optionality is clarified for all properties.
The "pain" is handled in the API specification.
cons:
The API specification grows in complexity. It is not clear to me if the desired inhertance is possible and how it is specified.
Scalability & maintainability of the API may suffer.
So my question on this topic: How can the inhertiance of the DTOs be specified for OpenAPI 3.0? Are there better alternatives? I am happy for all suggestions concerning these thoughts on an API for basic CRUD operations.
I did face a similar issue while designing an API for a client recently. The trick here is to $ref: redundancies in the schemata for the different routes. For simplicity, I will only define GET, POST and PATCH.
Note: In this case it is easier, as the schema for write operations is a subset of the schema for the read operation.
By contrast, imagine a user-object, where there is a password field, it should be writable, but not readable. In this case, you would need three different schemata:
userRead
userWrite
userCommonFieldsForReadAndWrite
In any case, here is one possible solution to your question:
paths:
/pets:
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/petCreateUpdate"
responses:
"201":
description: Pet created.
headers:
Location:
schema:
type: string
description: points to the Pet resource created. Can be directly used in a subsequent GET request.
example: "Location: /pets/32"
/pets/{id}:
parameters:
- name: id
schema:
type: string
in: path
required: true
get:
responses:
"200":
description: Pet with given id returned.
content:
application/json:
schema:
$ref: "#/components/schemas/petRead"
"404":
description: Pet with given id not found.
patch:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/petCreateUpdate"
responses:
"204":
description: Pet updated successfully.
components:
schemas:
petRead:
allOf:
- type: object
properties:
id:
type: integer
format: int32
minimum: 1
created:
type: string
format: date-time
- $ref: "#/components/schemas/petCreateUpdate"
petCreateUpdate:
type: object
properties:
name:
type: string
age:
type: number
format: integer
minimum: 1
maximum: 100
This renders in SwaggerEditor as follows:
POST for the Pet
GET for the Pet
PATCH for the Pet

openapi3: how to specify that an object schema should be a schema itself?

I want a field in a PUT body that is itself supposed to be a jsonschema. How do I formally specify this rather than putting that info in a description?
type: object
description: "..."
properties:
foo:
type: integer
create_schema:
type: object
description: >
jsonschema (following http://json-schema.org/draft-07/schema) of the CREATE payload...
OpenAPI doesn't like the schema keyword in a definition here

What does it mean for a REST API to have a `required` response property?

Learning about REST APIs and am following https://apihandyman.io/writing-openapi-swagger-specification-tutorial-part-2-the-basics/.
The API can receive two parameters: username and bla, but only username is required by using the required keyword. This makes sense to me.
The API will return firstname, lastname, and username, but only username is required by using the required keyword. This does not make sense to me. Does the lack of the required keyword indicate that the other two might sometimes not be required? What influences whether they are or are not?
paths:
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
- name: bla
in: query
description: bla bla bla
type: string
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
404:
description: The Person does not exists.
Your interpretation is correct. If a property of a response object is listed in the required property list, is must be present in the response object for it to be valid, quite similar to the required field in a parameter object. Whether a non-required property is included in the response or not is up to the business logic of your application to decide.
Some more information with pointers to the relevant parts of the specification below:
The semantics of the required property list of a response object is defined as part of the Schema Object section of the OpenAPI specification. There it says that the schema object "is based on the JSON Schema Specification Draft 4 and uses a predefined subset of it".
In the corresponding section on the required validation keyword of the JSON Schema Validation specification its semantics is defined as follows:
5.4.3. required
5.4.3.1. Valid values
The value of this keyword MUST be an array. This array MUST have at
least one element. Elements of this array MUST be strings, and MUST be
unique.
5.4.3.2. Conditions for successful validation
An object instance is valid against this keyword if its property set
contains all elements in this keyword's array value.
You'll find further examples of how the required keyword can be used in the examples section of the JSON Schema specification or Part 5, Section 2.2 of the tutorial you're following.

How to exclude a property from a referenced schema?

I have an endpoint to create an address and one to update it. Describing this in an OpenAPI spec I'd like to use a component for the address so that I don't have to specify the address twice. Now the problem is, that the address object used for updating should include a property "id", but the one used for creating doesn't.
So basically, I'm looking for a way to describe the full address (incl. the id property) in the components section and then reference to the create endpoint, but excluding the "id" property there.
You can extend another type using the allOf keyword, as documented here
In the example below, creationType has only a name property, but updateType has all the properties of creationType as well as an id property.
Example:
components:
schemas:
creationType:
type: object
properties:
name:
type: string
updateType:
allOf:
- $ref: '#/components/schemas/creationType'
- type: object
required:
- id
properties:
id:
type: string