Tips for writing a good specification
Garbage in, garbage out applies especially to code generation tools. In short the more detailed, and accurate the specification, the better the code and documentation youâll get from it.
This page outlines some tips to enhance the quality of the generated code, and make your specification easier to maintain.
Declare operationIds
The operationId is used to generate method and type names. If you donât specify one,
then one will be generated from the HTTP method and route.
Eg: Without an operation id, youâll get generated method names, that might look like:
client.getV2ListListIdItems(...)
client.postV2ListListIdItems(...)Instead of more readable ones like:
client.getTodoListItems(...)
client.createTodoListItem(...)Keep query parameters simple
Query parameters work best for simple primitive types. They get confusing and ill defined when you venture into complex objects.
An effort has been made to correctly serialize and parse query parameters of all shapes, respecting the style
and explode modifiers⊠but weâd suggest you just donât use this where possible.
Stick to a simple set of string / number query parameters, maybe include an array of string / number if needed,
and your API will be easier to reason about.
For details of what is possible, albeit not recommended you can refer to the query-parser.spec.ts test suite
Use application/json, avoid using application/x-www-form-urlencoded request bodies
Similar to the point about query parameters above, weâd suggest you avoid using the application/x-www-form-urlencoded
content type for your request bodies, and use application/json instead. It doesnât have the same pitfalls around
percent encoding, etc.
Make liberal use of $ref
Using $ref can reduce the repetition in your specification, making it far more readable
and maintainable.
It also has the advantage of giving a name to things, that can be used in the generated code, and avoid generating duplicate code.
If you canât use $ref easily, there is also the option to extract-inline-schemas
which will generate names to avoid inline types, but it wonât save you from duplicate code.
Example:
paths:
/list:
get:
operationId: getTodoLists
responses:
200:
description: 'success'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TodoList'
components:
schemas:
TodoList:
properties:
...Compose $refs using allOf / anyOf
You can create union and intersection types using allOf and anyOf
Union Types
Using anyOf we can combine types / schemas into unions.
components:
schemas:
Apple:
type: object
Pear:
type: object
Fruit:
anyOf:
- $ref: "#/components/schemas/Apple"
- $ref: "#/components/schemas/Pear"Produces something like:
export type Apple = {}
export type Pear = {}
export type Fruit = Apple | PearIntersection Types
Using allOf of we can combine types / schemas into intersection types. This is often
handy for âextendingâ a type with additional properties
components:
schemas:
Profile:
type: object
properties:
...
FullProfile:
type: object
allOf:
- $ref: "#/components/schemas/Profile"
- type: object
properties:
...Produces something like:
export type Profile = {}
export type FullProfile = Profile & {}Use validation constraints where sensible
A fairly rich set of validations can be specified in the specification. Make use of these in order for robust runtime validation.
Example:
components:
schemas:
MyRequestBody:
type: object
properties:
name:
type: string
minLength: 1
tags:
type: array
minItems: 1
maxItems: 100
items:
type: stringSee compatibility table for an idea of whatâs possible.
Use a clear info.title
The root info.title property is used to name the generated client. Using a name like:
info:
title: Awesome ServiceWill output a class AwesomeServiceClient
If you canât modify the title, you can use --override-specification-title "Some Other Title"
to workaround.