OpenAPI Specification Schemas
External
- https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schemaObject
- https://tools.ietf.org/html/draft-bhutton-json-schema-00
- https://tools.ietf.org/html/draft-bhutton-json-schema-validation-00
Internal
Things to Clarify
- How is the Error type used by the code generator? Do we really need to declare it in the OpenAPI specification, or we should rely on HTTP status code support in the web framework used by the code generator?
Overview
The /components/schemas
section of the OpenAPI specification defines reusable types that are used as input and output data types. These types can represent objects, but also primitives and arrays. The specification is based on JSON Schema Specification Draft 2020-12. A client or server code generator creates programming language types from these schemas.
This article is annotated with details related to how oapi-codegen
generates Go code. It is best to define types under the /components
field in the OpenAPI schema file, since those will be used to generate named Go types. If the types are instead inlined in the handler definitions, oapi-codegen
will generate in-line anonymous Go types, which are more tedious to deal with.
Schema
SchemaName type: object|string|integer|number|array|boolean format: int32|int64|float|double|password|date|email|file|uuid properties: color: type: string description: .... size: type: integer format: int32 description: .... minimum: 0 maximum: 100 weight: type: number format: double description: .... required: - color - size - weight description: discriminator: externalDocs: example:
A Go type is generated for a schema only if a schema is referred from the specification with schema.$ref
. If the type is declared, but not referred, no code will be generated for it. Alias types are generated: a custom-named typed for integer or string can be generated.
Both the types and their fields are implicitly exported with oapi-codegen
, they get names that start with upper case characters, even if they are declared with lower case characters in the OpenAPI specification.
Schema Name
type
One of the supported data types ("object", "string", "integer", "number", "array", "boolean", etc.):
format
One of the supported formats ( "int32", "int64", "float", "double", "password", "date", "email", etc.):
minimum
maximum
properties
For a schema of type object
, properties
contains a map whose keys are the names of the fields. Each map element must mandatorily include type
and optionally format
:
MyType:
type: object
properties:
color: # 'color' is the name of a field of MyType
type: string
size: # 'size' is the name of a field of MyType
type: integer
format: int32
minimum: 0
weight: # 'weight' is the name of a field of MyType
type: number
format: double
required
An array with the name of the required fields:
MyType:
[...]
required:
- color
- size
# weight is not required, it will be represented as a pointer
If a field is declared as "required", oapi-codegen
will generate the struct
corresponding to the schema with a value for that field. If the field is not among the required fields, the struct
will carry a pointer instead of a the value, presumably so it can be set to nil
.
type MyType struct {
Color string `json:"color"` // required field
Size int32 `json:"size"` // required field
Weight *float64 `json:"weight,omitempty"` // not required field
}
description
components:
schemas:
PetPayload:
description: |
Type that encapsulates the state of a Pet, less its ID. This type will be used for validation when
creating new Pets, when the ID is not available yet, and it will be combined with a type that encapsulates
a mandatory ID to create a composite type (Pet) when retrieving existing pets.
[...]
discriminator
TODO. Also see Polymorphism below.
externalDocs
example
type: object
properties:
id:
type: integer
format: int64
name:
type: string
required:
- name
example:
name: Blue
id: 1
Composition
Types can be composed with $ref
:
components:
schemas:
Person:
type: object
properties:
firstName:
type: string
lastName:
type: string
address:
$ref: "#/components/schemas/Address"
required:
- firstName
- lastName
- address
Address:
type: object
properties:
street:
type: string
city:
type: string
zip:
type: integer
format: int32
required:
- street
- city
- zip
The corresponding oapi-codegen
generated code:
type Person struct {
Address Address `json:"address"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
}
type Address struct {
City string `json:"city"`
Street string `json:"street"`
Zip int32 `json:"zip"`
}
An Array
components:
schemas:
AnArray:
type: array
items:
type: string
format: uuid
Mixed type arrays: https://swagger.io/docs/specification/data-models/data-types/#mixed-array
A <String,String> Map
components:
schemas:
AMap:
type: object
additionalProperties:
type: string
The corresponding oapi-codegen
generated code:
type AMap map[string]string
A <String,Object> Map
components:
schemas:
AMap:
type: object
additionalProperties:
$ref: "#/components/schemas/MapElement"
MapElement:
type: object
properties:
color:
type: string
size:
type: integer
required:
- color
- size
The corresponding oapi-codegen
generated code:
type AMap map[string]MapElement
type MapElement struct {
Color string `json:"color"`
Size int `json:"size"`
}
Combining Schemas
OpenAPI 3.0 provides a set of keywords that can be used to combine schemas: allOf
, oneOf
, anyOf
and not
. These keywords can be used to compose complex schemas from components, or validate against multiple criteria. The keywords are defined in JSON Schema and used in OpenAPI to define the structure and validation rules for data.
allOf
allOf
takes an array of object definitions that are used for independent validation - they can be used as individual types - and composes them in a single object. allOf
can be used to create a new type by combining multiple existing types. The new type has all the features of the existing types. This is a simple composition model, it does imply hierarchy between models. To create a hierarchy, use the discriminator
field. To be valid against allOf
, the data provided by the client must be valid against all of the given subschemas.
components:
schemas:
PetPayload:
description: |
Type that encapsulates the state of a Pet, less its ID. This type will be used for validation when
creating new Pets, when the ID is not available yet, and it will be combined with a type that encapsulates
a mandatory ID to create a composite type (Pet) when retrieving existing pets.
type: object
properties:
name:
type: string
age:
type: integer
format: int32
minimum: 0
tag:
type: string
required:
- name
- age
Pet:
description: |
The union of PetPayload and an anonymous type that carries a mandatory ID. This type will used to retrieve
existing Pets that have an ID in the database.
allOf:
- # the type component that carries the mandatory ID. This type is composed with PetPayload to create
# 'Pet', the type that is used to retrieve existing Pets.
type: object
properties:
ID:
type: string
format: uuid
required:
- ID
- $ref: '#/components/schemas/PetPayload'
description
on types that involve allOf
does not seem to be propagated to generated types with oapi-codegen
.
oneOf
oneOf
is a JSON Schema keyword specifying that a value must match exactly one form a given set of scheme. oneOf
is the closest OpenAPI analog to the concept of a union type. A union type is a way to declare a variable or parameter that can hold values of multiple different types.
anyOf
not
Polymorphism
See discriminator
above.
Examples
Error Type
See Things to Clarify above.
components:
schemas:
Error:
description: |
A type that encapsulates a generic error.
type: object
properties:
Code:
type: integer
format: int32
minimum: 300
maximum: 600
Message:
type: string
required:
- Code
- Message
Empty Type
components:
schemas:
Empty:
type: object
Used in empty responses.