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

Feature route metadata #760

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ var (
// Do not run this function from `init()` in importable packages.
// Changing this value is not safe for concurrent use.
RegexpCompileFunc = regexp.Compile
// ErrMetadataKeyNotFound is returned when the specified metadata key is not present in the map
ErrMetadataKeyNotFound = errors.New("key not found in metadata")
)

// NewRouter returns a new router instance.
Expand Down
46 changes: 46 additions & 0 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Route struct {
// Error resulted from building a route.
err error

// The meta data associated with this route
metadata map[any]any

// "global" reference to all named routes
namedRoutes map[string]*Route

Expand Down Expand Up @@ -122,6 +125,49 @@ func (r *Route) BuildOnly() *Route {
return r
}

// MetaData -------------------------------------------------------------------

// Metadata is used to set metadata on a route
func (r *Route) Metadata(key any, value any) *Route {
if r.metadata == nil {
r.metadata = make(map[any]any)
}

r.metadata[key] = value
return r
}

// GetMetadata returns the metadata map for route
func (r *Route) GetMetadata() map[any]any {
return r.metadata
}

// MetadataContains returns whether or not the key is present in the metadata map
func (r *Route) MetadataContains(key any) bool {
_, ok := r.metadata[key]
return ok
}

// GetMetadataValue returns the value of a specific key in the metadata map. If the key is not present in the map mux.ErrMetadataKeyNotFound is returned
func (r *Route) GetMetadataValue(key any) (any, error) {
value, ok := r.metadata[key]
if !ok {
return nil, ErrMetadataKeyNotFound
}

return value, nil
}

// GetMetadataValueOr returns the value of a specific key in the metadata map. If the key is not present in the metadata the fallback value is returned
func (r *Route) GetMetadataValueOr(key any, fallbackValue any) any {
value, ok := r.metadata[key]
if !ok {
return fallbackValue
}

return value
}

// Handler --------------------------------------------------------------------

// Handler sets a handler for the route.
Expand Down
88 changes: 88 additions & 0 deletions route_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package mux

import (
"errors"
"net/http"
"reflect"
"regexp"
"sync"
"testing"
Expand Down Expand Up @@ -64,3 +66,89 @@ func testNewRouter(_ testing.TB, handler http.Handler) {
r.Queries("orgID", "{orgID:[0-9]*?}")
r.Host("{subdomain}.domain.com")
}

func TestRouteMetadata(t *testing.T) {
router := NewRouter()
rw := NewRecorder()

expectedMap := make(map[any]any)
expectedMap["key"] = "value"

router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
route := CurrentRoute(r)
metadata := route.GetMetadata()

if !reflect.DeepEqual(metadata, expectedMap) {
println(metadata)
t.Fatalf("Expected map does not equal the metadata map")
}

}).Metadata("key", "value")

router.HandleFunc("/single-value", func(w http.ResponseWriter, r *http.Request) {
route := CurrentRoute(r)
value, err := route.GetMetadataValue("key")
if err != nil {
t.Fatalf("Expected metadata value to be present, but gave error: %s", err)
}

stringValue, ok := value.(string)
if !ok {
t.Fatalf("Expected metadata value to be string, but was: %s", reflect.TypeOf(value))
}

if stringValue != "value" {
t.Fatalf("Expected metadata value to be '%s', but got '%s'", "value", stringValue)
}

_, err = route.GetMetadataValue("key2")
if err == nil {
t.Fatalf("Expected metadata key not to be present and error, but error was nil")
}

if !errors.Is(err, ErrMetadataKeyNotFound) {
t.Fatalf("Expected error to be ErrMetadataKeyNotFound but got: %s", err)
}

}).Metadata("key", "value")

router.HandleFunc("/single-value-fallback", func(w http.ResponseWriter, r *http.Request) {
route := CurrentRoute(r)
value := route.GetMetadataValueOr("key", "value-fallback")

stringValue, ok := value.(string)
if !ok {
t.Fatalf("Expected metadata value to be string, but was: %s", reflect.TypeOf(value))
}

if stringValue != "value" {
t.Fatalf("Expected metadata value to be '%s', but got '%s'", "value", stringValue)
}

fallbackValue := route.GetMetadataValueOr("key2", "value2")
fallbackStringValue, ok := fallbackValue.(string)
if !ok {
t.Fatalf("Expected metadata value to be string, but was: %s", reflect.TypeOf(value))
}

if fallbackStringValue != "value2" {
t.Fatalf("Expected metadata value to be '%s', but got '%s'", "value2", fallbackStringValue)
}

}).Metadata("key", "value")

t.Run("get metadata map", func(t *testing.T) {
req := newRequest("GET", "/")
router.ServeHTTP(rw, req)
})

t.Run("get metadata value", func(t *testing.T) {
req := newRequest("GET", "/single-value")
router.ServeHTTP(rw, req)
})

t.Run("get metadata value or fallback", func(t *testing.T) {
req := newRequest("GET", "/single-value-fallback")
router.ServeHTTP(rw, req)
})
}
Loading