Skip to content

Commit

Permalink
Merge pull request #35 from FireTail-io/dev
Browse files Browse the repository at this point in the history
v0.1.0
  • Loading branch information
TheTeaCat authored Feb 7, 2024
2 parents dc1c868 + 5160c3f commit 62d9054
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 16 deletions.
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 @@ package firetail
import (
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"net/http"
Expand All @@ -22,21 +23,9 @@ func GetMiddleware(options *Options) (func(next http.Handler) http.Handler, erro
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 @@ func GetMiddleware(options *Options) (func(next http.Handler) http.Handler, erro
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 @@ func GetMiddleware(options *Options) (func(next http.Handler) http.Handler, erro

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")}
}

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

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

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

0 comments on commit 62d9054

Please sign in to comment.