Getting StartedTips for Writing Specifications

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(...)

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 | Pear

Intersection 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: string

See 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 Service

Will output a class AwesomeServiceClient

If you can’t modify the title, you can use --override-specification-title "Some Other Title" to workaround.