diff --git a/mux.go b/mux.go index f175d739..7edc3c9e 100644 --- a/mux.go +++ b/mux.go @@ -82,6 +82,7 @@ func Register[T any, B any, Contexted ctx[B]](s *Server, method string, path str route.operation.Summary = name route.operation.Description = "controller: " + nameWithPath route.operation.OperationID = fullPath + ":" + name + route.operation.Tags = append(route.operation.Tags, s.tags...) return route } diff --git a/mux_test.go b/mux_test.go index b84028f1..475a9474 100644 --- a/mux_test.go +++ b/mux_test.go @@ -246,6 +246,44 @@ func TestDeleteStd(t *testing.T) { require.Equal(t, w.Body.String(), "test successful") } +func TestRegister(t *testing.T) { + t.Run("route tag inheritance", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + route := Register(s, http.MethodGet, "/path", func(ctx *ContextNoBody) (string, error) { + return "test", nil + }) + require.Equal(t, []string{"string", "my-server-tag"}, route.operation.Tags) + }) + t.Run("route tag override", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + route := Register(s, http.MethodGet, "/path", func(ctx *ContextNoBody) (string, error) { + return "test", nil + }).Tags("my-route-tag") + + require.Equal(t, []string{"my-route-tag"}, route.operation.Tags) + }) + t.Run("route tag add", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + route := Register(s, http.MethodGet, "/path", func(ctx *ContextNoBody) (string, error) { + return "test", nil + }).AddTags("my-route-tag") + + require.Equal(t, []string{"string", "my-server-tag", "my-route-tag"}, route.operation.Tags) + }) + t.Run("route tag removal", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + route := Register(s, http.MethodGet, "/path", func(ctx *ContextNoBody) (string, error) { + return "test", nil + }).AddTags("my-route-tag").RemoveTags("my-server-tag") + + require.Equal(t, []string{"string", "my-route-tag"}, route.operation.Tags) + }) +} + func TestHideOpenapiRoutes(t *testing.T) { t.Run("hide main server", func(t *testing.T) { s := NewServer() @@ -496,6 +534,50 @@ func TestGroup(t *testing.T) { }) } +func TestGroupTags(t *testing.T) { + t.Run("inherit tags", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + group := Group(s, "/slash") + + require.Equal(t, []string{"my-server-tag"}, group.tags) + }) + t.Run("override parent tags", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + group := Group(s, "/slash"). + Tags("my-group-tag") + + require.Equal(t, []string{"my-group-tag"}, group.tags) + }) + t.Run("add child group tag", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + group := Group(s, "/slash"). + AddTags("my-group-tag") + + require.Equal(t, []string{"my-server-tag", "my-group-tag"}, group.tags) + }) + t.Run("remove server tag", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag", "my-other-server-tag") + group := Group(s, "/slash"). + RemoveTags("my-server-tag") + + require.Equal(t, []string{"my-other-server-tag"}, group.tags) + }) + t.Run("multiple groups inheritance", func(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + group := Group(s, "/slash"). + AddTags("my-group-tag") + childGroup := Group(group, "/slash"). + AddTags("my-childGroup-tag") + + require.Equal(t, []string{"my-server-tag", "my-group-tag", "my-childGroup-tag"}, childGroup.tags) + }) +} + func ExampleContextNoBody_SetCookie() { s := NewServer() Get(s, "/test", func(c *ContextNoBody) (string, error) { diff --git a/options.go b/options.go index f2d4e249..331e6511 100644 --- a/options.go +++ b/options.go @@ -8,6 +8,7 @@ import ( "log/slog" "net/http" "os" + "slices" "time" "github.com/getkin/kin-openapi/openapi3" @@ -45,6 +46,10 @@ type Server struct { // For example, it allows OPTIONS /foo even if it is not declared (only GET /foo is declared). corsMiddleware func(http.Handler) http.Handler + // OpenAPI documentation tags used for logical groupings of operations + // These tags will be inherited by child Routes/Groups + tags []string + middlewares []func(http.Handler) http.Handler basePath string @@ -329,3 +334,31 @@ func WithValidator(newValidator *validator.Validate) func(*Server) { v = newValidator } } + +// Replaces Tags for the Server (i.e Group) +// By default, the tag is the type of the response body. +func (s *Server) Tags(tags ...string) *Server { + s.tags = tags + return s +} + +// AddTags adds tags from the Server (i.e Group) +// Tags from the parent Groups will be respected +func (s *Server) AddTags(tags ...string) *Server { + s.tags = append(s.tags, tags...) + return s +} + +// RemoveTags removes tags from the Server (i.e Group) +// if the parent Group(s) has matching tags they will be removed +func (s *Server) RemoveTags(tags ...string) *Server { + for _, tag := range tags { + for i, t := range s.tags { + if t == tag { + s.tags = slices.Delete(s.tags, i, i+1) + break + } + } + } + return s +} diff --git a/options_test.go b/options_test.go index e38061eb..681ad13a 100644 --- a/options_test.go +++ b/options_test.go @@ -271,3 +271,27 @@ func TestWithPort(t *testing.T) { require.Equal(t, "localhost:9999", s.Server.Addr) }) } + +func TestServerTags(t *testing.T) { + s := NewServer(). + Tags("my-server-tag") + + require.Equal(t, s.tags, []string{"my-server-tag"}) +} + +func TestServerAddTags(t *testing.T) { + s := NewServer(). + AddTags("my-server-tag"). + AddTags("my-other-server-tag") + + require.Equal(t, s.tags, []string{"my-server-tag", "my-other-server-tag"}) +} + +func TestServerRemoveTags(t *testing.T) { + s := NewServer(). + Tags("my-server-tag"). + AddTags("my-other-server-tag"). + RemoveTags("my-other-server-tag") + + require.Equal(t, s.tags, []string{"my-server-tag"}) +}