Skip to content

Commit

Permalink
docs: fix Context.Clone and Context.CloneWith not dealing with tsrParams
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Oct 13, 2024
1 parent ba6f157 commit fe0a4e0
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 37 deletions.
12 changes: 9 additions & 3 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,19 @@ func TestContext_Route(t *testing.T) {
assert.Equal(t, "/foo", w.Body.String())
}

func TestContext_Tags(t *testing.T) {
func TestContext_Annotations(t *testing.T) {
t.Parallel()
f := New()
f.MustHandle(http.MethodGet, "/foo", emptyHandler, WithTags("foo", "bar", "baz"))
f.MustHandle(
http.MethodGet,
"/foo",
emptyHandler,
WithAnnotations(Annotation{Key: "foo", Value: "bar"}, Annotation{Key: "foo", Value: "baz"}),
WithAnnotation("john", 1),
)
rte := f.Tree().Route(http.MethodGet, "/foo")
require.NotNil(t, rte)
assert.Equal(t, []string{"foo", "bar", "baz"}, slices.Collect(rte.Tags()))
assert.Equal(t, []Annotation{{"foo", "bar"}, {"foo", "baz"}, {"john", 1}}, slices.Collect(rte.Annotations()))
}

func TestContext_Clone(t *testing.T) {
Expand Down
7 changes: 0 additions & 7 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ func LoggerWithHandler(handler slog.Handler) MiddlewareFunc {
ipStr = "unknown"
}

var tags []string
if route := c.Route(); route != nil {
tags = route.tags
}

if location == "" {
log.LogAttrs(
req.Context(),
Expand All @@ -52,7 +47,6 @@ func LoggerWithHandler(handler slog.Handler) MiddlewareFunc {
slog.String("method", req.Method),
slog.String("path", c.Request().URL.String()),
slog.Duration("latency", roundLatency(latency)),
slog.Any("tags", tags),
)
} else {
log.LogAttrs(
Expand All @@ -63,7 +57,6 @@ func LoggerWithHandler(handler slog.Handler) MiddlewareFunc {
slog.String("method", req.Method),
slog.String("path", c.Request().URL.String()),
slog.Duration("latency", roundLatency(latency)),
slog.Any("tags", tags),
slog.String("location", location),
)
}
Expand Down
19 changes: 4 additions & 15 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package fox

import (
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"log/slog"
"net/http"
"net/http/httptest"
"slices"
"testing"
)

Expand All @@ -35,10 +33,6 @@ func TestLoggerWithHandler(t *testing.T) {
require.NoError(t, f.Handle(http.MethodGet, "/failure", func(c Context) {
c.Writer().WriteHeader(http.StatusInternalServerError)
}))
require.NoError(t, f.Handle(http.MethodGet, "/tags", func(c Context) {
c.Writer().WriteHeader(http.StatusOK)
fmt.Println(slices.Collect(c.Route().Tags()))
}, WithTags("foo", "bar", "baz")))

cases := []struct {
name string
Expand All @@ -48,27 +42,22 @@ func TestLoggerWithHandler(t *testing.T) {
{
name: "should log info level",
req: httptest.NewRequest(http.MethodGet, "/success", nil),
want: "time=time level=INFO msg=192.0.2.1 status=200 method=GET path=/success latency=latency tags=[]\n",
want: "time=time level=INFO msg=192.0.2.1 status=200 method=GET path=/success latency=latency\n",
},
{
name: "should log error level",
req: httptest.NewRequest(http.MethodGet, "/failure", nil),
want: "time=time level=ERROR msg=192.0.2.1 status=500 method=GET path=/failure latency=latency tags=[]\n",
want: "time=time level=ERROR msg=192.0.2.1 status=500 method=GET path=/failure latency=latency\n",
},
{
name: "should log warn level",
req: httptest.NewRequest(http.MethodGet, "/foobar", nil),
want: "time=time level=WARN msg=192.0.2.1 status=404 method=GET path=/foobar latency=latency tags=[]\n",
want: "time=time level=WARN msg=192.0.2.1 status=404 method=GET path=/foobar latency=latency\n",
},
{
name: "should log debug level",
req: httptest.NewRequest(http.MethodGet, "/success/", nil),
want: "time=time level=DEBUG msg=192.0.2.1 status=301 method=GET path=/success/ latency=latency tags=[] location=../success\n",
},
{
name: "should log info level tags",
req: httptest.NewRequest(http.MethodGet, "/tags", nil),
want: "time=time level=INFO msg=192.0.2.1 status=200 method=GET path=/tags latency=latency tags=\"[foo bar baz]\"\n",
want: "time=time level=DEBUG msg=192.0.2.1 status=301 method=GET path=/success/ latency=latency location=../success\n",
},
}

Expand Down
14 changes: 12 additions & 2 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,19 @@ func WithClientIPStrategy(strategy ClientIPStrategy) Option {
})
}

func WithTags(tags ...string) PathOption {
// WithAnnotations attach arbitrary metadata to routes. Annotations are key-value pairs that allow middleware, handler or
// any other components to modify behavior based on the attached metadata. Annotations must be explicitly reapplied when
// updating a route.
func WithAnnotations(annotations ...Annotation) PathOption {
return pathOptionFunc(func(route *Route) {
route.tags = tags
route.annots = append(route.annots, annotations...)
})
}

// WithAnnotation attaches a single key-value annotation to a route. See also [WithAnnotations] and [Annotation] for more details.
func WithAnnotation(key string, value any) PathOption {
return pathOptionFunc(func(route *Route) {
route.annots = append(route.annots, Annotation{key, value})
})
}

Expand Down
18 changes: 17 additions & 1 deletion recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,18 @@ func recovery(logger *slog.Logger, c Context, handle RecoveryFunc) {

sb.WriteString("Stack:\n")
sb.WriteString(stacktrace(3, 6))

params := slices.Collect(mapParamsToAttr(c.Params()))
var annotations []any
if route := c.Route(); route != nil {
annotations = slices.Collect(mapAnnotationsToAttr(route.Annotations()))
}

logger.Error(
sb.String(),
slog.String("path", c.Path()),
slog.Group("param", params...),
slog.Group("params", params...),
slog.Group("annotations", annotations...),
slog.Any("error", err),
)

Expand Down Expand Up @@ -143,3 +149,13 @@ func mapParamsToAttr(params iter.Seq[Param]) iter.Seq[any] {
}
}
}

func mapAnnotationsToAttr(annotations iter.Seq[Annotation]) iter.Seq[any] {
return func(yield func(any) bool) {
for a := range annotations {
if !yield(slog.Any(a.Key, a.Value)) {
break

Check warning on line 157 in recovery.go

View check run for this annotation

Codecov / codecov/patch

recovery.go#L156-L157

Added lines #L156 - L157 were not covered by tests
}
}
}
}
29 changes: 20 additions & 9 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import (
"strings"
)

// Annotations is a collection of Annotation key-value pairs that can be attached to routes.
type Annotations []Annotation

// Annotation represents a single key-value pair that provides metadata for a route.
// Annotations are typically used to store information that can be leveraged by middleware, handlers, or external
// libraries to modify or customize route behavior.
type Annotation struct {
Key string
Value any
}

// Route represent a registered route in the route tree.
// Most of the Route API is EXPERIMENTAL and is likely to change in future release.
type Route struct {
Expand All @@ -14,17 +25,17 @@ type Route struct {
hall HandlerFunc
path string
mws []middleware
tags []string
annots Annotations
redirectTrailingSlash bool
ignoreTrailingSlash bool
}

// Handle calls the handler with the provided Context. See also HandleMiddleware.
// Handle calls the handler with the provided [Context]. See also [HandleMiddleware].
func (r *Route) Handle(c Context) {
r.hbase(c)
}

// HandleMiddleware calls the handler with route-specific middleware applied, using the provided Context.
// HandleMiddleware calls the handler with route-specific middleware applied, using the provided [Context].
func (r *Route) HandleMiddleware(c Context, _ ...struct{}) {
// The variadic parameter is intentionally added to prevent this method from having the same signature as HandlerFunc.
// This avoids accidental use of HandleMiddleware where a HandlerFunc is required.
Expand All @@ -36,11 +47,11 @@ func (r *Route) Path() string {
return r.path
}

// Tags returns a range iterator over the tags associated with the route.
func (r *Route) Tags() iter.Seq[string] {
return func(yield func(string) bool) {
for _, tag := range r.tags {
if !yield(tag) {
// Annotations returns a range iterator over annotations associated with the route.
func (r *Route) Annotations() iter.Seq[Annotation] {
return func(yield func(Annotation) bool) {
for _, a := range r.annots {
if !yield(a) {
return

Check warning on line 55 in route.go

View check run for this annotation

Codecov / codecov/patch

route.go#L55

Added line #L55 was not covered by tests
}
}
Expand All @@ -61,7 +72,7 @@ func (r *Route) IgnoreTrailingSlashEnabled() bool {
return r.ignoreTrailingSlash
}

// ClientIPStrategyEnabled returns whether the route is configured with a ClientIPStrategy.
// ClientIPStrategyEnabled returns whether the route is configured with a [ClientIPStrategy].
// This api is EXPERIMENTAL and is likely to change in future release.
func (r *Route) ClientIPStrategyEnabled() bool {
_, ok := r.ipStrategy.(noClientIPStrategy)
Expand Down

0 comments on commit fe0a4e0

Please sign in to comment.