Several of our projects require back-end as well as front-end work. If a client wants us to create RESTful APIs, we start by using the OpenAPI 3 specification. We write the server code using a tool called oapi-codegen
. This article explains how we do it and add special tags to parts of our Go code, such as validate
tags.
The basic API specification
A lot of articles begin by exploring the history of the tools they discuss. Let's skip the traditional introduction and get started. At first, the API specification can be summarized as follows:
openapi: "3.0.0"
info:
version: 1.0.0
title: API
servers:
- url: http://localhost:8080/api/v1/something
paths:
/:
post:
tags: ["createSomething"]
operationId: createSomething
parameters:
- $ref: '#/components/parameters/xForwardedFor'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSomethingRequest'
responses:
'200':
description: uuid of the created entity
content:
application/json:
schema:
$ref: '#/components/schemas/UUIDResponse'
'401':
description: unauthorized
'403':
description: forbidden
'404':
description: not found
'500':
description: internal server error
components:
schemas:
CreateSomethingRequest:
type: object
required:
- something
properties:
something:
type: number
example: 1
x-oapi-codegen-extra-tags:
validate: gte=0
UUIDResponse:
type: object
required:
- uuid
properties:
uuid:
type: string
format: uuid
parameters:
xForwardedFor:
in: header
name: X-Forwarded-For
schema:
type: string
required: true
x-oapi-codegen-extra-tags:
validate: ipv4
This API consists of 1 POST
request. As a request body, it has the CreateSomethingRequest
schema. We also have a header called xForwardedFor
. If you look at the definition, CreateSomethingRequest.something
and xForwardedFor
properties have a custom property called x-oapi-codegen-extra-flag
. This allows you to add extra tags to your generated struct fields. As all request bodies require validation, we chose validate
.
Now let's generate the code...
$ oapi-codegen -package main -generate types api/api.yml > openapi_types.gen.go
The generated code should like this:
// CreateSomethingRequest defines model for CreateSomethingRequest.
type CreateSomethingRequest struct {
Something float32 `json:"something" validate:"gte=0"`
}
...
// CreateSomethingParams defines parameters for CreateSomething.
type CreateSomethingParams struct {
XForwardedFor XForwardedFor `json:"X-Forwarded-For" validate:"ipv4"`
}
It's great to see our fields have validate
tags. Now we can begin the validation process!
var (
_ ServerInterface = HTTPServer{}
)
type HTTPServer struct {
...
}
func (h HTTPServer) CreateSomething(w http.ResponseWriter, r *http.Request, params CreateSomethingParams) {
validate := validator.New()
err := validate.Struct(params)
if err != nil {
errValidation := err.(validator.ValidationErrors)
// your implementation
}
}
The final result should look similar to what you see here, even though this is just an illustration.