OpenAPI Specification Schemas: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(57 intermediate revisions by the same user not shown)
Line 6: Line 6:
=Internal=
=Internal=
* [[OpenAPI_Specification#schemas|OpenAPI Specification]]
* [[OpenAPI_Specification#schemas|OpenAPI Specification]]
=Things to Clarify=
<font color=darkkhaki>
* How is the [[OpenAPI_Specification_Schemas#Error_Type|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?
</font>
=Overview=
=Overview=
The <code>/components/schemas</code> 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 [https://tools.ietf.org/html/draft-bhutton-json-schema-00 JSON Schema Specification Draft 2020-12]. A client or server code generator creates programming language types from these schemas.  
The <code>/components/schemas</code> 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 [https://tools.ietf.org/html/draft-bhutton-json-schema-00 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 <code>[[Oapi-codegen#Overview|oapi-codegen]]</code> generates Go code.
This article is annotated with details related to how <code>[[Oapi-codegen#Overview|oapi-codegen]]</code> generates Go code. It is best to define types under the <code>/components</code> 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, <code>oapi-codegen</code> will generate in-line anonymous Go types, which are more tedious to deal with.


=Schema=
=Schema=
Line 19: Line 24:
     color:
     color:
       type: string
       type: string
      description: ....
     size:
     size:
       type: integer
       type: integer
       format: int32
       format: int32
      description: ....
       [[#minimum|minimum]]: 0
       [[#minimum|minimum]]: 0
      [[#maximum|maximum]]: 100
     weight:
     weight:
       type: number
       type: number
       format: double
       format: double
      description: ....
   [[#required|required]]:
   [[#required|required]]:
     - color
     - color
Line 33: Line 42:
   [[#discriminator|discriminator]]:
   [[#discriminator|discriminator]]:
   [[#externalDocs|externalDocs]]:
   [[#externalDocs|externalDocs]]:
  [[#example|example]]:
</font>
</font>


Line 47: Line 57:
One of the supported formats ( "int32", "int64", "float", "double", "password", "date", "email", etc.): {{Internal|OpenAPI_Specification#Data_Types|OpenAPI Data Types}}
One of the supported formats ( "int32", "int64", "float", "double", "password", "date", "email", etc.): {{Internal|OpenAPI_Specification#Data_Types|OpenAPI Data Types}}
==<tt>minimum</tt>==
==<tt>minimum</tt>==
==<tt>maximum</tt>==


==<tt>properties</tt>==
==<tt>properties</tt>==
Line 76: Line 87:
     - color
     - color
     - size
     - size
     - weight
     # weight is not required, it will be represented as a pointer
</syntaxhighlight>
</syntaxhighlight>


If a field is declared as "required", <code>[[Oapi-codegen|oapi-codegen]]</code> will generate the <code>struct</code> corresponding to the schema with a value for that field. If the field is not among the required fields, the <code>struct</code> will carry a pointer instead of a the value, presumably so it can be set to <code>nil</code>.
If a field is declared as "required", <code>[[Oapi-codegen|oapi-codegen]]</code> will generate the <code>struct</code> corresponding to the schema with a value for that field. If the field is not among the required fields, the <code>struct</code> will carry a pointer instead of a the value, presumably so it can be set to <code>nil</code>.
<syntaxhighlight lang='go'>
type MyType struct {
Color  string  `json:"color"`            // required field
Size  int32    `json:"size"`            // required field
Weight *float64 `json:"weight,omitempty"` // not required field
}
</syntaxhighlight>


==<tt>description</tt>==
==<tt>description</tt>==
<syntaxhighlight lang='yaml'>
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.
      [...]
</syntaxhighlight>
==<tt>discriminator</tt>==
==<tt>discriminator</tt>==
<font color=darkkhaki>TODO. Also see [[#Polymorphism|Polymorphism]] below.</font>
==<tt>externalDocs</tt>==
==<tt>externalDocs</tt>==
==<tt>example</tt>==
<syntaxhighlight lang='yaml'>
type: object
properties:
  id:
    type: integer
    format: int64
  name:
    type: string
required:
- name
example:
  name: Blue
  id: 1
</syntaxhighlight>


=Composition=
=Composition=
Line 118: Line 164:
         - zip
         - zip
</syntaxhighlight>
</syntaxhighlight>
<code>allOf</code>
The corresponding <code>[[Oapi-codegen#Overview|oapi-codegen]]</code> generated code:
<syntaxhighlight lang='go'>
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"`
}
</syntaxhighlight>


=A <String,String> Map=
=An Array=
<syntaxhighlight lang='yaml'>
components:
  schemas:
    AnArray:
      type: array
      items:
        type: string
        format: uuid
</syntaxhighlight>
 
<font color=darkkhaki>Mixed type arrays: https://swagger.io/docs/specification/data-models/data-types/#mixed-array</font>
=A <tt><String,String></tt> Map=
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
components:
components:
Line 130: Line 201:
</syntaxhighlight>
</syntaxhighlight>


The corresponding generated code:
The corresponding <code>[[Oapi-codegen#Overview|oapi-codegen]]</code> generated code:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
type AMap map[string]string
type AMap map[string]string
</syntaxhighlight>
</syntaxhighlight>
=A <String,Object> Map=
 
=A <tt><String,Object></tt> Map=
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
components:
components:
Line 154: Line 226:
</syntaxhighlight>
</syntaxhighlight>


The corresponding generated code:
The corresponding <code>[[Oapi-codegen#Overview|oapi-codegen]]</code> generated code:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
type AMap map[string]MapElement
type AMap map[string]MapElement
Line 163: Line 235:
</syntaxhighlight>
</syntaxhighlight>


=Polymorphism=
=Combining Schemas=
{{External|https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism}}
OpenAPI 3.0 provides a set of keywords that can be used to combine schemas: <code>[[#allOf|allOf]]</code>, <code>[[#oneOf|oneOf]]</code>, <code>[[#anyOf|anyOf]]</code> and <code>[[#not|not]]</code>. These keywords can be used to compose complex schemas from components, or validate against multiple criteria. The keywords are defined in [[JSON_Schema#Overview|JSON Schema]] and used in OpenAPI to define the structure and validation rules for data.
 
==<tt>allOf</tt>==
{{External|https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#models-with-composition}}
<code>allOf</code> 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. <code>allOf</code> 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 <code>[[#discriminator|discriminator]]</code> field. To be valid against <code>allOf</code>, the data provided by the client must be valid against all of the given subschemas.
<span id='PetPayload'></span><span id='Pet'></span><syntaxhighlight lang='yaml'>
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'
</syntaxhighlight>
 
 
<font color=darkkhaki><code>description</code> on types that involve <code>allOf</code> does not seem to be propagated to generated types with <code>[[Oapi-codegen|oapi-codegen]]</code>.</font>
 
==<tt>oneOf</tt>==
{{External|https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/}}
 
<code>oneOf</code> is a JSON Schema keyword specifying that a value must match exactly one form a given set of scheme. <code>oneOf</code> 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.


<code>discriminator</code>
==<tt>anyOf</tt>==
{{External|https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/}}


==<tt>not</tt>==
{{External|https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/}}


=TO WRAP UP Example=
=Polymorphism=
{{External|https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism}}
{{External|https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#models-with-polymorphism-support}}
See <code>[[#discriminator|discriminator]]</code> above.
=Examples=
==Error Type==
<font color=darkkhaki>See [[#Things_to_Clarify|Things to Clarify]] above.</font>
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
components:
components:
   schemas:
   schemas:
     Pet:
     Error:
       allOf:
       description: |
         - $ref: '#/components/schemas/NewPet'
         A type that encapsulates a generic error.
        - type: object
          required:
            - id
          properties:
            id:
              type: integer
              format: int64
    NewPet:
       type: object
       type: object
      required:
        - name
       properties:
       properties:
         name:
         Code:
           type: string
           type: integer
         tag:
          format: int32
          minimum: 300
          maximum: 600
         Message:
           type: string
           type: string
      required:
        - Code
        - Message
</syntaxhighlight>
==Empty Type==
<syntaxhighlight lang='yaml'>
components:
  schemas:
    Empty:
      type: object
</syntaxhighlight>
</syntaxhighlight>
Used in [[OpenAPI_Specification_Path#Empty_Response|empty responses]].

Latest revision as of 19:07, 1 February 2024

External

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

OpenAPI Data Types

format

One of the supported formats ( "int32", "int64", "float", "double", "password", "date", "email", etc.):

OpenAPI Data Types

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

https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#models-with-composition

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

https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/

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

https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/

not

https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/

Polymorphism

https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#models-with-polymorphism-support

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.