187 lines
5.3 KiB
Go
187 lines
5.3 KiB
Go
// Package httpcontext provides type-safe context keys and helpers for HTTP request contexts.
|
|
//
|
|
// This package standardizes how context values are stored and retrieved across all services.
|
|
// Using unexported types for context keys prevents collisions with other packages.
|
|
//
|
|
// Usage in middleware:
|
|
//
|
|
// func AuthMiddleware() func(http.Handler) http.Handler {
|
|
// return func(next http.Handler) http.Handler {
|
|
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// user := extractUserFromAuth(r)
|
|
// ctx := httpcontext.SetUser(r.Context(), user)
|
|
// ctx = httpcontext.SetOrgID(ctx, user.OrganizationID)
|
|
// next.ServeHTTP(w, r.WithContext(ctx))
|
|
// })
|
|
// }
|
|
// }
|
|
//
|
|
// Usage in handlers:
|
|
//
|
|
// func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
|
// user, ok := httpcontext.GetUser(r.Context())
|
|
// if !ok {
|
|
// http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
// return
|
|
// }
|
|
// // ... use user
|
|
// }
|
|
package httpcontext
|
|
|
|
import "context"
|
|
|
|
// contextKey is an unexported type used for context keys to prevent collisions.
|
|
// Other packages cannot create values of this type, ensuring our keys are unique.
|
|
type contextKey string
|
|
|
|
// Standard context keys used across services.
|
|
const (
|
|
keyUser contextKey = "user"
|
|
keyOrgID contextKey = "organization_id"
|
|
keyRequestID contextKey = "request_id"
|
|
keyTraceID contextKey = "trace_id"
|
|
keyJWTClaims contextKey = "jwt_claims"
|
|
)
|
|
|
|
// SetUser adds a user to the context.
|
|
// The user can be any type - typically a domain user struct.
|
|
// Returns a new context with the user attached.
|
|
func SetUser(ctx context.Context, user any) context.Context {
|
|
if user == nil {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, keyUser, user)
|
|
}
|
|
|
|
// GetUser retrieves the user from context.
|
|
// Returns (user, true) if present, (nil, false) if not found.
|
|
// Caller should type-assert the returned value to their user type.
|
|
//
|
|
// Example:
|
|
//
|
|
// if val, ok := httpcontext.GetUser(ctx); ok {
|
|
// user := val.(*domain.User)
|
|
// // ... use user
|
|
// }
|
|
func GetUser(ctx context.Context) (any, bool) {
|
|
val := ctx.Value(keyUser)
|
|
if val == nil {
|
|
return nil, false
|
|
}
|
|
return val, true
|
|
}
|
|
|
|
// SetOrgID adds an organization ID to the context.
|
|
// Returns a new context with the organization ID attached.
|
|
func SetOrgID(ctx context.Context, orgID string) context.Context {
|
|
if orgID == "" {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, keyOrgID, orgID)
|
|
}
|
|
|
|
// GetOrgID retrieves the organization ID from context.
|
|
// Returns (orgID, true) if present, ("", false) if not found.
|
|
func GetOrgID(ctx context.Context) (string, bool) {
|
|
val := ctx.Value(keyOrgID)
|
|
if val == nil {
|
|
return "", false
|
|
}
|
|
if orgID, ok := val.(string); ok {
|
|
return orgID, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// SetRequestID adds a request ID to the context.
|
|
// Returns a new context with the request ID attached.
|
|
func SetRequestID(ctx context.Context, requestID string) context.Context {
|
|
if requestID == "" {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, keyRequestID, requestID)
|
|
}
|
|
|
|
// GetRequestID retrieves the request ID from context.
|
|
// Returns (requestID, true) if present, ("", false) if not found.
|
|
func GetRequestID(ctx context.Context) (string, bool) {
|
|
val := ctx.Value(keyRequestID)
|
|
if val == nil {
|
|
return "", false
|
|
}
|
|
if requestID, ok := val.(string); ok {
|
|
return requestID, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// SetTraceID adds a trace ID to the context.
|
|
// Returns a new context with the trace ID attached.
|
|
func SetTraceID(ctx context.Context, traceID string) context.Context {
|
|
if traceID == "" {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, keyTraceID, traceID)
|
|
}
|
|
|
|
// GetTraceID retrieves the trace ID from context.
|
|
// Returns (traceID, true) if present, ("", false) if not found.
|
|
func GetTraceID(ctx context.Context) (string, bool) {
|
|
val := ctx.Value(keyTraceID)
|
|
if val == nil {
|
|
return "", false
|
|
}
|
|
if traceID, ok := val.(string); ok {
|
|
return traceID, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// SetJWTClaims adds JWT claims to the context.
|
|
// The claims can be any type - typically a custom claims struct.
|
|
// Returns a new context with the claims attached.
|
|
func SetJWTClaims(ctx context.Context, claims any) context.Context {
|
|
if claims == nil {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, keyJWTClaims, claims)
|
|
}
|
|
|
|
// GetJWTClaims retrieves JWT claims from context.
|
|
// Returns (claims, true) if present, (nil, false) if not found.
|
|
// Caller should type-assert the returned value to their claims type.
|
|
//
|
|
// Example:
|
|
//
|
|
// if val, ok := httpcontext.GetJWTClaims(ctx); ok {
|
|
// claims := val.(*auth.CustomClaims)
|
|
// // ... use claims
|
|
// }
|
|
func GetJWTClaims(ctx context.Context) (any, bool) {
|
|
val := ctx.Value(keyJWTClaims)
|
|
if val == nil {
|
|
return nil, false
|
|
}
|
|
return val, true
|
|
}
|
|
|
|
// MustGetUser retrieves the user from context and panics if not found.
|
|
// Use only when authentication middleware guarantees user presence.
|
|
func MustGetUser(ctx context.Context) any {
|
|
user, ok := GetUser(ctx)
|
|
if !ok {
|
|
panic("httpcontext: user not found in context")
|
|
}
|
|
return user
|
|
}
|
|
|
|
// MustGetRequestID retrieves the request ID from context and panics if not found.
|
|
// Use only when middleware guarantees request ID presence.
|
|
func MustGetRequestID(ctx context.Context) string {
|
|
requestID, ok := GetRequestID(ctx)
|
|
if !ok {
|
|
panic("httpcontext: request_id not found in context")
|
|
}
|
|
return requestID
|
|
}
|