Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.1.0 #35

Merged
merged 8 commits into from
Feb 7, 2024
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ if err != nil {

You will then have a `func(next http.Handler) http.Handler`, `firetailMiddleware`, which you can use to wrap a `http.Handler` just the same as with the middleware from [`net/http/middleware`](https://pkg.go.dev/go.ntrrg.dev/ntgo/net/http/middleware). This should also be suitable for [Chi](https://go-chi.io/#/pages/middleware).

See the [Go reference for the Options struct](https://pkg.go.dev/github.com/FireTail-io/[email protected]/middlewares/http#Options) for documentation regarding the available options. For example, if you are using `us.firetail.app` you will need to set the `LogsApiUrl` to `https://api.logging.us-east-2.prod.firetail.app/logs/bulk`.



## Tests
Expand Down
58 changes: 42 additions & 16 deletions middlewares/http/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"net/http"
Expand All @@ -22,21 +23,9 @@
options.setDefaults() // Fill in any defaults where apropriate

// Load in our appspec, validate it & create a router from it if we have an appspec to load
var router routers.Router
if options.OpenapiSpecPath != "" {
loader := &openapi3.Loader{Context: context.Background(), IsExternalRefsAllowed: true}
doc, err := loader.LoadFromFile(options.OpenapiSpecPath)
if err != nil {
return nil, ErrorInvalidConfiguration{err}
}
err = doc.Validate(context.Background())
if err != nil {
return nil, ErrorAppspecInvalid{err}
}
router, err = gorillamux.NewRouter(doc)
if err != nil {
return nil, err
}
router, err := getRouter(options)
if err != nil {
return nil, err
}

// Register any custom body decoders
Expand Down Expand Up @@ -188,7 +177,7 @@
chainResponseWriter := httptest.NewRecorder()
startTime := time.Now()
next.ServeHTTP(chainResponseWriter, r)
logEntry.ExecutionTime = float64(time.Since(startTime).Milliseconds())
logEntry.ExecutionTime = float64(time.Since(startTime)) / 1000000.0

// If it has been enabled, and we were able to determine the route and path params, validate the response against the openapi spec
if options.EnableResponseValidation && route != nil && pathParams != nil {
Expand Down Expand Up @@ -239,3 +228,40 @@

return middleware, nil
}

func getRouter(options *Options) (routers.Router, error) {
hasBytes := options.OpenapiBytes != nil && len(options.OpenapiBytes) > 0
hasSpecPath := options.OpenapiSpecPath != ""

if !hasBytes && !hasSpecPath {
return nil, nil
}

loader := &openapi3.Loader{Context: context.Background(), IsExternalRefsAllowed: true}

var doc *openapi3.T
var err error
if hasBytes {
doc, err = loader.LoadFromData(options.OpenapiBytes)
} else if hasSpecPath {
doc, err = loader.LoadFromFile(options.OpenapiSpecPath)
}
if err != nil {
return nil, ErrorInvalidConfiguration{err}
}
if doc == nil {
return nil, ErrorInvalidConfiguration{errors.New("OpenAPI doc was nil after loading from file or data")}
}

Check warning on line 254 in middlewares/http/middleware.go

View check run for this annotation

Codecov / codecov/patch

middlewares/http/middleware.go#L253-L254

Added lines #L253 - L254 were not covered by tests

err = doc.Validate(context.Background())
if err != nil {
return nil, ErrorAppspecInvalid{err}
}

router, err := gorillamux.NewRouter(doc)
if err != nil {
return nil, err
}

Check warning on line 264 in middlewares/http/middleware.go

View check run for this annotation

Codecov / codecov/patch

middlewares/http/middleware.go#L263-L264

Added lines #L263 - L264 were not covered by tests

return router, nil
}
13 changes: 13 additions & 0 deletions middlewares/http/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import (
"net/http/httptest"
"testing"

_ "embed"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/sbabiv/xml2map"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

//go:embed test-spec.yaml
var openapiSpecBytes []byte

var healthHandler http.HandlerFunc = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Add("Content-Type", "application/json")
Expand Down Expand Up @@ -124,6 +129,14 @@ func TestInvalidSpec(t *testing.T) {
require.Equal(t, "invalid appspec: invalid paths: invalid path /health: invalid operation GET: a short description of the response is required", err.Error())
}

func TestSpecFromBytes(t *testing.T) {
middleware, err := GetMiddleware(&Options{
OpenapiBytes: openapiSpecBytes,
})
require.Nil(t, err)
require.NotNil(t, middleware)
}

func TestRequestToInvalidRoute(t *testing.T) {
middleware, err := GetMiddleware(&Options{
OpenapiSpecPath: "./test-spec.yaml",
Expand Down
6 changes: 6 additions & 0 deletions middlewares/http/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ type Options struct {
// SpecPath is the path at which your openapi spec can be found. Supplying an empty string disables any validation.
OpenapiSpecPath string

// OpenapiBytes is the raw bytes of your openapi spec. Supplying an empty slice disables any validation. OpenapiBytes takes
// precedence over OpenapiSpecPath if both are provided. OpenapiSpecPath will be used if OpenapiBytes is nil or len() == 0
OpenapiBytes []byte

// LogsApiToken is the API token which will be used when sending logs to the Firetail logging API with the default batch callback.
// This value should typically be loaded in from an environment variable. If unset, the default batch callback will not forward
// logs to the Firetail SaaS
LogsApiToken string

// LogsApiUrl is the URL of the Firetail logging API endpoint to which logs will be sent by the default batch callback. This value
// should typically be loaded in from an environment variable. If unset, the default value is the Firetail SaaS' bulk logs endpoint
// in the default region (firetail.app). If another region is being used, this option will need to be configured appropriately. For
// example, for us.firetail.app LogsApiUrl should normally be https://api.logging.us-east-2.prod.firetail.app/logs/bulk
LogsApiUrl string

// LogBatchCallback is an optional callback which is provided with a batch of Firetail log entries ready to be sent to Firetail. The
Expand Down
Loading