README
¶
oapi-codegen
Using oapi-codegen allows you to reduce the boilerplate required to create or integrate with
services based on OpenAPI 3.x, and instead focus on writing your business logic, and working
on the real value-add for your organisation.
With oapi-codegen, there are a few Key Design Decisions we've made,
including:
- idiomatic Go, where possible
- fairly simple generated code, erring on the side of duplicate code over nicely refactored code
- supporting as much of OpenAPI 3.x as is possible, alongside Go's type system
Migrate from v2
This project is a fork of oapi-codegen v2.
Due to the lack of OpenAPI 3.1 support in the original repository, we introduced a fully reworked implementation.
While this includes some breaking changes, it also brings more flexible generator and parser APIs for finer control over code generation.
If you're migrating from v2, please refer to the migration guide for important differences.
Usage
oapi-codegen is largely configured using a YAML configuration file, to simplify the number of
flags that users need to remember, and to make reading the go:generate command less daunting.
Features
At a high level, oapi-codegen supports:
- Generating the types (docs)
- Splitting large OpenAPI specs across multiple packages(docs)
- This is also known as "Import Mapping" or "external references" across our documentation / discussion in GitHub issues
Key design decisions
- Produce an interface that can be satisfied by your implementation, with reduced boilerplate
- Bulk processing and parsing of OpenAPI document in Go
- Resulting output is using Go's
text/templates, which are user-overridable - Attempts to produce Idiomatic Go
- Single or multiple file output
- Support of OpenAPI 3.1
- Extract parameters from requests, to reduce work required by your implementation
- Implicit
additionalPropertiesare ignored by default (more details) - Prune unused types by default
Generating API models
If you're looking to only generate the models for interacting with a remote service, for instance if you need to hand-roll the API client for whatever reason, you can do this as-is.
[!TIP] Try to define as much as possible within the
#/components/schemasobject, asoapi-codegenwill generate all the types here.Although we can generate some types based on inline definitions in i.e. a path's response type, it isn't always possible to do this, or if it is generated, can be a little awkward to work with as it may be defined as an anonymous struct.
OpenAPI extensions
As well as the core OpenAPI support, we also support the following OpenAPI extensions, as denoted by the OpenAPI Specification Extensions.
| Extension | Description | Example usage |
|---|---|---|
|
|
Override the generated type definition (and optionally, add an import from another package) |
Using the We can see this at play with the following schemas:
From here, we now get two different models:
You can see this in more detail in the example code. |
|
|
Do not add a pointer type for optional fields in structs |
By default, Using the We can see this at play with the following schemas:
From here, we now get two different models:
You can see this in more detail in the example code. |
|
|
Override the generated name of a field or a type |
By default, However, sometimes, the name doesn't quite fit what your codebase standards are, or the intent
of the field, so you can override it with We can see this at play with the following schemas:
From here, we now get two different models:
You can see this in more detail in the example code. |
|
|
Override the generated name of a type |
By default, However, sometimes, the name doesn't quite fit what your codebase standards are, or the intent of the field, so you can override it with We can see this at play with the following schemas:
From here, we now get two different models and a type alias:
You can see this in more detail in the example code. |
|
|
Force the presence of the JSON tag `omitempty` on a field |
In a case that you may want to add the JSON struct tag We can see this at play with the following schemas:
From here, we now get two different models:
You can see this in more detail in the example code. |
|
|
When (un)marshaling JSON, ignore field(s) |
By default, However, sometimes, you want to omit fields, which can be done with the We can see this at play with the following schemas:
From here, we now get two different models:
Notice that the You can see this in more detail in the example code. |
|
|
Generate arbitrary struct tags to fields |
If you're making use of a field's struct tags to i.e. apply validation, decide whether something should be logged, etc, you can use We can see this at play with the following schemas:
From here, we now get two different models:
You can see this in more detail in the example code. |
|
|
Automatically mask sensitive data in JSON output |
The The extension supports several masking strategies:
Example:
This generates:
When marshaling to JSON:
Partial masking options:
You can see this in more detail in the example code. |
|
|
Override generated variable names for enum constants |
When consuming an enum value from an external system, the name may not produce a nice variable name.
Using the We can see this at play with the following schemas:
From here, we now get two different forms of the same enum definition.
You can see this in more detail in the example code. |
|
|
Add a GoDoc deprecation warning to a type |
When an OpenAPI type is deprecated, a deprecation warning can be added in the GoDoc
using We can see this at play with the following schemas:
From here, we now get two different forms of the same enum definition.
Notice that because we've not set You can see this in more detail in the example code. |
Custom code generation
It is possible to extend the inbuilt code generation from oapi-codegen using
Go's text/templates.
You can specify, through your configuration file, the user-templates setting to override
the inbuilt templates and use a user-defined template.
[!NOTE] Filenames given to the
user-templatesconfiguration must exactly match the filename thatoapi-codegenis looking for
Local paths
Within your configuration file, you can specify relative or absolute paths to a file to reference for the template, such as:
# yaml-language-server: $schema=https://raw.githubusercontent.com/doordash/oapi-codegen/HEAD/configuration-schema.json
# ...
user-templates:
client.tmpl: ./custom-template.tmpl
enums.tmpl: /tmp/foo.bar
types.tmpl: no-prefix.tmpl
Using the Go package
You can get full control of the generator and the parser by using the codegen package directly.
TBD: add documentation
Additional Properties (additionalProperties)
OpenAPI Schemas implicitly accept additionalProperties, meaning that any fields
provided, but not explicitly defined via properties on the schema are accepted as input,
and propagated.
When unspecified, OpenAPI defines that the additionalProperties field is assumed to be true.
For simplicity, and to remove a fair bit of duplication and boilerplate,
oapi-codegen decides to ignore the implicit additionalProperties: true,
and instead requires you to specify the additionalProperties key to generate the boilerplate.
Below you can see some examples of how additionalProperties affects the generated code.
Implicit additionalProperties: true / no additionalProperties set
components:
schemas:
Thing:
type: object
required:
- id
properties:
id:
type: integer
# implicit additionalProperties: true
Will generate:
// Thing defines model for Thing.
type Thing struct {
Id int `json:"id"`
}
// with no generated boilerplate nor the `AdditionalProperties` field
Explicit additionalProperties: true
components:
schemas:
Thing:
type: object
required:
- id
properties:
id:
type: integer
# explicit true
additionalProperties: true
Will generate:
// Thing defines model for Thing.
type Thing struct {
Id int `json:"id"`
AdditionalProperties map[string]interface{} `json:"-"`
}
// with generated boilerplate below
Boilerplate
// Getter for additional properties for Thing. Returns the specified
// element and whether it was found
func (a Thing) Get(fieldName string) (value interface{}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for Thing
func (a *Thing) Set(fieldName string, value interface{}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]interface{})
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a *Thing) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if raw, found := object["id"]; found {
err = json.Unmarshal(raw, &a.Id)
if err != nil {
return fmt.Errorf("error reading 'id': %w", err)
}
delete(object, "id")
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]interface{})
for fieldName, fieldBuf := range object {
var fieldVal interface{}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a Thing) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
object["id"], err = json.Marshal(a.Id)
if err != nil {
return nil, fmt.Errorf("error marshaling 'id': %w", err)
}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
additionalProperties as integers
components:
schemas:
Thing:
type: object
required:
- id
properties:
id:
type: integer
# simple type
additionalProperties:
type: integer
Will generate:
// Thing defines model for Thing.
type Thing struct {
Id int `json:"id"`
AdditionalProperties map[string]int `json:"-"`
}
// with generated boilerplate below
Boilerplate
// Getter for additional properties for Thing. Returns the specified
// element and whether it was found
func (a Thing) Get(fieldName string) (value int, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for Thing
func (a *Thing) Set(fieldName string, value int) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]int)
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a *Thing) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if raw, found := object["id"]; found {
err = json.Unmarshal(raw, &a.Id)
if err != nil {
return fmt.Errorf("error reading 'id': %w", err)
}
delete(object, "id")
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]int)
for fieldName, fieldBuf := range object {
var fieldVal int
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a Thing) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
object["id"], err = json.Marshal(a.Id)
if err != nil {
return nil, fmt.Errorf("error marshaling 'id': %w", err)
}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
additionalProperties with an object
components:
schemas:
Thing:
type: object
required:
- id
properties:
id:
type: integer
# object
additionalProperties:
type: object
properties:
foo:
type: string
Will generate:
// Thing defines model for Thing.
type Thing struct {
Id int `json:"id"`
AdditionalProperties map[string]struct {
Foo *string `json:"foo,omitempty"`
} `json:"-"`
}
// with generated boilerplate below
Boilerplate
// Getter for additional properties for Thing. Returns the specified
// element and whether it was found
func (a Thing) Get(fieldName string) (value struct {
Foo *string `json:"foo,omitempty"`
}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for Thing
func (a *Thing) Set(fieldName string, value struct {
Foo *string `json:"foo,omitempty"`
}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]struct {
Foo *string `json:"foo,omitempty"`
})
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a *Thing) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if raw, found := object["id"]; found {
err = json.Unmarshal(raw, &a.Id)
if err != nil {
return fmt.Errorf("error reading 'id': %w", err)
}
delete(object, "id")
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]struct {
Foo *string `json:"foo,omitempty"`
})
for fieldName, fieldBuf := range object {
var fieldVal struct {
Foo *string `json:"foo,omitempty"`
}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for Thing to handle AdditionalProperties
func (a Thing) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
object["id"], err = json.Marshal(a.Id)
if err != nil {
return nil, fmt.Errorf("error marshaling 'id': %w", err)
}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
Examples
The examples directory contains some additional cases which are useful examples
for how to use oapi-codegen, including how you'd take the Petstore API and implement it
with oapi-codegen.
Frequently Asked Questions (FAQs)
How does oapi-codegen handle anyOf, allOf and oneOf?
oapi-codegen supports anyOf, allOf and oneOf for generated code.
For instance, through the following OpenAPI spec:
openapi: "3.0.0"
info:
version: 1.0.0
title: Using complex schemas
description: An example of `anyOf`, `allOf` and `oneOf`
components:
schemas:
# base types
Client:
type: object
required:
- name
properties:
name:
type: string
Identity:
type: object
required:
- issuer
properties:
issuer:
type: string
# allOf performs a union of all types defined
ClientWithId:
allOf:
- $ref: '#/components/schemas/Client'
- properties:
id:
type: integer
required:
- id
# allOf performs a union of all types defined, but if there's a duplicate field defined, it'll be overwritten by the last schema
# https://github.com/oapi-codegen/oapi-codegen/issues/1569
IdentityWithDuplicateField:
allOf:
# `issuer` will be ignored
- $ref: '#/components/schemas/Identity'
# `issuer` will be ignored
- properties:
issuer:
type: integer
# `issuer` will take precedence
- properties:
issuer:
type: object
properties:
name:
type: string
required:
- name
# anyOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve
ClientAndMaybeIdentity:
anyOf:
- $ref: '#/components/schemas/Client'
- $ref: '#/components/schemas/Identity'
# oneOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve
ClientOrIdentity:
oneOf:
- $ref: '#/components/schemas/Client'
- $ref: '#/components/schemas/Identity'
This results in the following types:
Base types
// Client defines model for Client.
type Client struct {
Name string `json:"name"`
}
// Identity defines model for Identity.
type Identity struct {
Issuer string `json:"issuer"`
}
allOf
// ClientWithId defines model for ClientWithId.
type ClientWithId struct {
Id int `json:"id"`
Name string `json:"name"`
}
// IdentityWithDuplicateField defines model for IdentityWithDuplicateField.
type IdentityWithDuplicateField struct {
Issuer struct {
Name string `json:"name"`
} `json:"issuer"`
}
anyOf
import (
"encoding/json"
"github.com/oapi-codegen/runtime"
)
// ClientAndMaybeIdentity defines model for ClientAndMaybeIdentity.
type ClientAndMaybeIdentity struct {
union json.RawMessage
}
// AsClient returns the union data inside the ClientAndMaybeIdentity as a Client
func (t ClientAndMaybeIdentity) AsClient() (Client, error) {
var body Client
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromClient overwrites any union data inside the ClientAndMaybeIdentity as the provided Client
func (t *ClientAndMaybeIdentity) FromClient(v Client) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeClient performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Client
func (t *ClientAndMaybeIdentity) MergeClient(v Client) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsIdentity returns the union data inside the ClientAndMaybeIdentity as a Identity
func (t ClientAndMaybeIdentity) AsIdentity() (Identity, error) {
var body Identity
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromIdentity overwrites any union data inside the ClientAndMaybeIdentity as the provided Identity
func (t *ClientAndMaybeIdentity) FromIdentity(v Identity) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeIdentity performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Identity
func (t *ClientAndMaybeIdentity) MergeIdentity(v Identity) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ClientAndMaybeIdentity) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ClientAndMaybeIdentity) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
oneOf
// AsClient returns the union data inside the ClientOrIdentity as a Client
func (t ClientOrIdentity) AsClient() (Client, error) {
var body Client
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromClient overwrites any union data inside the ClientOrIdentity as the provided Client
func (t *ClientOrIdentity) FromClient(v Client) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeClient performs a merge with any union data inside the ClientOrIdentity, using the provided Client
func (t *ClientOrIdentity) MergeClient(v Client) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsIdentity returns the union data inside the ClientOrIdentity as a Identity
func (t ClientOrIdentity) AsIdentity() (Identity, error) {
var body Identity
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromIdentity overwrites any union data inside the ClientOrIdentity as the provided Identity
func (t *ClientOrIdentity) FromIdentity(v Identity) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeIdentity performs a merge with any union data inside the ClientOrIdentity, using the provided Identity
func (t *ClientOrIdentity) MergeIdentity(v Identity) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ClientOrIdentity) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ClientOrIdentity) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
For more info, check out the example code.
How can I ignore parts of the spec I don't care about?
By default, oapi-codegen will generate everything from the specification.
If you'd like to reduce what's generated, you can use one of a few options in the configuration file to tune the generation of the resulting output:
# yaml-language-server: $schema=https://raw.githubusercontent.com/doordash/oapi-codegen/HEAD/configuration-schema.json
filter:
include:
paths: []
tags: []
operation-ids: []
schema-properties:
extensions: []
exclude:
operation-ids: []
License
This project is licensed under the Apache License 2.0.
See LICENSE.txt for details.
Notices
See NOTICE.txt for third-party components and attributions.
Contributor License Agreement (CLA)
Contributions to this project require agreeing to the DoorDash Contributor License Agreement.
See CONTRIBUTOR_LICENSE_AGREEMENT.txt.