OpenAPI Specification Path: Difference between revisions
(24 intermediate revisions by the same user not shown) | |||
Line 246: | Line 246: | ||
type: string | type: string | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=Examples= | =Operation Declaration and Implementation Examples= | ||
The server code generation the following examples work in top of is done with: {{Internal|Oapi-codegen#Server_Code_Generation|<tt>oapi-codegen</tt>}} | |||
==Create a Resource Instance== | ==Create a Resource Instance== | ||
Creation of new resources is conventionally implemented with <code>POST</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#POST|HTTP POST}} | |||
OpenAPI path specification: | OpenAPI path specification: | ||
<syntaxhighlight lang='yaml'> | <syntaxhighlight lang='yaml'> | ||
Line 266: | Line 271: | ||
$ref: '#/components/schemas/PetPayload' | $ref: '#/components/schemas/PetPayload' | ||
responses: | responses: | ||
201: | |||
description: The state of a newly create instance, including the new ID. | description: The state of a newly create instance, including the new ID. | ||
content: | content: | ||
Line 280: | Line 285: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The following types are used: | The following types are used: | ||
* [[]] | * <code>[[OpenAPI_Specification_Schemas#PetPayload|PetPayload]]</code> type. | ||
* [[OpenAPI_Specification_Schemas#Error_Type|Error]] type. | * <code>[[OpenAPI_Specification_Schemas#Pet|Pet]]</code> type. | ||
* <code>[[OpenAPI_Specification_Schemas#Error_Type|Error]]</code> type. | |||
Server implementation: | |||
<syntaxhighlight lang='go'> | |||
func (s *PetStoreServer) CreatePet(ctx echo.Context) error { | |||
var petPayload petstore.PetPayload | |||
err := json.NewDecoder(ctx.Request().Body).Decode(&petPayload) | |||
if err != nil { | |||
return err | |||
} | |||
// create and store the pet, then return the result, the ID will be generated internally by NewPet() | |||
p := petstore.NewPet(petPayload) | |||
s.Lock() | |||
defer s.Unlock() | |||
s.pets[p.ID] = p | |||
resp, err := json.Marshal(p) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = ctx.Response().Write(resp) | |||
return err | |||
} | |||
</syntaxhighlight> | |||
Invocation: | |||
<syntaxhighlight lang='bash'> | |||
curl -H "Content-Type: application/json" -X POST -d '{"name":"Fido", "age":2, "tag":"blue"}' http://localhost:30000/pets | |||
</syntaxhighlight> | |||
===TODO Create=== | |||
<font color=darkkhaki>The appropriate response should be 201. See [[HTTP_Status_Codes#201_Created|HTTP 201]] and the "Location" header may contain the URL of the new resource, see: {{Internal|REST_and_Hypermedia#POST|HTTP POST}}</font> | |||
==Get All Items as a JSON Array== | |||
Reading resources is conventionally implemented with <code>GET</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#GET|HTTP GET}} | |||
OpenAPI path specification: | |||
<syntaxhighlight lang='yaml'> | |||
paths: | |||
/pets: | |||
get: | |||
operationId: GetPets | |||
summary: Get all Pet instances the user has access to and that match the filter, if any. | |||
description: | | |||
GetPets returns all Pet instances that match the filter specified by the operation's parameters, if any. | |||
If no filtering criteria are provided, all Pet instances are returned. The method is safe and idempotent. | |||
parameters: | |||
- name: tags | |||
in: query | |||
description: Tags to filter Pet instances by. | |||
required: false | |||
schema: | |||
type: array | |||
items: | |||
type: string | |||
responses: | |||
200: | |||
description: Return all Pet instances the user has access to and that match the filter, as a list. | |||
content: | |||
application/json: | |||
schema: | |||
type: array | |||
items: | |||
$ref: '#/components/schemas/Pet' | |||
default: | |||
description: Unexpected error. | |||
content: | |||
application/json: | |||
schema: | |||
$ref: '#/components/schemas/Error' | |||
</syntaxhighlight> | |||
The following types are used: | |||
* <code>[[OpenAPI_Specification_Schemas#Pet|Pet]]</code> type. | |||
* <code>[[OpenAPI_Specification_Schemas#Error_Type|Error]]</code> type. | |||
Server implementation: | |||
<syntaxhighlight lang='go'> | |||
func (s *PetStoreServer) GetPets(ctx echo.Context, params petstore.GetPetsParams) error { | |||
var tags []string | |||
ptags := params.Tags | |||
// ensure that tags exist, and filter our those that are empty or blank strings ... | |||
tags = ... | |||
var pets []petstore.Pet | |||
s.RLock() | |||
defer s.RUnlock() | |||
for _, p := range s.pets { | |||
if len(tags) == 0 { | |||
pets = append(pets, *p) | |||
} else { | |||
// filter by tags | |||
if p.Tag != nil { | |||
for _, t := range tags { | |||
if *p.Tag == t { | |||
pets = append(pets, *p) | |||
} | |||
} | |||
} | |||
} | |||
} | |||
resp, err := json.Marshal(pets) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = ctx.Response().Write(resp) | |||
return err | |||
} | |||
</syntaxhighlight> | |||
Invocation: | |||
<syntaxhighlight lang='bash'> | |||
curl -H "Content-Type: application/json" -X GET http://localhost:30000/pets?tags=blue | |||
</syntaxhighlight> | |||
==Get One Item by ID== | |||
Reading resources is conventionally implemented with <code>GET</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#GET|HTTP GET}} | |||
OpenAPI path specification: | |||
<syntaxhighlight lang='yaml'> | |||
paths: | |||
/pets/{id}: | |||
get: | |||
operationId: GetPet | |||
summary: Get a Pet instance by ID | |||
description: | | |||
Return a single Pet instance that corresponds to the given ID, or nothing if the ID does not exist | |||
or the user does not have read privileges. | |||
parameters: | |||
- name: id | |||
in: path | |||
description: ID of Pet instance to fetch. | |||
required: true | |||
schema: | |||
type: string | |||
format: uuid | |||
responses: | |||
200: | |||
description: The Pet instance as JSON or nothing if no such Pet exists. | |||
content: | |||
application/json: | |||
schema: | |||
$ref: '#/components/schemas/Pet' | |||
404: | |||
description: No such ID exists. | |||
content: | |||
application/json: | |||
schema: | |||
$ref: '#/components/schemas/Error' | |||
default: | |||
description: unexpected error | |||
content: | |||
application/json: | |||
schema: | |||
$ref: '#/components/schemas/Error' | |||
</syntaxhighlight> | |||
The following types are used: | |||
* <code>[[OpenAPI_Specification_Schemas#Pet|Pet]]</code> type. | |||
* <code>[[OpenAPI_Specification_Schemas#Error_Type|Error]]</code> type. | |||
Server implementation: | |||
<syntaxhighlight lang='go'> | |||
func (s *PetStoreServer) GetPet(ctx echo.Context, id openapi_types.UUID) error { | |||
s.RLock() | |||
defer s.RUnlock() | |||
var pet *petstore.Pet | |||
for _, p := range s.pets { | |||
if p.ID == id { | |||
pet = p | |||
break | |||
} | |||
} | |||
if pet == nil { | |||
return echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String()) | |||
} | |||
resp, err := json.Marshal(*pet) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = ctx.Response().Write(resp) | |||
return err | |||
} | |||
</syntaxhighlight> | |||
Invocation: | |||
<syntaxhighlight lang='bash'> | |||
local id=$1 | |||
curl -v -H "Content-Type: application/json" -X GET "http://localhost:30000/pets/${id}" | |||
</syntaxhighlight> | |||
===<span id='TODO'></span>TODO Get Item by ID=== | |||
<font color=darkkhaki>If I use the <code>echo</code> server error facilities and return an <code>echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String())</code>, then the custom error type <code>[[OpenAPI_Specification_Schemas#Error_Type|Error]]</code> is not used, even if it's declared in the operation OpenAPI definition. Reconcile that.</font>. | |||
==Wholesale Update an Item with PUT== | |||
Wholesale update of a resource is conventionally implemented with <code>PUT</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#PUT|HTTP PUT}} | |||
==Partial Update with PATCH== | |||
Partial update of a resource is conventionally implemented with <code>PATCH</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#PATCH|HTTP PATCH}} | |||
==Delete an Item== | ==Delete an Item== | ||
Resource deletion is conventionally implemented with <code>DELETE</code> in REST architectures. | |||
{{Internal|REST_and_Hypermedia#DELETE|HTTP DELETE}} | |||
OpenAPI specification: | |||
<syntaxhighlight lang='yaml'> | |||
paths: | |||
/pets/{id}: | |||
delete: | |||
operationId: DeletePet | |||
summary: Delete the Pet instance that corresponds to the given ID. | |||
description: | | |||
Attempts to delete the Pet instance corresponding to the given ID. | |||
parameters: | |||
- name: id | |||
in: path | |||
description: ID of Pet instance to delete. | |||
required: true | |||
schema: | |||
type: string | |||
format: uuid | |||
responses: | |||
204: | |||
description: Standard response on successful deletion. | |||
404: | |||
description: No such ID exists. | |||
default: | |||
description: Unexpected error. | |||
content: | |||
application/json: | |||
schema: | |||
$ref: '#/components/schemas/Error' | |||
</syntaxhighlight> | |||
The following types are used: | |||
* <code>[[OpenAPI_Specification_Schemas#Error_Type|Error]]</code> type. | |||
Server implementation: | |||
<syntaxhighlight lang='go'> | |||
func (s *PetStoreServer) DeletePet(ctx echo.Context, id openapi_types.UUID) error { | |||
s.Lock() | |||
defer s.Unlock() | |||
_, exists := s.pets[id] | |||
if !exists { | |||
return echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String()) | |||
} | |||
delete(s.pets, id) | |||
return ctx.String(http.StatusNoContent, "no content") | |||
} | |||
</syntaxhighlight> | |||
Invocation: | |||
<syntaxhighlight lang='bash'> | |||
local id=$1 | |||
curl -v -H "Content-Type: application/json" -X DELETE "http://localhost:30000/pets/${id}" | |||
</syntaxhighlight> |
Latest revision as of 03:55, 27 January 2024
Internal
Overview
The top level paths
keyword introduce a map of paths, keyed by their path values:
[...] paths: /a: [...] /b: [...] /c: [...] [...]
Path
Each path name must start with a forward slash "/". The path is appended to the expanded URL from the server object url
field in order to construct the full URL. Path templating is allowed. Each path accepts zero or more of the available operations (get
, put
, post
, delete
, options
, head
, patch
, trace
) and parameters
, which is a list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at operation level but cannot be removed there.
/a: [...] summary: description: get: [...] # a GET /a interface method will be generated by server code generation put: [...] # a PUT /a interface method will be generated by server code generation post: [...] # a POST /a interface method will be generated by server code generation delete: [...] # a DELETE /a interface method will be generated by server code generation options: [...] # ... head: [...] patch: [...] trace: [...] parameters: [...] servers:
Server Code Generation for Path/Operation Combinations
For each path/operation combination, oapi-codegen
will generate a Go "Operation /Path" handler method in the ServerInterface
interface object. The name of the methods will be given by operationId
value, with the first character uppercased.
Path Templating
Path templating refers to the usage of curly braces {}
to mark a section of a URL path as replaceable using path parameters.
Operations
An operation represents a single HTTP operation on a path.
Valid operations:
get
put
post
delete
options
head
patch
trace
get|put|post|delete|options|head|patch|trace: summary: | A short description of the operation. operationId: GetPets description: '...' parameters: [...] responses: [...] tags: [...] requestBody: callbacks: security: servers: deprecated:
operationId
A unique string, among all operations described by this API, used to identify the operation. The operationId
value is case-sensitive. Tools and libraries may use operationId
to uniquely identify an operation, therefore, it is recommended to follow common programming naming conventions.
oapi-codegen
uses operationId
as a base for a various function and struct names in client and server generated code. All names get their first character upper-cased, making them exported, regardless of whether the first character of the operationId
is lower or upper case. For:
paths:
/pets:
get:
operationId: GetPets
[...]
oapi-codegen
generates:
[...]
// The interface specification for the client above.
type ClientInterface interface {
// GetPets request
GetPets(ctx context.Context, params *GetPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
}
[...]
// ServerInterface represents all server handlers.
type ServerInterface interface {
// (GET /pets)
GetPets(ctx echo.Context, params GetPetsParams) error
}
[...]
summary
Surfaces in the generated code, as the generated server interface method comment.
description
responses
The responses
field is required and lists all possible HTTP responses that may result from executing this operation.
Response
The element must contain at least one response code. The definition is not expected to cover all possible HTTP response codes, because they may not be known in advance. However, the definition should cover a successful operation response and any known errors. The default
map key may be used as a default response object for all HTTP codes that are not covered individually in the definition.
paths:
/pets:
get:
[...]
responses:
200:
description: Return all the pets the user has access to, as a list.
headers: [...]
links: [...]
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
default:
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
An empty response:
paths:
/a:
get:
[...]
responses:
200:
schema:
$ref: '#/components/schemas/Empty'
The Empty type is declared in the /components/schemas
as such:
headers
The header object follows the structure of a parameter object, with the exceptions that name
must not be specified, it is given in the corresponding headers map, and in
must not be specified, it is implicitly in header. headers
is a container that maps a header name to its definition. The header names are case insensitive. If a header is specified by the an extension, such as x-amazon-apigateway-integration, it has to be declared in the headers section for the corresponding response, otherwise a template error is generated. Responses can include custom headers, or headers that implement a protocol like CORS.
200:
[...]
headers:
Access-Control-Allow-Origin:
description: some description
schema:
type: string
Access-Control-Allow-Methods:
description: some description
schema:
type: string
Access-Control-Allow-Headers:
description: some description
schema:
type: string
requestBody
Also see:
tags
Each operation can be annotated with a list of tags. Tagged operations may be handled differently by tools and libraries. Optionally, each tag can get a description
and an externalDocs
in the global tags
section on the root level. The tag names here should match those used in operations. The tag order in the global tags section also controls the default sorting in the UI. It is possible to use a tag at operation level even if it is not specified on the root level.
tags:
- name: tag-a
description: Something that would shed light on tag-a semantics
externalDocs:
url: https://example.com/my-docs/tag-a.html
paths:
/a:
get:
tags:
- tag-a
- other-tag
Parameters
A unique parameter is defined by a combination of its name, defined as value of the name
field, and its location, defined as value of the in
field. The name
value is required and case sensitive. There are four possible parameter locations: "query", "header", "path", "cookie". An operation accepts multiple parameters, which should be specified as an array.
get: [...] parameters: - name: Color in: path|query|header|cookie description: required: true|false style: form schema: [...] deprecated: true|false allowEmptyValue: true|false - [...]
Parameter Locations
Path Parameters
A path parameter is declared as in: path
in the OpenAPI specification file, and is a URL fragment at the left side of the question mark in the URL. For "path" parameters, the parameter name must correspond to a template expression occurring in the path
field. The parameter value is actually part of the operation's URL. Also, the required
property is required and the value must be true
.
/query/{id}
paths:
/query/{id}:
get:
- name: id
in: path
required: true
[...]
Also see:
Query Parameters
A query parameter is declared as in: query
in the OpenAPI specification file, and it is an URL fragment that follows the question mark in the full URL.
allowEmptyValue
field is valid only for query parameters and allows sending a parameter with an empty value. The default value is false
. Use of this property is not recommended and it is likely to be removed in a later revision.
Also see:
Header Parameters
Header parameters are key value pairs that can be used to configure the behavior of the API.
Also see:
Cookie Parameters
Parameter Schema
paths:
/pets:
get:
parameters:
- name: tags
[...]
schema:
type: array
items:
type: string
Operation Declaration and Implementation Examples
The server code generation the following examples work in top of is done with:
Create a Resource Instance
Creation of new resources is conventionally implemented with POST
in REST architectures.
OpenAPI path specification:
paths:
/pets:
post:
operationId: CreatePet
summary: Create a new Pet instance.
description: |
CreatePet creates a new Pet instance, expecting all required state as arguments.
It generates an unique ID. The method is NOT idempotent.
requestBody:
description: The state of the Pet to be added to the store.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PetPayload'
responses:
201:
description: The state of a newly create instance, including the new ID.
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
default:
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
The following types are used:
PetPayload
type.Pet
type.Error
type.
Server implementation:
func (s *PetStoreServer) CreatePet(ctx echo.Context) error {
var petPayload petstore.PetPayload
err := json.NewDecoder(ctx.Request().Body).Decode(&petPayload)
if err != nil {
return err
}
// create and store the pet, then return the result, the ID will be generated internally by NewPet()
p := petstore.NewPet(petPayload)
s.Lock()
defer s.Unlock()
s.pets[p.ID] = p
resp, err := json.Marshal(p)
if err != nil {
return err
}
_, err = ctx.Response().Write(resp)
return err
}
Invocation:
curl -H "Content-Type: application/json" -X POST -d '{"name":"Fido", "age":2, "tag":"blue"}' http://localhost:30000/pets
TODO Create
The appropriate response should be 201. See HTTP 201 and the "Location" header may contain the URL of the new resource, see:
Get All Items as a JSON Array
Reading resources is conventionally implemented with GET
in REST architectures.
OpenAPI path specification:
paths:
/pets:
get:
operationId: GetPets
summary: Get all Pet instances the user has access to and that match the filter, if any.
description: |
GetPets returns all Pet instances that match the filter specified by the operation's parameters, if any.
If no filtering criteria are provided, all Pet instances are returned. The method is safe and idempotent.
parameters:
- name: tags
in: query
description: Tags to filter Pet instances by.
required: false
schema:
type: array
items:
type: string
responses:
200:
description: Return all Pet instances the user has access to and that match the filter, as a list.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
default:
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
The following types are used:
Server implementation:
func (s *PetStoreServer) GetPets(ctx echo.Context, params petstore.GetPetsParams) error {
var tags []string
ptags := params.Tags
// ensure that tags exist, and filter our those that are empty or blank strings ...
tags = ...
var pets []petstore.Pet
s.RLock()
defer s.RUnlock()
for _, p := range s.pets {
if len(tags) == 0 {
pets = append(pets, *p)
} else {
// filter by tags
if p.Tag != nil {
for _, t := range tags {
if *p.Tag == t {
pets = append(pets, *p)
}
}
}
}
}
resp, err := json.Marshal(pets)
if err != nil {
return err
}
_, err = ctx.Response().Write(resp)
return err
}
Invocation:
curl -H "Content-Type: application/json" -X GET http://localhost:30000/pets?tags=blue
Get One Item by ID
Reading resources is conventionally implemented with GET
in REST architectures.
OpenAPI path specification:
paths:
/pets/{id}:
get:
operationId: GetPet
summary: Get a Pet instance by ID
description: |
Return a single Pet instance that corresponds to the given ID, or nothing if the ID does not exist
or the user does not have read privileges.
parameters:
- name: id
in: path
description: ID of Pet instance to fetch.
required: true
schema:
type: string
format: uuid
responses:
200:
description: The Pet instance as JSON or nothing if no such Pet exists.
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
404:
description: No such ID exists.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
The following types are used:
Server implementation:
func (s *PetStoreServer) GetPet(ctx echo.Context, id openapi_types.UUID) error {
s.RLock()
defer s.RUnlock()
var pet *petstore.Pet
for _, p := range s.pets {
if p.ID == id {
pet = p
break
}
}
if pet == nil {
return echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String())
}
resp, err := json.Marshal(*pet)
if err != nil {
return err
}
_, err = ctx.Response().Write(resp)
return err
}
Invocation:
local id=$1
curl -v -H "Content-Type: application/json" -X GET "http://localhost:30000/pets/${id}"
TODO Get Item by ID
If I use the echo
server error facilities and return an echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String())
, then the custom error type Error
is not used, even if it's declared in the operation OpenAPI definition. Reconcile that..
Wholesale Update an Item with PUT
Wholesale update of a resource is conventionally implemented with PUT
in REST architectures.
Partial Update with PATCH
Partial update of a resource is conventionally implemented with PATCH
in REST architectures.
Delete an Item
Resource deletion is conventionally implemented with DELETE
in REST architectures.
OpenAPI specification:
paths:
/pets/{id}:
delete:
operationId: DeletePet
summary: Delete the Pet instance that corresponds to the given ID.
description: |
Attempts to delete the Pet instance corresponding to the given ID.
parameters:
- name: id
in: path
description: ID of Pet instance to delete.
required: true
schema:
type: string
format: uuid
responses:
204:
description: Standard response on successful deletion.
404:
description: No such ID exists.
default:
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
The following types are used:
Error
type.
Server implementation:
func (s *PetStoreServer) DeletePet(ctx echo.Context, id openapi_types.UUID) error {
s.Lock()
defer s.Unlock()
_, exists := s.pets[id]
if !exists {
return echo.NewHTTPError(http.StatusNotFound, "no such ID: "+id.String())
}
delete(s.pets, id)
return ctx.String(http.StatusNoContent, "no content")
}
Invocation:
local id=$1
curl -v -H "Content-Type: application/json" -X DELETE "http://localhost:30000/pets/${id}"