Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go-Swagger в продуктиве

Go-Swagger в продуктиве

Avatar for Ilya Kaznacheev

Ilya Kaznacheev

May 30, 2020
Tweet

More Decks by Ilya Kaznacheev

Other Decks in Programming

Transcript

  1. Илья Казначеев Remote Backend SWE Основатель Golang Voronezh Ведущий подкаста

    Z-Namespace Организатор конференций и митапов Любитель кофе
  2. Golang Voronezh - ~30 активных участников - митапы - мероприятия

    для новичков - открыто и дружелюбно t.me/golang_vrn meetup.com/Golang-Voronezh
  3. Representational state transfer (REST) is a software architectural style that

    defines a set of constraints to be used for creating Web services Wikipedia
  4. 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
  5. структура сгенерированных файлов internal/api ├ models │ └ ... └

    restapi ├ operations │ └ ... ├ configure_<your_service_name>.go ├ doc.go ├ embedded_spec.go └ server.go
  6. как мы генерируем код rm -rf internal/api && mkdir -p

    internal/api swagger generate server -t internal/api --exclude-main go mod tidy
  7. есть некоторые проблемы - go-swagger - это фреймворк, а не

    библиотека - куча сгенерированных типов на каждый чих - несовместим с популярными http библиотеками
  8. net/http хендлеры 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) }) }
  9. middleware с кастомным хендлером 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()
  10. настройки вне configure_<your_service_name>.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
  11. кастомные имена методов /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) }
  12. кастомные имена методов /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) }
  13. встроенная валидация 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
  14. шпаргалка по валидации 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
  15. юнит-тесты 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) }
  16. юнит-тесты 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}`, }, }
  17. юнит-тесты 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") }) }