Repository: go-fuego/fuego

Issue:
Add way to set tags description
**Is your feature request related to a problem? Please describe.**
Fuego has possibility to parse and create global tags, but there isn'tway to set description for it like that.
```
tags:
  - name: group
    description: test descr
```
Description for group's tags helps make more clear doc

**Describe the solution you'd like**
One of ways is write value of group's option.Description into tags. 
Another way (maybe it's simpler) make another option method with argument map[string]string, where key - tag name and val - tag description


Discussion:
@EwenQuim: I guess for now you can hack like this: 

```go
for i, t := range e.OpenAPI.Description().Tags {
	if t.Name == tag.Name {
		e.OpenAPI.Description().Tags[i].Description = "my Description"
	}
}
```

--- 

Would you like an Engine Option like this?

```go
s := fuego.NewServer(
  fuego.WithEngineOptions(
    fuego.WithTags(openapi.Tag{
      Name: "my-group",
      Description: "my description,
    },
  )
)
```

@dylanhitt what do you think about this syntax?
@dylanhitt: I think making it a variadic would be cool like `fuego.WithTags(tag ...openapi.Tag)` @EwenQuim: @maks56893 are you OK with the implementation I proposed?@maks56893: @EwenQuim what do you think if fuego.Group will also support set tag description? I'm creating tags for routes groups and will be cool to set description near created group@EwenQuim: You mean in `option.Tags`?@maks56893: Yes, if it possible. Or make a separate method so as not to break the existing code@EwenQuim: Ok maybe an `option.TagInfo(name, description string)`@EwenQuim: Is the PR ok for you? 😁 

Solution patch:
diff --git a/option.go b/option.go
index 1ab28c3..b4479d4 100644
--- a/option.go
+++ b/option.go
@@ -299,6 +299,33 @@ func OptionTags(tags ...string) func(*BaseRoute) {
 	}
 }
 
+// OptionTagInfo adds a tag with name and description to the route and registers it in the global OpenAPI tags.
+// This ensures the tag appears in the OpenAPI spec with its description.
+func OptionTagInfo(name, description string) func(*BaseRoute) {
+	return func(r *BaseRoute) {
+		if !slices.Contains(r.Operation.Tags, name) {
+			r.Operation.Tags = append(r.Operation.Tags, name)
+		}
+
+		if r.OpenAPI.Description().Tags == nil {
+			r.OpenAPI.Description().Tags = openapi3.Tags{}
+		}
+
+		existingTag := r.OpenAPI.Description().Tags.Get(name)
+		if existingTag != nil {
+			if existingTag.Description == "" {
+				existingTag.Description = description
+			}
+			return
+		}
+
+		r.OpenAPI.Description().Tags = append(r.OpenAPI.Description().Tags, &openapi3.Tag{
+			Name:        name,
+			Description: description,
+		})
+	}
+}
+
 // OptionSummary adds a summary to the route.
 func OptionSummary(summary string) func(*BaseRoute) {
 	return func(r *BaseRoute) {
diff --git a/option/option.go b/option/option.go
index a1cb0b3..cc61b05 100644
--- a/option/option.go
+++ b/option/option.go
@@ -104,6 +104,13 @@ var Param = fuego.OptionParam
 // Tags adds one or more tags to the route.
 var Tags = fuego.OptionTags
 
+// TagInfo adds a tag with name and description to the route and registers it in the global OpenAPI tags.
+// This ensures the tag appears in the OpenAPI spec with its description.
+// Example:
+//
+//	TagInfo("users", "Operations related to user management")
+var TagInfo = fuego.OptionTagInfo
+
 // Summary adds a summary to the route.
 var Summary = fuego.OptionSummary
 



Test patch:
diff --git a/option_test.go b/option_test.go
index 4bf37f1..7448ad1 100644
--- a/option_test.go
+++ b/option_test.go
@@ -1073,3 +1073,230 @@ func TestOptionStripTrailingSlash(t *testing.T) {
 		require.Equal(t, "/test", route.Path)
 	})
 }
+
+func TestOptionTagInfo(t *testing.T) {
+	t.Run("adds tag with description to route and global tags", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		// Apply the TagInfo option
+		tagName := "users"
+		tagDescription := "Operations related to user management"
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+
+		// Check that the tag was added to the route operation
+		require.Contains(t, route.Operation.Tags, tagName)
+
+		// Check that the tag was added to the global OpenAPI tags with description
+		globalTag := openAPI.Description().Tags.Get(tagName)
+		require.NotNil(t, globalTag)
+		assert.Equal(t, tagName, globalTag.Name)
+		assert.Equal(t, tagDescription, globalTag.Description)
+	})
+
+	t.Run("does not duplicate tags on route", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		tagName := "users"
+		tagDescription := "Operations related to user management"
+
+		// Apply the TagInfo option twice
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+
+		// Check that the tag appears only once in the route operation
+		count := 0
+		for _, tag := range route.Operation.Tags {
+			if tag == tagName {
+				count++
+			}
+		}
+		require.Equal(t, 1, count)
+	})
+
+	t.Run("updates empty description in existing global tag", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Pre-add a tag without description
+		openAPI.Description().Tags = append(openAPI.Description().Tags, &openapi3.Tag{
+			Name: "users",
+		})
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		tagName := "users"
+		tagDescription := "Operations related to user management"
+
+		// Apply the TagInfo option
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+
+		// Check that the existing tag now has the description
+		globalTag := openAPI.Description().Tags.Get(tagName)
+		require.NotNil(t, globalTag)
+		assert.Equal(t, tagName, globalTag.Name)
+		assert.Equal(t, tagDescription, globalTag.Description)
+	})
+
+	t.Run("does not overwrite existing description", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		existingDescription := "Existing description"
+
+		// Pre-add a tag with description
+		openAPI.Description().Tags = append(openAPI.Description().Tags, &openapi3.Tag{
+			Name:        "users",
+			Description: existingDescription,
+		})
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		tagName := "users"
+		newDescription := "New description"
+
+		// Apply the TagInfo option
+		fuego.OptionTagInfo(tagName, newDescription)(route)
+
+		// Check that the existing description is preserved
+		globalTag := openAPI.Description().Tags.Get(tagName)
+		require.NotNil(t, globalTag)
+		assert.Equal(t, tagName, globalTag.Name)
+		require.Equal(t, existingDescription, globalTag.Description)
+	})
+
+	t.Run("handles multiple different tags", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		// Apply multiple different TagInfo options
+		fuego.OptionTagInfo("users", "User management operations")(route)
+		fuego.OptionTagInfo("products", "Product management operations")(route)
+		fuego.OptionTagInfo("orders", "Order management operations")(route)
+
+		// Check that all tags were added to the route operation
+		require.Contains(t, route.Operation.Tags, "users")
+		require.Contains(t, route.Operation.Tags, "products")
+		require.Contains(t, route.Operation.Tags, "orders")
+		require.Len(t, route.Operation.Tags, 3)
+
+		// Check that all tags were added to the global OpenAPI tags with descriptions
+		usersTag := openAPI.Description().Tags.Get("users")
+		require.NotNil(t, usersTag)
+		require.Equal(t, "users", usersTag.Name)
+		require.Equal(t, "User management operations", usersTag.Description)
+
+		productsTag := openAPI.Description().Tags.Get("products")
+		require.NotNil(t, productsTag)
+		require.Equal(t, "products", productsTag.Name)
+		require.Equal(t, "Product management operations", productsTag.Description)
+
+		ordersTag := openAPI.Description().Tags.Get("orders")
+		require.NotNil(t, ordersTag)
+		require.Equal(t, "orders", ordersTag.Name)
+		require.Equal(t, "Order management operations", ordersTag.Description)
+	})
+
+	t.Run("initializes tags slice if nil", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Ensure tags is nil
+		openAPI.Description().Tags = nil
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		tagName := "users"
+		tagDescription := "Operations related to user management"
+
+		// Apply the TagInfo option
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+
+		// Check that tags slice was initialized and tag was added
+		require.NotNil(t, openAPI.Description().Tags)
+		globalTag := openAPI.Description().Tags.Get(tagName)
+		require.NotNil(t, globalTag)
+		assert.Equal(t, tagName, globalTag.Name)
+		assert.Equal(t, tagDescription, globalTag.Description)
+	})
+
+	t.Run("works with empty description", func(t *testing.T) {
+		// Create a new OpenAPI instance
+		openAPI := fuego.NewOpenAPI()
+
+		// Create a base route
+		route := &fuego.BaseRoute{
+			OpenAPI:   openAPI,
+			Operation: openapi3.NewOperation(),
+		}
+
+		tagName := "users"
+		tagDescription := ""
+
+		// Apply the TagInfo option with empty description
+		fuego.OptionTagInfo(tagName, tagDescription)(route)
+
+		// Check that the tag was added with empty description
+		require.Contains(t, route.Operation.Tags, tagName)
+		globalTag := openAPI.Description().Tags.Get(tagName)
+		require.NotNil(t, globalTag)
+		assert.Equal(t, tagName, globalTag.Name)
+		require.Empty(t, globalTag.Description)
+	})
+
+	t.Run("integration test with real route", func(t *testing.T) {
+		s := fuego.NewServer()
+
+		route := fuego.Get(s, "/test", helloWorld,
+			option.TagInfo("users", "Operations related to user management"),
+		)
+
+		// Check that the tag was added to the route operation
+		require.Contains(t, route.Operation.Tags, "users")
+
+		// Check that the tag was added to the global OpenAPI tags with description
+		globalTag := s.OpenAPI.Description().Tags.Get("users")
+		require.NotNil(t, globalTag)
+		require.Equal(t, "users", globalTag.Name)
+		require.Equal(t, "Operations related to user management", globalTag.Description)
+
+		// Test that the route still works
+		r := httptest.NewRequest(http.MethodGet, "/test", nil)
+		w := httptest.NewRecorder()
+
+		s.Mux.ServeHTTP(w, r)
+
+		require.Equal(t, "hello world", w.Body.String())
+	})
+}