swagger-core
swagger-core copied to clipboard
Allow inline schema to support strict type generation for client developers
Feature request: Allow schemas of request bodies (or probably for all related elements) to be inline rather then always a reference to a common schema. E.g. by adding a property @Schema(inline = true).
Alternatively: Have some feature that supports the use case described below.
I have a specific use case for this request in mind that I have not seen represented in any other issue. I basically want to accomplish three things:
- Have one DTO class for one resource in my server code, that I can use for PUT and GET (resp. for create/update/retrieve)
- Have all schema annotations for this resource in this one DTO class.
- Allow users of the API to generate distinct types for POST/PUT/GET based on my API scheme, especially for handling required properties in different scenarios
I want to publish an API like:
PUT /my-resource/{id} --> create/update the resource.
Request Body: Only input properties (~not access-mode=read-only), response Body: All properties
GET /my-resource/{id} --> retrieve resource,
Response Body: All properties (and obviously no request body)
This can be accomplished by modeling a resource class like this:
class MyResource {
// input field, part of request body and response body
@Schema()
String inputField;
// read-only: not present in request body, only in response body
// required: field is not optional, will always be filled by server on GET
@Schema(required = true, accessMode = Schema.AccessMode.READ_ONLY)
String outputField;
}
The typical use case for outputField is the a resource ID. The ID should not be part of a request body; in POST the server will generate the ID, in PUT the ID will be part of the URL. But there are plenty of other use cases, e,g, cratedAt, modifedAt and any number of examples you might come up with depending on your domain.
A tool like Swagger UI will present this scenario correctly: On PUT, outputField will not be displayed, and if you use "Try it out" it will not require you to fill this field. At the same time the property is present in the response and correctly marked as required.
BUT: There are problems when a client developer generates types from this API scheme. E.g. a TypeScript generator might produce something like:
interface MyResource {
inputField: string | undefined;
outputField: string;
}
Note that in TypeScript outputField may never be null.
For GET this is exactly what I want to give the client developer: outputField is guaranteed to be !=null by the server.
const myResource = server.get('/my-resource/123');
// no compiler warning on next line, since outputField can never be null
if (myResource.outputField.length == 42)
But for POST this now requires the client developer to fill outputField with some dummy value that will be ignored by the server:
const myNewResource = {
inputField: 'some sensible value',
outputField: 'some redundant value that will be ignored by server',
}
server.put('/my-resource/123', myNewResource);
The only way I have found to avoid this, is to split the resource class definition, eg. by subclassing like this:
class MyResourceInput {
@Schema()
String inputField;
}
class MyResource extends MyResourceInput {
@Schema(required = true, accessMode = Schema.AccessMode.READ_ONLY)
String outputField;
}
But this also splits my schema annotation across multiple files. And it gets really nasty if you have sub-object properties with mixed input/output properties, in this case you have to introduce generics to deal with it all, e.g.:
class MyItemInput {
@Schema()
String inputField;
}
class MyItem extends MyItemInput {
@Schema(required = true, accessMode = Schema.AccessMode.READ_ONLY)
String outputField;
}
class MyResourceInput<T extends MyItemInput> {
List<T> items;
}
class MyResource extends MyResourceInput<MyItem> {
}
I see that some APIs provide a solution for this (e,g. the GitHub-API (https://docs.github.com/en/rest).
- Use common schemas for all response bodies; these schemas are the "proper" representation of the resource
- Use inline schemas for request bodies; so a generator will generate a specific type per operation
So ... it would really be awesome if inline schemas were a thing.
Or something else that supports this use case. E.g. a parameter that I could use to control the schema for one operation like
@PutMapping(value = "my-resource/{id}")
public ResponseEntity<MyResource> create(
@RequestBody @PublishAsSchemaWithoutReadOnly(name = "MyResourceInput") MyResource input) {
...
}
Sorry that this has become this long. Hopefully this request isn't silly and I am not overseeing some obvious solution for my conundrum.