Files
galaxy-game/backend/internal/server/middleware/requestid/requestid.go
T
2026-05-06 10:14:55 +03:00

84 lines
2.6 KiB
Go

// Package requestid carries a per-request identifier across the gin handler
// chain.
//
// The middleware reads the inbound `X-Request-ID` header, generates a UUIDv4
// when absent, stores the value on the gin context, and reflects it on the
// response. Downstream code retrieves the identifier through FromContext.
package requestid
import (
"context"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Header is the canonical case-correct header name carrying the request id.
const Header = "X-Request-ID"
// ginContextKey is the gin.Context key under which the resolved request id is
// stored. The value is a string. The key is exported in lowercase form so that
// it never collides with handler-level keys; consumers should prefer
// FromContext rather than reading the gin context directly.
const ginContextKey = "backend.request_id"
// requestIDContextKey is the unexported context.Context key used when the
// resolved request id is propagated outside gin (background goroutines,
// downstream client calls). The unexported value type prevents accidental
// collisions across packages.
type requestIDContextKey struct{}
// Middleware returns the gin middleware that resolves and propagates the
// request id.
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := strings.TrimSpace(c.GetHeader(Header))
if requestID == "" {
requestID = uuid.NewString()
}
c.Set(ginContextKey, requestID)
c.Writer.Header().Set(Header, requestID)
c.Request = c.Request.WithContext(WithValue(c.Request.Context(), requestID))
c.Next()
}
}
// FromContext returns the request id stored on ctx by Middleware. The boolean
// reports whether an id was found. Consumers must always check the boolean
// before using the returned string.
func FromContext(ctx context.Context) (string, bool) {
if ctx == nil {
return "", false
}
value, ok := ctx.Value(requestIDContextKey{}).(string)
if !ok || value == "" {
return "", false
}
return value, true
}
// FromGin returns the request id stored on the gin context by Middleware. The
// boolean reports whether an id was found.
func FromGin(c *gin.Context) (string, bool) {
if c == nil {
return "", false
}
raw, ok := c.Get(ginContextKey)
if !ok {
return "", false
}
value, ok := raw.(string)
if !ok || value == "" {
return "", false
}
return value, true
}
// WithValue stores requestID on ctx under the package-private context key.
// Exposed primarily for tests that build a context outside the middleware.
func WithValue(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, requestIDContextKey{}, requestID)
}