Go Swagger: автогенерація клієнту та документації
Повна версія
Go Swagger: автогенерація клієнту та документації
Підписуйтеся наTelegram-канал «DOU #tech», щоб не пропустити нові технічні статті.
Привіт! За минулий 2021 рік я встиг попрацювати з gRPC, gRPC-Web, OpenAPI (специфікація, початково відома як Swagger) та розібратись з gRPC-Gateway. Було б добре написати окрему статтю з порівнянням цих інструментів, але поки лише можу проконсультувати, бо пояснити особливості усно — це година-дві, а написати статтю з порівнянням — десять-двадцять годин.
А ще за 2021 рік в мене накопились теми, які я б хотів оформити в статті, а тому, якщо у вас є час та бажання пописати технічні статті на тематику Go, то пишіть мені, допоможу вам, це буде гарним вкладом в розвиток спільноти GolangUA. Спеціально для популяризації Go в Україні, Редакція DOU запустила суб’єктивну програму лояльності для авторів технічних статей з Go ПишуНаDOU де будуть дарувати іграшку «Гофер», але це тільки мої суб’єктивні очікування.
Про свої досягнення та фантазії стосовно програм лояльності розповів, тож варто починати технічну статтю.
Стаття буде класичної структури, а саме інструкція: від написання анотацій для API-методів на сервері до генерації документації та генерації клієнту, а в кінці статті буде посилання на репозиторії з кодом та усіма скриптами для запуску генерацій в Makefile.
OpenAPI (Swagger)
На першому проєкті, де я дізнався про Swagger, він використовувався тільки для
генерації документації з анотацій до методів API. Це був 2015 рік, я працював
з PHP.
Тоді подібна генерація для мене виглядала чимось надзвичайним і протягом
довгого часу я сприймав Swagger тільки як генератор документації.
Коли в 2021 році, вже на проєкті з Go, я побачив Swagger знову, то вже мав досвід з gRPC, а тому вирішив пошукати, чи є можливість згенерувати клієнт для браузера, за аналогією з gRPC-Web, як виявилось, можливість є.
Swagger, як і gRPC, можна використовувати для кодогенерації міжсервісної клієнт-серверної взаємодії, хоча на мою думку, використання gRPC — краще рішення для мікросервісів, ніж OpenAPI (Swagger).
А ось для створення MVP (прототипів), де є взаємодія між браузером та сервером, краще OpenAPI. З OpenAPI почати працювати простіше, ніж з gRPC- Gateway. А згенерований OpenAPI клієнт поки має значно менший розмір, ніж клієнт gRPC-Web, який залежить від важкої бібліотеки protobufjs.
Далі в статті будуть зустрічатись обидві назви, сучасна OpenAPI та історична Swagger, за бажанням можете відкрити у фоновій вкладці статтю про OpenAPI (з Вікіпедії) та почитати пізніше.
Опис задачі
Зазвичай, в книгах наводять приклади банківських систем, але ми для прикладу візьмемо відомий для аудиторії DOU, пошук роботи і на його прикладі зробимо API:
- Створення компаній та вакансій.
- Перегляд.
- Редагування.
- Перегляд списку.
Тестовий запуск генерації документації
У Swagger відсутня залежність від вебкаркасів, а тому протестую генерацію документації без запуску HTTP-сервера.
Для початку потрібно створити проєкт та описати анотації:
mkdir -p ~/go/src/gitlab.com/go-yp/go-swagger-typescript-client-example
cd ~/go/src/gitlab.com/go-yp/go-swagger-typescript-client-example
go mod init
go: creating new go.mod: module gitlab.com/go-yp/go-swagger-typescript-client-example
package main
// Company meta from https://jobs.dou.ua/register/
type Company struct {
ID int `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
SiteURL string `json:"site_url"`
EmployeeCount int `json:"employee_count"`
}
type ErrorResponse struct {
Message string `json:"message"`
}
// Companies godoc
// @Summary Show companies
// @Tags Companies
// @Accept json
// @Produce json
// @Param page query string false "Page"
// @Param size query string false "Size"
// @Param search query string false "Search by name and description"
// @Success 200 {object} []Company
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/hire/companies [get]
func Companies() {
// NOP
}
type ResponseNew struct {
ID int `json:"id"`
}
type CompaniesNewRequest struct {
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
SiteURL string `json:"site_url"`
EmployeeCount int `json:"employee_count"`
}
// CompaniesNew godoc
// @Summary Add company
// @Tags Companies
// @Accept json
// @Produce json
// @Param body body CompaniesNewRequest true "CompaniesNewRequest"
// @Success 200 {object} ResponseNew
// @Failure 401 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/hire/companies [post]
func CompaniesNew() {
// NOP
}
// CompaniesGetByID godoc
// @Summary Get one company by id
// @Tags Companies
// @Accept json
// @Produce json
// @Param id path integer true "id"
// @Success 200 {object} Company
// @Failure 401 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/hire/companies/{id} [get]
func CompaniesGetByID() {
// NOP
}
type EmptyResponse = struct{}
type CompaniesUpdateRequest struct {
Name string `json:"name"`
Description string `json:"description"`
SiteURL string `json:"site_url"`
EmployeeCount int `json:"employee_count"`
}
// CompaniesUpdate godoc
// @Summary Update company
// @Tags Companies
// @Accept json
// @Produce json
// @Param id path integer true "id"
// @Param body body CompaniesUpdateRequest true "CompaniesUpdateRequest"
// @Success 200 {object} EmptyResponse
// @Failure 401 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /api/v1/hire/companies/{id} [post]
func CompaniesUpdate() {
// NOP
}
func main() {
// NOP
}
tree .
├── examples
│ └── 001_annotations_only
│ └── main.go
├── go.mod
├── LICENSE
└── README.md
2 directories, 4 files
Якщо уважно прочитати анотації, то майже у всіх зрозуміле призначення. Єдина анотація, до якої мають бути питання, це @Param.
@Param має формат, розділений пробілами. Приклади які бачили раніше, для кращого розуміння можна представити в табличному виді:
HTTP methods| @Param| param name| param type| data type| is mandatory?|
comment| attribute(optional)
---|---|---|---|---|---|---|---
GET| @Param| page| query| string| false| «Page»|
GET| @Param| size| query| string| false| «Size»|
POST| @Param| body| body| CompaniesNewRequest| true| «CompaniesNewRequest»|
GET, POST| @Param| id| path| integer| true| «id»|
POST| @Param| body| body| CompaniesUpdateRequest| true|
«CompaniesUpdateRequest»|
Можливі варіанти для Param Type (взяті з документації інструмента, який буду використовувати для генерації документації):
- query
- path
- header
- body
- formData
Можливі варіанти для Data Type (взяті з документації інструмента, який буду використовувати для генерації документації):
- string (string)
- integer (int, uint, uint32, uint64)
- number (float32)
- boolean (bool)
- user defined struct (CompaniesNewRequest, CompaniesUpdateRequest)
Для генерації документації буду використовувати github.com/swaggo/swag, бо має гарну документацію з прикладами використання для популярних вебкаркасів та додаткову теку example.
Для встановлення та використання swag створив Makefile :
install-swag:
go get -u github.com/swaggo/swag/cmd/swag
generate-swagger-docs-001:
swag init -g ./examples/001_annotations_only/main.go --output ./docs/examples/001_annotations_only/apidocs
make install-swag
make generate-swagger-docs-001
2022/01/08 03:15:30 Generate swagger docs....
2022/01/08 03:15:30 Generate general API Info, search dir:./
2022/01/08 03:15:30 Generating main.Company
2022/01/08 03:15:30 Generating main.ErrorResponse
2022/01/08 03:15:30 Generating main.CompaniesNewRequest
2022/01/08 03:15:30 Generating main.ResponseNew
2022/01/08 03:15:30 Generating main.CompaniesUpdateRequest
2022/01/08 03:15:30 Generating main.EmptyResponse
2022/01/08 03:15:30 create docs.go at docs/examples/001_annotations_only/apidocs/docs.go
2022/01/08 03:15:30 create swagger.json at docs/examples/001_annotations_only/apidocs/swagger.json
2022/01/08 03:15:30 create swagger.yaml at docs/examples/001_annotations_only/apidocs/swagger.yaml
tree .
├── docs
│ └── examples
│ └── 001_annotations_only
│ └── apidocs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── examples
│ └── 001_annotations_only
│ └── main.go
├── go.mod
├── LICENSE
├── Makefile
└── README.md
6 directories, 8 files
Для перегляду документації в браузері достатньо мати swagger.json (або swagger.yaml ):
docker run -p 80:8080 \
-e SWAGGER_JSON=/www/swagger.json \
-v ${PWD}/docs/examples/001_annotations_only/apidocs:/www \
swaggerapi/swagger-ui
browse http://localhost
swagger.json swagger.yaml
generate-swagger-ts-client-001:
docker run --rm \
-v $(shell pwd)/docs/examples/001_annotations_only:/local openapitools/openapi-generator-cli generate \
--skip-validate-spec \
-g typescript-fetch \
-i /local/apidocs/swagger.json \
-o /local/client/gen
make generate-swagger-ts-client-001
tree ./docs/examples/001_annotations_only
./docs/examples/001_annotations_only
├── apidocs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
└── client
└── gen
├── apis
│ ├── CompaniesApi.ts
│ └── index.ts
├── index.ts
├── models
│ ├── index.ts
│ ├── MainCompaniesNewRequest.ts
│ ├── MainCompaniesUpdateRequest.ts
│ ├── MainCompany.ts
│ ├── MainErrorResponse.ts
│ └── MainResponseNew.ts
└── runtime.ts
5 directories, 13 files
Якщо ви працювали зі Swagger раніше, то цієї інформації вам має бути достатньо для кодогенерації TypeScript-клієнту.
Список доступних генераторів є в репозиторії організації OpenAPITools openapi-generator/tree/master/docs/generators.
Виведу тільки список генераторів, які стосуються JavaScript, TypeScript та Go:
- javascript
- javascript-apollo
- javascript-closure-angular
- javascript-flowtyped
- typescript
- typescript-node
- typescript-angular
- typescript-aurelia
- typescript-axios
- typescript-fetch
- typescript-inversify
- typescript-jquery
- typescript-nestjs
- typescript-redux-query
- typescript-rxjs
- go
- go-echo-server
- go-gin-server
- go-server
Отже, працюючи зі Swagger, у вас є варіанти:
-
Перший, який описується в цій статті, писати анотації, на основі яких генерувати файли специфікації swagger.json та swagger.yaml , а далі, на основі файлів специфікації, генерувати документацію та клієнт.
-
Другий: ви можете писати файли специфікації swagger.json та swagger.yaml в улюбленій IDE або через спеціальний редактор editor.swagger.io, й на основі файлів специфікації генерувати серверну частину, документацію та клієнт.
-
/docs/examples/001_annotations_only
-
/apidocs
-
docs.go
- swagger.json
- swagger.yaml
-
/client/gen
-
/apis
- index.ts
-
CompaniesApi.ts
- /models
-
index.ts
- MainCompaniesNewRequest.ts
- MainCompaniesUpdateRequest.ts
- MainCompany.ts
- MainErrorResponse.ts
- MainResponseNew.ts
- index.ts
-
runtime.ts
-
Реалізація задачі на прикладі вебкаркасу Gin
Щоб краще розібратись, то пропоную реалізувати заглушки для задачі, яку описав раніше.
Для цього буду використовувати вебкаркас Gin та вже знайомий swag.
В інструмента swag є підтримка популярних каркасів:
- gin (офіційна підтримка від swaggo);
- echo (офіційна підтримка від swaggo);
- buffalo (офіційна підтримка від swaggo);
- net/http (офіційна підтримка від swaggo);
- flamingo;
- fiber;
- atreugo.
Вибрав Gin, бо ім’я співзвучне з відомим сервісом для пошуку роботи achievki.io, а також, бо сподобалась обробка параметрів, маршрутизація та документація.
План такий: в main.go опишу маршрутизацію, додам API методи та анотації, згенерую документацію та клієнт, ініціалізую npm-проєкт, де підключу згенерований OpenAPI клієнт.
cat main.go
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
// @TODO uncomment after run "swag init ..."
// _ "./docs/examples/002_extends/apidocs" // docs is generated by Swag CLI, you have to import it.
)
func TodoHandlerFunc(ctx *gin.Context) {
// @TODO
}
func TodoAuthMiddleware(ctx *gin.Context) {
// @TODO
}
func main() {
r := gin.Default()
// employer
r.
Use(TodoAuthMiddleware).
GET("/api/v1/hire/companies", TodoHandlerFunc). // get list
POST("/api/v1/hire/companies", TodoHandlerFunc). // create one
GET("/api/v1/hire/companies/:company_id", TodoHandlerFunc). // get one
POST("/api/v1/hire/companies/:company_id", TodoHandlerFunc). // update one
Use(TodoAuthMiddleware).
GET("/api/v1/hire/companies/:company_id/vacancies", TodoHandlerFunc). // get list
POST("/api/v1/hire/companies/:company_id/vacancies", TodoHandlerFunc). // create one
GET("/api/v1/hire/companies/:company_id/vacancies/:vacancy_id", TodoHandlerFunc). // get one
POST("/api/v1/hire/companies/:company_id/vacancies/:vacancy_id", TodoHandlerFunc) // update one
// employee
r.
GET("/api/v1/companies", TodoHandlerFunc). // get list
GET("/api/v1/vacancies", TodoHandlerFunc). // get list
StaticFile("/vacancies", "./public/vacancies.html"). // view page
StaticFile("/companies", "./public/companies.html") // view page
if gin.IsDebugging() {
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
go run main.go
[GIN-debug] Listening and serving HTTP on :8080
Я буду рухатись такими ж кроками, якими зазвичай розробляю в комфортному темпі (ці кроки маленькі). Опишу контролери та використаю їх в файлі main.go :
tree ./controllers/
./controllers/
├── employee.go
└── employer.go
0 directories, 2 files
cat ./controllers/employer.go ./controllers/employer.go/
package controllers
import (
"database/sql"
"github.com/gin-gonic/gin"
)
type EmployerController struct {
connection *sql.DB
}
func NewEmployerController(connection *sql.DB) *EmployerController {
return &EmployerController{connection: connection}
}
func (c *EmployerController) Companies(ctx *gin.Context) {}
func (c *EmployerController) CompaniesNew(ctx *gin.Context) {}
func (c *EmployerController) CompaniesGetOne(ctx *gin.Context) {}
func (c *EmployerController) CompaniesUpdate(ctx *gin.Context) {}
func (c *EmployerController) Vacancies(ctx *gin.Context) {}
func (c *EmployerController) VacanciesNew(ctx *gin.Context) {}
func (c *EmployerController) VacanciesGetOne(ctx *gin.Context) {}
func (c *EmployerController) VacanciesUpdate(ctx *gin.Context) {}
package controllers
import (
"database/sql"
"github.com/gin-gonic/gin"
)
type EmployeeController struct {
connection *sql.DB
}
func NewEmployeeController(connection *sql.DB) *EmployeeController {
return &EmployeeController{connection: connection}
}
func (c *EmployeeController) Companies(ctx *gin.Context) {}
func (c *EmployeeController) Vacancies(ctx *gin.Context) {}
cat main.go
package main
import (
"database/sql"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"gitlab.com/go-yp/go-swagger-typescript-client-example/controllers"
"gitlab.com/go-yp/go-swagger-typescript-client-example/middlewares"
// @TODO uncomment after run "swag init ..."
// "./docs/examples/002_extends/apidocs" // docs is generated by Swag CLI, you have to import it.
)
func main() {
var connection *sql.DB = nil // @TODO
defer func() {
if connection != nil {
connection.Close()
}
}()
var (
erController = controllers.NewEmployerController(connection)
eeController = controllers.NewEmployeeController(connection)
)
r := gin.Default()
// employer
r.
Use(middlewares.Auth).
GET("/api/v1/hire/companies", erController.Companies). // get list
POST("/api/v1/hire/companies", erController.CompaniesNew). // create one
GET("/api/v1/hire/companies/:company_id", erController.CompaniesGetOne). // get one
POST("/api/v1/hire/companies/:company_id", erController.CompaniesUpdate). // update one
Use(middlewares.Auth).
GET("/api/v1/hire/companies/:company_id/vacancies", erController.Vacancies). // get list
POST("/api/v1/hire/companies/:company_id/vacancies", erController.VacanciesNew). // create one
GET("/api/v1/hire/companies/:company_id/vacancies/:vacancy_id", erController.VacanciesGetOne). // get one
POST("/api/v1/hire/companies/:company_id/vacancies/:vacancy_id", erController.VacanciesUpdate) // update one
// employee
r.
GET("/api/v1/companies", eeController.Companies). // get list
GET("/api/v1/vacancies", eeController.Vacancies). // get list
StaticFile("/vacancies", "./public/vacancies.html"). // view page
StaticFile("/companies", "./public/companies.html") // view page
if gin.IsDebugging() {
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
go run main.go
[GIN-debug] Listening and serving HTTP on :8080
Додам анотації до усіх API методів, можете переглянути за посиланнями /controllers/employer.go та /controllers/employee.go, але в статті наведу тільки парочку, бо об’ємні й повторюють анотації з попередніх прикладів.
package controllers
import (
"database/sql"
"github.com/gin-gonic/gin"
"gitlab.com/go-yp/go-swagger-typescript-client-example/middlewares"
"gitlab.com/go-yp/go-swagger-typescript-client-example/models"
"net/http"
)
// ...
type EmployerCompaniesQuery struct {
Page string `form:"page"`
Size string `form:"size"`
}
// Companies godoc
// @Summary Show companies
// @Tags Employer, Companies
// @Accept json
// @Produce json
// @Param page query string false "Page"
// @Param size query string false "Size"
// @Success 200 {object} []models.Company
// @Failure 400 {object} models.ErrorResponse
// @Failure 401 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Router /api/v1/hire/companies [get]
func (c *EmployerController) Companies(ctx *gin.Context) {
var user, ok = middlewares.User(ctx)
_ = user
_ = ok
if false && !ok {
ctx.JSON(http.StatusUnauthorized, &models.ErrorResponse{
Message: "Unauthorized",
})
return
}
var query EmployerCompaniesQuery
if err := ctx.ShouldBindQuery(&query); err != nil {
ctx.JSON(http.StatusBadRequest, &models.ErrorResponse{
Message: err.Error(),
})
return
}
_ = query.Page
_ = query.Size
// ... logic
ctx.JSON(http.StatusInternalServerError, &models.ErrorResponse{
Message: "Unimplemented",
})
}
// ...
type VacancyURI struct {
CompanyID int `uri:"company_id" binding:"required"`
VacancyID int `uri:"vacancy_id" binding:"required"`
}
// VacanciesUpdate godoc
// @Summary Update vacancy
// @Tags Employer, Vacancies
// @Accept json
// @Produce json
// @Param company_id path integer true "Company ID"
// @Param vacancy_id path integer true "Vacancy ID"
// @Param body body models.VacanciesUpdateRequest true "VacanciesUpdateRequest"
// @Success 200 {object} models.Vacancy
// @Failure 400 {object} models.ErrorResponse
// @Failure 401 {object} models.ErrorResponse
// @Failure 404 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Router /api/v1/hire/companies/{company_id}/vacancies/{vacancy_id} [post]
func (c *EmployerController) VacanciesUpdate(ctx *gin.Context) {
var user, ok = middlewares.User(ctx)
_ = user
_ = ok
if false && !ok {
ctx.JSON(http.StatusUnauthorized, &models.ErrorResponse{
Message: "Unauthorized",
})
return
}
var uri VacancyURI
if err := ctx.ShouldBindUri(&uri); err != nil {
ctx.JSON(http.StatusBadRequest, &models.ErrorResponse{
Message: err.Error(),
})
return
}
_ = uri.CompanyID
_ = uri.VacancyID
var vacancyRequest models.VacanciesUpdateRequest
if err := ctx.ShouldBindJSON(&vacancyRequest); err != nil {
ctx.JSON(http.StatusBadRequest, &models.ErrorResponse{
Message: err.Error(),
})
return
}
// ... logic
ctx.JSON(http.StatusInternalServerError, &models.ErrorResponse{
Message: "Unimplemented",
})
}
Тепер, коли вже є анотації, то можемо згенерувати swagger.json , swagger.yaml та docs.go.
generate-swagger-docs-002:
swag init -g main.go --output ./docs/examples/002_extends/apidocs --exclude ./examples/001_annotations_only
make generate-swagger-docs-002
tree ./docs/examples/002_extends/apidocs
./docs/examples/002_extends/apidocs
├── docs.go
├── swagger.json
└── swagger.yaml
0 directories, 3 files
Щоб документація стала доступною при запуску Go серверу, потрібно підключити файл docs.go в main.go:
import (
// ...
_ "gitlab.com/go-yp/go-swagger-typescript-client-example/docs/examples/002_extends/apidocs" // docs is generated by Swag CLI, you have to import it.
// ...
)
package main
import (
"database/sql"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"gitlab.com/go-yp/go-swagger-typescript-client-example/controllers"
_ "gitlab.com/go-yp/go-swagger-typescript-client-example/docs/examples/002_extends/apidocs" // docs is generated by Swag CLI, you have to import it.
"gitlab.com/go-yp/go-swagger-typescript-client-example/middlewares"
)
func main() {
// ... same
r := gin.Default()
// employer
// ... same
// employee
// ... same
if gin.IsDebugging() {
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
go run main.go
[GIN-debug] Listening and serving HTTP on :8080
browse http://localhost:8080/swagger/index.html
Єдине, що залишилось, це знову згенерувати TypeScript клієнт й написати приклад його використання:
generate-swagger-ts-client-002:
# https://openapi-generator.tech/docs/installation/
# openapi-generator-cli generate -i ./apidocs/swagger.yaml --generator-name typescript-fetch -o gen/api
docker run --rm \
-v $(shell pwd):/local openapitools/openapi-generator-cli generate \
--skip-validate-spec \
-g typescript-fetch \
-i /local/docs/examples/002_extends/apidocs/swagger.json \
-o /local/client/gen
make generate-swagger-ts-client-002
tree ./client/ -h
./client/
└── [4.0K] gen
├── [4.0K] apis
│ ├── [8.2K] CompaniesApi.ts
│ ├── [3.3K] EmployeeApi.ts
│ ├── [ 15K] EmployerApi.ts
│ ├── [ 168] index.ts
│ └── [9.7K] VacanciesApi.ts
├── [ 119] index.ts
├── [4.0K] models
│ ├── [ 459] index.ts
│ ├── [2.3K] ModelsCompaniesNewRequest.ts
│ ├── [2.1K] ModelsCompaniesUpdateRequest.ts
│ ├── [2.3K] ModelsCompany.ts
│ ├── [1.8K] ModelsEmployeeVacancyCompany.ts
│ ├── [2.3K] ModelsEmployeeVacancy.ts
│ ├── [1.3K] ModelsErrorResponse.ts
│ ├── [1.3K] ModelsResponseNew.ts
│ ├── [1.6K] ModelsVacanciesNewRequest.ts
│ ├── [1.6K] ModelsVacanciesUpdateRequest.ts
│ └── [1.6K] ModelsVacancy.ts
└── [ 10K] runtime.ts
3 directories, 18 files
Для мене було в новинку, що клієнти згрупувались по тегам:
- CompaniesApi.ts
- EmployeeApi.ts
- EmployerApi.ts
- VacanciesApi.ts
Тепер напишу два тестових приклади, де підключу ці клієнти, один для роботи з компаніями , а другий для роботи з вакансіями :
cat ./client/companies-app.ts
import {CompaniesApi, Configuration, ModelsCompany} from "./gen";
const companiesApiClient = new CompaniesApi(new Configuration({
basePath: "http://localhost:8080",
}));
companiesApiClient.apiV1CompaniesGet()
.then(function (companies: ModelsCompany[]) {
for (const company of companies) {
console.log(
company.id,
company.slug,
company.name,
company.description,
company.siteUrl,
company.employeeCount,
);
}
})
.catch(console.error)
cat ./client/vacancies-app.ts
import {VacanciesApi, Configuration, ModelsEmployeeVacancy} from "./gen";
const vacanciesApiClient = new VacanciesApi(new Configuration({
basePath: "http://localhost:8080",
}));
vacanciesApiClient.apiV1VacanciesGet()
.then(function (vacancies: ModelsEmployeeVacancy[]) {
for (const vacancy of vacancies) {
console.log(
vacancy.id,
vacancy.title,
vacancy.description,
vacancy.company.id,
vacancy.company.slug,
vacancy.company.name,
);
}
})
.catch(console.error)
Тепер у нас є повноцінний клієнт з усіма моделями.
Особливості swag або читайте документацію
У swag — чудова документація, основні можливості описані в CLI, але якщо будете використовувати в розробці, то прочитайте весь файл README.
По аналогії з go fmt , є swag fmt для форматування анотацій.
Також варто звернути увагу, коли в анотаціях використовуються типи зі стандартної бібліотеки Go, на зразок json.Number чи json.RawMessage , тоді потрібний флаг swag init —parseInternal , а якщо тип з залежностей на зразок gin.H{} , тоді swag init —parseDependency. Якщо генерація відбувається занадто довго, то ймовірно, вам треба налаштувати флаг swag init —parseDepth або ж додати частину тек в swag init —exclude.
У статті наводиться генерація клієнту через docker openapitools/openapi- generator-cli generate , але є й інші, приклади через npm i @openapitools/openapi-generator-cli.
Звісно, у OpenAPI є мінуси. Один з низ: після внесення змін до коду, анотації можуть забути оновити, й тоді документація й клієнт будуть відставати.
Go вакансії з OpenAPI для початківців
Softcery Go розробника
Go Developer (Remote)
- Ryddm — сервіс, який допомагає молодим музикантам зібрати свою аудиторію, стати частиною спільноти та заробляти на своїй творчості.
- QuickCheckout — це нова система чекауту для власників онлайн-магазинів, яка полегшує процес оформлення замовлення мільйонам покупців з усього світу.
Також у вакансії описаний процес найму та відпустка.
Компанія Softcery фінансово підтримала мою мотивацію написати статтю про OpenAPI, яку ще спланував у 2021 році, бо Softcery також у проєктах генерують TypeScript клієнт.
Ви можете відгукнутись на вакансію на сторінці вакансії, через форму на сайті careers.softcery.com, познайомитись через calendly.com/softcery/introduction або написати напряму в LinkedIn Elijah Atamas.
Епілог
- gitlab.com/go-yp/go-swagger-typescript-client-example
- gitlab.com/go-yp/go-openapi-crud-example
Якщо ви осилили «Тестовий запуск генерації документації», то цього достатньо.
Сподобалась стаття? Натискай «Подобається» внизу. Це допоможе автору виграти подарунок у програмі #ПишуНаDOU