In this article I would like to tell you about how you can quickly and easily make a web server in the Golang language with documentation for it. And about what are the approaches and tools for their implementation
Today we will analyze these ready-made tools:
Swagger codegen
Let's start with swagger-api / swagger-codegen provided by swagger.io . Swagger Codegen simplifies the build process by creating server stubs and client SDKs for any API defined in the OpenAPI specification (formerly known as Swagger), all you have to do is implement your API.
Below is an example of our swagger file and its visual appearance in the Swagger Editor, where we can generate our implementations for one thing.
swagger.json{
"swagger" : "2.0",
"info" : {
"version" : "1.0.0",
"title" : "Swagger Petstore"
},
"host" : "localhost",
"basePath" : "/v1",
"tags" : [ {
"name" : "pet"
} ],
"schemes" : [ "https", "http" ],
"paths" : {
"/pet" : {
"post" : {
"tags" : [ "pet" ],
"summary" : "Add a new pet to the store",
"operationId" : "addPet",
"consumes" : [ "application/json" ],
"produces" : [ "application/json" ],
"parameters" : [ {
"in" : "body",
"name" : "body",
"description" : "Pet object that needs to be added to the store",
"required" : true,
"schema" : {
"$ref" : "#/definitions/Pet"
}
} ],
"responses" : {
"405" : {
"description" : "Invalid input"
}
}
}
}
},
"definitions" : {
"Category" : {
"type" : "object",
"properties" : {
"id" : {
"type" : "integer",
"format" : "int64"
},
"name" : {
"type" : "string"
}
}
},
"Tag" : {
"type" : "object",
"properties" : {
"id" : {
"type" : "integer",
"format" : "int64"
},
"name" : {
"type" : "string"
}
}
},
"Pet" : {
"type" : "object",
"required" : [ "name", "photoUrls" ],
"properties" : {
"id" : {
"type" : "integer",
"format" : "int64"
},
"category" : {
"$ref" : "#/definitions/Category"
},
"name" : {
"type" : "string",
"example" : "doggie"
},
"photoUrls" : {
"type" : "array",
"items" : {
"type" : "string"
}
},
"tags" : {
"type" : "array",
"items" : {
"$ref" : "#/definitions/Tag"
}
},
"status" : {
"type" : "string",
"description" : "pet status in the store",
"enum" : [ "available", "pending", "sold" ]
}
}
}
}
}
Visual view in Swagger UI Export from Swagger Editor The client and server have identical structure objects. Example file go-client-generated/model_pet.go
and go-server-server-generated/go/model_pet.go
.
Pet structure model in client and server
package swagger
type Pet struct {
Id int64 `json:"id,omitempty"`
Category *Category `json:"category,omitempty"`
Name string `json:"name"`
PhotoUrls []string `json:"photoUrls"`
Tags []Tag `json:"tags,omitempty"`
Status string `json:"status,omitempty"`
}
The client also contains methods for sending requests, and the server endpionts and stubs for processing them.
, . , .
go-swagger
β β βgo-swagger/go-swagger. go API- Swagger API: , .
- Swagger
- Swagger
- , jsonschema swagger,
- swagger go
- swagger
- ,
:
$ swagger generate server -f ./swagger.json
, , . .
β go-swagger-service/restapi/configure_swagger_petstore.go
. : handler', middleware, , , .
package restapi
import (
"crypto/tls"
"net/http"
errors "github.com/go-openapi/errors"
runtime "github.com/go-openapi/runtime"
middleware "github.com/go-openapi/runtime/middleware"
"go-generator/go-swagger-service/restapi/operations"
"go-generator/go-swagger-service/restapi/operations/pet"
)
func configureFlags(api *operations.SwaggerPetstoreAPI) {
}
func configureAPI(api *operations.SwaggerPetstoreAPI) http.Handler {
api.ServeError = errors.ServeError
api.JSONConsumer = runtime.JSONConsumer()
api.JSONProducer = runtime.JSONProducer()
if api.PetAddPetHandler == nil {
api.PetAddPetHandler = pet.AddPetHandlerFunc(func(params pet.AddPetParams) middleware.Responder {
return middleware.NotImplemented("operation pet.AddPet has not yet been implemented")
})
}
api.ServerShutdown = func() {}
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
func configureTLS(tlsConfig *tls.Config) {
}
func configureServer(s *http.Server, scheme, addr string) {
}
func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
func setupGlobalMiddleware(handler http.Handler) http.Handler {
return handler
}
. 422
.
swagger . enum
, required
, type
, maximum
.
Pet
package models
import (
"encoding/json"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
type Pet struct {
Category *Category `json:"category,omitempty"`
ID int64 `json:"id,omitempty"`
Name *string `json:"name"`
PhotoUrls []string `json:"photoUrls"`
Status string `json:"status,omitempty"`
Tags []*Tag `json:"tags"`
}
func (m *Pet) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCategory(formats); err != nil {
res = append(res, err)
}
if err := m.validateName(formats); err != nil {
res = append(res, err)
}
if err := m.validatePhotoUrls(formats); err != nil {
res = append(res, err)
}
if err := m.validateStatus(formats); err != nil {
res = append(res, err)
}
if err := m.validateTags(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Pet) validateCategory(formats strfmt.Registry) error {
if swag.IsZero(m.Category) {
return nil
}
if m.Category != nil {
if err := m.Category.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("category")
}
return err
}
}
return nil
}
func (m *Pet) validateName(formats strfmt.Registry) error {
if err := validate.Required("name", "body", m.Name); err != nil {
return err
}
return nil
}
func (m *Pet) validatePhotoUrls(formats strfmt.Registry) error {
if err := validate.Required("photoUrls", "body", m.PhotoUrls); err != nil {
return err
}
return nil
}
var petTypeStatusPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["available","pending","sold"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
petTypeStatusPropEnum = append(petTypeStatusPropEnum, v)
}
}
const (
PetStatusAvailable string = "available"
PetStatusPending string = "pending"
PetStatusSold string = "sold"
)
func (m *Pet) validateStatusEnum(path, location string, value string) error {
if err := validate.Enum(path, location, value, petTypeStatusPropEnum); err != nil {
return err
}
return nil
}
func (m *Pet) validateStatus(formats strfmt.Registry) error {
if swag.IsZero(m.Status) {
return nil
}
if err := m.validateStatusEnum("status", "body", m.Status); err != nil {
return err
}
return nil
}
func (m *Pet) validateTags(formats strfmt.Registry) error {
if swag.IsZero(m.Tags) {
return nil
}
for i := 0; i < len(m.Tags); i++ {
if swag.IsZero(m.Tags[i]) {
continue
}
if m.Tags[i] != nil {
if err := m.Tags[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tags" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Pet) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
func (m *Pet) UnmarshalBinary(b []byte) error {
var res Pet
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
$ run ./go-swagger-service/cmd/swagger-petstore-server/main.go --port 8090
:
$ swagger generate client -f ./swagger.json
, api-sdk. , , .
package pet
import (
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
)
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
return &Client{transport: transport, formats: formats}
}
type Client struct {
transport runtime.ClientTransport
formats strfmt.Registry
}
func (a *Client) AddPet(params *AddPetParams) error {
if params == nil {
params = NewAddPetParams()
}
_, err := a.transport.Submit(&runtime.ClientOperation{
ID: "addPet",
Method: "POST",
PathPattern: "/pet",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http", "https"},
Params: params,
Reader: &AddPetReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
if err != nil {
return err
}
return nil
}
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
}
. Generate a spec from source code.
. , swagger .
grpc-gateway
swagger . grpc-ecosystem/grpc-gateway β β protoc
. gRPC -, RESTful JSON API gRPC. gRPC.
HTTP + JSON gRPC. API gRPC RESTful .
grpc-gateway reverse-proxy swagger.json protoc-gen-swagger.
. .
$ go install \
github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger \
github.com/golang/protobuf/protoc-gen-go
.proto
protobufsyntax = "proto3";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
package pet;
message AddPetRequest {
int64 id = 1;
message Category {
int64 id = 1;
string name = 2;
}
Category category = 2;
string name = 3;
repeated string photo_urls = 4;
message Tag {
int64 id = 1;
string name = 2;
}
repeated Tag tags = 5;
//pet status in the store
enum Status {
available = 0;
pending = 1;
sold = 2;
}
Status status = 6;
}
service PetService {
rpc AddPet (AddPetRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/pet"
body: "*"
};
}
}
gRPC stub:
$ protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
pet.proto
pet.pb.go
. reverse-proxy protoc-gen-grpc-gateway
:
$ protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
pet.proto
pet.pb.gw.go
. gateway. pet.swagger
.json protoc-gen-swagger
:
$ protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
pet.proto
PetServiceServer.
type PetServiceServer interface {
AddPet(context.Context, *AddPetRequest) (*empty.Empty, error)
}
:
main.gopackage pet
import (
"context"
"flag"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
gw "grpc-gateway/pet"
"grpc-gateway/service"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
err := gw.RegisterPetServiceHandlerServer(ctx, mux, &service.PetService{})
if err != nil {
return err
}
return http.ListenAndServe(":8081", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
, . , c grpc-gateway.
Swag
Swag Go Swagger 2.0. - Go. swaggo/swag Go ( Swagger UI).
:
Swagger , endpoint'. .
API-
func (c *ExampleController) ShowAccount(ctx *contex.Context) {
}
github.
, , .
Now we have just expanded our understanding of possible interactions with swagger documentation. We examined ways to design the code and compile documentation for it.
And I think that some have already chosen the appropriate method for implementing the API of their new project, or tried the analyzed examples against the current ones.