Skip to content

Commit

Permalink
sub packages
Browse files Browse the repository at this point in the history
  • Loading branch information
dawkaka committed Jul 10, 2024
1 parent 890cab0 commit f0e9a45
Show file tree
Hide file tree
Showing 18 changed files with 824 additions and 768 deletions.
110 changes: 0 additions & 110 deletions echo.go

This file was deleted.

136 changes: 136 additions & 0 deletions echo/echo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package apitoolkitecho

import (
"bufio"
"bytes"
"context"
"errors"
"io"
"net"
"net/http"
"time"

apt "github.com/apitoolkit/apitoolkit-go"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
)

// bodyDumpResponseWriter use to preserve the http response body during request processing
type echoBodyLogWriter struct {
io.Writer
http.ResponseWriter
}

func (w *echoBodyLogWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
}

func (w *echoBodyLogWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}

func (w *echoBodyLogWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}

func (w *echoBodyLogWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}

func NewClient(ctx context.Context, conf apt.Config) (*apt.Client, error) {
return apt.NewClient(ctx, conf)
}

func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client {
return apt.HTTPClient(ctx, opts...)
}
func WithRedactHeaders(headers ...string) apt.RoundTripperOption {
return apt.WithRedactHeaders(headers...)
}
func WithRedactRequestBody(paths ...string) apt.RoundTripperOption {
return apt.WithRedactRequestBody(paths...)
}
func WithRedactResponseBody(paths ...string) apt.RoundTripperOption {
return apt.WithRedactResponseBody(paths...)
}

func ReportError(ctx context.Context, err error) {
apt.ReportError(ctx, err)
}

// EchoMiddleware middleware for echo framework, collects requests, response and publishes the payload
func EchoMiddleware(c *apt.Client) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) (err error) {
// Register the client in the context,
// so it can be used for outgoing requests with little ceremony
ctx.Set(string(apt.CurrentClient), c)

msgID := uuid.Must(uuid.NewRandom())
ctx.Set(string(apt.CurrentRequestMessageID), msgID)

errorList := []apt.ATError{}
ctx.Set(string(apt.ErrorListCtxKey), &errorList)
newCtx := context.WithValue(ctx.Request().Context(), apt.ErrorListCtxKey, &errorList)
newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID)
newCtx = context.WithValue(newCtx, apt.CurrentClient, c)
ctx.SetRequest(ctx.Request().WithContext(newCtx))

var reqBuf []byte
// safely read request body
if ctx.Request().Body != nil {
reqBuf, _ = io.ReadAll(ctx.Request().Body)
}
ctx.Request().Body = io.NopCloser(bytes.NewBuffer(reqBuf))
startTime := time.Now()

// create a MultiWriter that streams the response body into resBody
resBody := new(bytes.Buffer)
mw := io.MultiWriter(ctx.Response().Writer, resBody)
writer := &echoBodyLogWriter{Writer: mw, ResponseWriter: ctx.Response().Writer}
ctx.Response().Writer = writer
pathParams := map[string]string{}
for _, paramName := range ctx.ParamNames() {
pathParams[paramName] = ctx.Param(paramName)
}
config := c.GetConfig()

defer func() {
if err := recover(); err != nil {
if _, ok := err.(error); !ok {
err = errors.New(err.(string))
}
apt.ReportError(ctx.Request().Context(), err.(error))
payload := c.BuildPayload(apt.GoDefaultSDKType, startTime,
ctx.Request(), 500,
reqBuf, resBody.Bytes(), ctx.Response().Header().Clone(),
pathParams, ctx.Path(),
config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody,
errorList,
msgID,
nil,
)
c.PublishMessage(ctx.Request().Context(), payload)
panic(err)
}
}()

// pass on request handling
err = next(ctx)

// proceed post-response processing
payload := c.BuildPayload(apt.GoDefaultSDKType, startTime,
ctx.Request(), ctx.Response().Status,
reqBuf, resBody.Bytes(), ctx.Response().Header().Clone(),
pathParams, ctx.Path(),
config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody,
errorList,
msgID,
nil,
)
c.PublishMessage(ctx.Request().Context(), payload)
return err
}

}
}
57 changes: 29 additions & 28 deletions echo_test.go → echo/echo_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package apitoolkit
package apitoolkitecho

import (
"context"
Expand All @@ -9,21 +9,21 @@ import (
"testing"
"time"

apt "github.com/apitoolkit/apitoolkit-go"
"github.com/imroc/req"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

func TestEchoServerMiddleware(t *testing.T) {
client := &Client{
config: &Config{
RedactHeaders: []string{"X-Api-Key", "Accept-Encoding"},
RedactResponseBody: exampleDataRedaction,
},
}
client := &apt.Client{}
client.SetConfig(&apt.Config{
RedactHeaders: []string{"X-Api-Key", "Accept-Encoding"},
RedactResponseBody: apt.ExampleDataRedaction,
})
var publishCalled bool

client.PublishMessage = func(ctx context.Context, payload Payload) error {
client.PublishMessage = func(ctx context.Context, payload apt.Payload) error {
assert.Equal(t, "POST", payload.Method)
assert.Equal(t, "/:slug/test", payload.URLPath)
assert.Equal(t, map[string]string{
Expand All @@ -48,10 +48,10 @@ func TestEchoServerMiddleware(t *testing.T) {
assert.Equal(t, "/slug-value/test?param1=abc&param2=123", payload.RawURL)
assert.Equal(t, http.StatusAccepted, payload.StatusCode)
assert.Greater(t, payload.Duration, 1000*time.Nanosecond)
assert.Equal(t, GoDefaultSDKType, payload.SdkType)
assert.Equal(t, apt.GoDefaultSDKType, payload.SdkType)

reqData, _ := json.Marshal(exampleData2)
respData, _ := json.Marshal(exampleDataRedacted)
reqData, _ := json.Marshal(apt.ExampleData2)
respData, _ := json.Marshal(apt.ExampleDataRedacted)
assert.Equal(t, reqData, payload.RequestBody)
assert.Equal(t, respData, payload.ResponseBody)

Expand All @@ -60,30 +60,30 @@ func TestEchoServerMiddleware(t *testing.T) {
}

e := echo.New()
e.Use(client.EchoMiddleware)
e.Use(EchoMiddleware(client))
e.POST("/:slug/test", func(c echo.Context) (err error) {
body, err := io.ReadAll(c.Request().Body)
assert.NoError(t, err)
assert.NotEmpty(t, body)
reqData, _ := json.Marshal(exampleData2)
reqData, _ := json.Marshal(apt.ExampleData2)
assert.Equal(t, reqData, body)
c.Response().Header().Set("Content-Type", "application/json")
c.Response().Header().Set("X-API-KEY", "applicationKey")
c.JSON(http.StatusAccepted, exampleData)
c.JSON(http.StatusAccepted, apt.ExampleData)
return
})

ts := httptest.NewServer(e)
defer ts.Close()

respData, _ := json.Marshal(exampleData)
respData, _ := json.Marshal(apt.ExampleData)
resp, err := req.Post(ts.URL+"/slug-value/test",
req.Param{"param1": "abc", "param2": 123},
req.Header{
"Content-Type": "application/json",
"X-API-KEY": "past-3",
},
req.BodyJSON(exampleData2),
req.BodyJSON(apt.ExampleData2),
)
assert.NoError(t, err)
assert.True(t, publishCalled)
Expand All @@ -93,12 +93,11 @@ func TestEchoServerMiddleware(t *testing.T) {
}

func TestOutgoingRequestEcho(t *testing.T) {
client := &Client{
config: &Config{},
}
client := &apt.Client{}
client.SetConfig(&apt.Config{})
publishCalled := false
var parentId *string
client.PublishMessage = func(ctx context.Context, payload Payload) error {
client.PublishMessage = func(ctx context.Context, payload apt.Payload) error {
if payload.RawURL == "/from-gorilla" {
assert.NotNil(t, payload.ParentID)
parentId = payload.ParentID
Expand All @@ -109,19 +108,21 @@ func TestOutgoingRequestEcho(t *testing.T) {
return nil
}
router := echo.New()
router.Use(client.EchoMiddleware)
client.PublishMessage(context.Background(), apt.Payload{})
router.Use(EchoMiddleware(client))
router.POST("/:slug/test", func(c echo.Context) (err error) {
body, err := io.ReadAll(c.Request().Body)
assert.NotEmpty(t, body)
reqData, _ := json.Marshal(exampleData2)
reqData, _ := json.Marshal(apt.ExampleData2)
assert.Equal(t, reqData, body)
HTTPClient := http.DefaultClient
HTTPClient.Transport = client.WrapRoundTripper(
c.Request().Context(), HTTPClient.Transport,
hClient := HTTPClient(c.Request().Context(),
WithRedactHeaders("content-type", "Authorization", "HOST"),
WithRedactRequestBody("$.user.email", "$.user.addresses"),
WithRedactResponseBody("$.users[*].email", "$.users[*].credit_card"),
)
_, _ = HTTPClient.Get("http://localhost:3000/from-gorilla")
_, _ = hClient.Get("http://localhost:3000/from-gorilla")

c.JSON(http.StatusAccepted, exampleData)
c.JSON(http.StatusAccepted, apt.ExampleData)
return
})
ts := httptest.NewServer(router)
Expand All @@ -133,7 +134,7 @@ func TestOutgoingRequestEcho(t *testing.T) {
"Content-Type": "application/json",
"X-API-KEY": "past-3",
},
req.BodyJSON(exampleData2),
req.BodyJSON(apt.ExampleData2),
)
assert.NoError(t, err)
assert.True(t, publishCalled)
Expand Down
Loading

0 comments on commit f0e9a45

Please sign in to comment.