Slide 1

Slide 1 text

Go-Swagger in production wins and pitfalls

Slide 2

Slide 2 text

Ilya Kaznacheev Remote Backend SWE Founder of Golang Voronezh Host of Z-Namespace podcast Organizer of conference and meetups Coffee geek

Slide 3

Slide 3 text

Golang Voronezh - ~30 active members - meetups - events for beginners - open & friendly

Slide 4

Slide 4 text

what swagger is?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

SOAP JSON-PRC GraphQL gRPC OData REST

Slide 7

Slide 7 text

Representational state transfer (REST) is a software architectural style that defines a set of constraints to be used for creating Web services Wikipedia

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

swagger: "2.0" info: title: Pet API version: "1.0.0" basePath: /api schemes: - http paths: /pets: get: summary: List all pets parameters: - name: limit in: query description: "How many items to return at one time" required: true type: integer responses: 200: description: an paged array of pets 400: description: unexpected error

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

why do we use swagger?

Slide 13

Slide 13 text

my team trying to sync API changes...

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

go-swagger

Slide 16

Slide 16 text

code generation swagger generate server -t internal/api --exclude-main

Slide 17

Slide 17 text

generated code structure internal/api ├ models │ └ ... └ restapi ├ operations │ └ ... ├ configure_.go ├ doc.go ├ embedded_spec.go └ server.go

Slide 18

Slide 18 text

our code generation rm -rf internal/api && mkdir -p internal/api swagger generate server -t internal/api --exclude-main go mod tidy

Slide 19

Slide 19 text

and we're all set ?

Slide 20

Slide 20 text

NO

Slide 21

Slide 21 text

there are some problems - go-swagger is a framework, not a library - plenty of generated types for everything - incompatible with popular http-libraries

Slide 22

Slide 22 text

let’s fix ’em all!

Slide 23

Slide 23 text

serving net/http handlers type CustomResponder func(http.ResponseWriter, runtime.Producer) func (c CustomResponder) WriteResponse(w http.ResponseWriter, p runtime.Producer) { c(w, p) } func MetricsHandler(p instruments.GetMetricsParams) middleware.Responder { return CustomResponder(func(w http.ResponseWriter, _ runtime.Producer) { promhttp.Handler().ServeHTTP(w, p.HTTPRequest) }) }

Slide 24

Slide 24 text

simple middleware api := operations.NewSwaggerPetstoreAPI(swaggerSpec) api.InstrumentsGetMetricsHandler = instruments.GetMetricsHandlerFunc(MetricsHandler) api.AddMiddlewareFor("GET", "/metrics", SomeMiddleware) srv := restapi.NewServer(api) srv.Serve()

Slide 25

Slide 25 text

middleware with custom handler h := api.Serve(nil) r := chi.NewRouter() r.Use( middleware.Recoverer, ) r.With(AuthMiddleware).Group(func(r chi.Router) { r.Handle("/user/*", h) }) r.Mount("/", h) srv.ConfigureAPI() srv.SetHandler(r) srv.Serve()

Slide 26

Slide 26 text

setup outside of configure_.go api.Logger = log.Printf api.HTMLProducer = runtime.TextProducer() srv := restapi.NewServer(api) srv.EnabledListeners = []string{"http"} srv.Port = conf.HTTPPort srv.Host = conf.HTTPAddr

Slide 27

Slide 27 text

custom method names /store/order/{orderId}/items: get: tags: - store summary: Find purchase order items parameters: - name: orderId in: path required: true type: integer func GetOrderItems( param store.GetStoreOrderOrderIDItemsParams, ) middleware.Responder { items, err := getOrderItems(param.OrderID) if err != nil { return store.NewGetStoreOrderOrderIDItemsNotFound() } res := &models.OrderItems{} // // fill resopnse // return store.NewGetStoreOrderOrderIDItemsOK(). WithPayload(res) }

Slide 28

Slide 28 text

custom method names /store/order/{orderId}/items: get: tags: - store summary: Find purchase order items operationId: getOrderItems parameters: - name: orderId in: path required: true type: integer func GetOrderItems( param store.GetOrderItemsParams, ) middleware.Responder { items, err := getOrderItems(param.OrderID) if err != nil { return store.NewGetOrderItemsNotFound() } res := &models.OrderItems{} // // fill resopnse // return store.NewGetOrderItemsOK(). WithPayload(res) }

Slide 29

Slide 29 text

validity checks OrderItems: type: object properties: message: type: string maximum: 3 # swg/internal/api/models internal/api/models/order_items.go:45:55: cannot convert m.Message (type string) to type float64

Slide 30

Slide 30 text

validity check cheat sheet numbers and integers - multipleOf - maximum - minimum - exclusiveMaximum - exclusiveMinimum strings - maxLength - minLength - pattern arrays - maxItems - minItems - uniqueItems - maxContains - minContains objects - maxProperties - minProperties - required - dependentRequired any type - type - enum - const

Slide 31

Slide 31 text

extensions (tricks) x-omitempty x-nullable x-isnullable x-order x-go-custom-tag x-schemes x-go-name x-go-type x-go-json-string x-go-enum-ci

Slide 32

Slide 32 text

unit tests func GetOrderByID(param store.GetOrderByIDParams) middleware.Responder { order := models.Order{ ID: 123, PetID: 456, Quantity: 20, Status: "approved", } if param.OrderID != order.ID { return store.NewGetOrderByIDNotFound().WithPayload(&models.ErrorMessage{ Code: http.StatusNotFound, Message: http.StatusText(http.StatusNotFound), }) } return store.NewGetOrderByIDOK().WithPayload(&order) }

Slide 33

Slide 33 text

unit tests tests := []struct { name string req store.GetOrderByIDParams code int want string }{ { name: "good test", req: store.GetOrderByIDParams{OrderID: 123}, code: 200, want: `{"id":123,"petId":456,"quantity":20,"status":"approved"}`, }, { name: "bad test", req: store.GetOrderByIDParams{OrderID: 456}, code: 404, want: `{"message":"Not Found", "code":404}`, }, }

Slide 34

Slide 34 text

unit tests for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rr := httptest.NewRecorder() GetOrderByID(tt.req).WriteResponse(rr, runtime.JSONProducer()) assert.JSONEq(t, tt.want, rr.Body.String(), "wrong response body") assert.Equal(t, tt.code, rr.Code, "wrong response code") }) }

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

helpful links json-schema.org/specification.html swagger.io/docs/specification/2-0 goswagger.io

Slide 37

Slide 37 text

bonus A pluggable go-swagger (in development) github.com/ilyakaznacheev/go-plugger

Slide 38

Slide 38 text

bonus 2 Insomnia Designer insomnia.rest/products/designer

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

ilyakaznacheev