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) можна згенерувати TypeScript клієнт:
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, й на основі файлів специфікації генерувати серверну частину, документацію та клієнт.
Реалізація задачі на прикладі вебкаркасу 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, хоча б у pet-проєктах.Мені сподобалась їх вакансія Go Developer (Remote) бо вказана винагорода та є опис проєктів:
- Ryddm — сервіс, який допомагає молодим музикантам зібрати свою аудиторію, стати частиною спільноти та заробляти на своїй творчості.
- QuickCheckout — це нова система чекауту для власників онлайн-магазинів, яка полегшує процес оформлення замовлення мільйонам покупців з усього світу.
Також у вакансії описаний процес найму та відпустка.
Компанія Softcery фінансово підтримала мою мотивацію написати статтю про OpenAPI, яку ще спланував у 2021 році, бо Softcery також у проєктах генерують TypeScript клієнт.
Ви можете відгукнутись на вакансію на сторінці вакансії, через форму на сайті careers.softcery.com, познайомитись через calendly.com/softcery/introduction або написати напряму в LinkedIn Elijah Atamas.
Епілог
Маю надію, що вам вдалось осилити статтю, і вона буде вам корисною. Репозиторії:Якщо ви осилили «Тестовий запуск генерації документації», то цього достатньо.
17 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів