From 73bcd01cc621e0465a40eac4967d01c29d418bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20de=20Jong?= <5366568-cornedejong@users.noreply.gitlab.com> Date: Sun, 26 May 2024 17:20:35 +0200 Subject: [PATCH] added route metadata with tests --- mux.go | 2 ++ route.go | 46 +++++++++++++++++++++++++++ route_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/mux.go b/mux.go index 24920ace..c9396e91 100644 --- a/mux.go +++ b/mux.go @@ -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. diff --git a/route.go b/route.go index b6582dae..418e9676 100644 --- a/route.go +++ b/route.go @@ -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 @@ -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. diff --git a/route_test.go b/route_test.go index 5aa35d11..283c3ad5 100644 --- a/route_test.go +++ b/route_test.go @@ -1,7 +1,9 @@ package mux import ( + "errors" "net/http" + "reflect" "regexp" "sync" "testing" @@ -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) + }) +}