Skip to content

alexferl/echo-openapi

Repository files navigation

echo-openapi Go Report Card codecov

An OpenAPI middleware for the Echo framework using getkin/kin-openapi to validate HTTP requests and responses.

Installing

go get github.com/alexferl/echo-openapi

Using

Code example

package main

import (
    "net/http"

    mw "github.com/alexferl/echo-openapi"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

/*
# openapi.yaml
openapi: 3.0.4
info:
  version: 1.0.0
  title: Test API
  description: A test API
paths:
  /hello:
    post:
      description: Hello
      parameters:
        - name: message
          in: query
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - message
                properties:
                  message:
                    type: string
                    description: Welcome message
                    minLength: 4
*/

type Handler struct {
    *mw.Handler
}

func (h *Handler) Hello(c echo.Context) error {
    msg := c.QueryParam("message")
    return h.Validate(c, http.StatusOK, echo.Map{"message": msg})
}

func main() {
    e := echo.New()

    h := &Handler{mw.NewHandler()}
    e.Add(http.MethodPost, "/hello", h.Hello)

    e.Use(middleware.Logger())
    e.Use(mw.OpenAPI("./openapi.yaml"))

    e.Logger.Fatal(e.Start("localhost:1323"))
}

Send an invalid request to test request validation:

curl -i -X POST http://localhost:1323/hello
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json; charset=UTF-8
Date: Sat, 12 Nov 2022 17:31:24 GMT
Content-Length: 117

{"message":"Validation error","errors":["parameter 'message' in query has an error: value is required but missing"]}

Send a valid request:

curl -i -X POST http://localhost:1323/hello\?message\=hello
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 12 Nov 2022 17:31:47 GMT
Content-Length: 19

{"message":"hello"}

Send a valid request with an invalid response:

curl -i -X POST http://localhost:1323/hello\?message\=a
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Date: Sat, 12 Nov 2022 17:31:01 GMT
Content-Length: 36

{"message":"Internal Server Error"}

You should also have the following in the server's log to help you debug your schema:

{"error":"failed validating response: message: minimum string length is 4"}

Configuration

type Config struct {
    // Skipper defines a function to skip middleware.
    Skipper middleware.Skipper

    // Schema defines the OpenAPI that will be loaded and
    // that the request and responses will be validated against.
    // Required.
    Schema string

    // ContextKey defines the key that will be used to store the validator
    // on the echo.Context when the request is successfully validated.
    // Optional. Defaults to "validator".
    ContextKey string

    // ExemptRoutes defines routes and methods that don't require tokens.
    // Optional.
    ExemptRoutes map[string][]string
}

type HandlerConfig struct {
    // ContentType sets the Content-Type header of the response.
    // Optional. Defaults to "application/json".
    ContentType string

    // ValidatorKey defines the key that will be used to read the
    // *openapi3filter.RequestValidationInput from the echo.Context
    // set by the middleware.
    // Optional. Defaults to "validator".
    ValidatorKey string

    // ExcludeRequestBody makes Validate skips request body validation.
    // Optional. Defaults to false.
    ExcludeRequestBody bool

    // ExcludeResponseBody makes Validate skips response body validation.
    // Optional. Defaults to false.
    ExcludeResponseBody bool

    // IncludeResponseStatus makes Validate fail on response
    // statuses not defined in the OpenAPI spec.
    // Optional. Defaults to true.
    IncludeResponseStatus bool
}