ctxval

package
v0.56.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2026 License: MIT Imports: 2 Imported by: 0

README

ctxval

Type-safe context values without sentinel keys or type assertions.

// Before: sentinel key + WithValue + type assertion + nil check
type contextKey struct{}
var requestIDKey = contextKey{}

ctx = context.WithValue(ctx, requestIDKey, "req-123")

reqID, ok := ctx.Value(requestIDKey).(string)
if !ok {
    reqID = "unknown"
}

// After
type RequestID string

ctx = ctxval.With(ctx, RequestID("req-123"))

reqID := ctxval.Get[RequestID](ctx).Or("unknown")

Seven lines become two. No sentinel type to define, no type assertion to get wrong, no nil check to forget.

What It Looks Like

// Store a value by its type
ctx := ctxval.With(ctx, RequestID("req-123"))

// Retrieve with fallback
reqID := ctxval.Get[RequestID](ctx).Or("unknown")
// Multiple named types coexist — each type is its own key
type RequestID string
type TraceID string

ctx = ctxval.With(ctx, RequestID("req-123"))
ctx = ctxval.With(ctx, TraceID("trace-abc"))
reqID := ctxval.Get[RequestID](ctx)  // Option("req-123")
trID  := ctxval.Get[TraceID](ctx)    // Option("trace-abc")
// Comma-ok extraction when you need to branch
if user, ok := ctxval.Get[User](ctx).Get(); ok {
    log.Printf("request from %s", user.Name)
}
// Named keys — when multiple values of the same type coexist
var authTokenKey = ctxval.NewKey[string]()
var csrfTokenKey = ctxval.NewKey[string]()

ctx = authTokenKey.With(ctx, "bearer-xyz")
ctx = csrfTokenKey.With(ctx, "csrf-abc")
auth, _ := authTokenKey.From(ctx).Get()  // "bearer-xyz"
csrf, _ := csrfTokenKey.From(ctx).Get()  // "csrf-abc"

When to Use What

Type-keyed (With/Get): When each Go type naturally maps to one value per context. Middleware injecting a User, RequestID, or TraceID — one value per type, no collision possible.

Named keys (NewKey/Key.With/Key.From): When multiple values share the same underlying type at a shared API boundary. Two different string tokens, two different int IDs. Each NewKey call creates a unique key by pointer identity.

Type aliases (type Alias = string) share the key with the aliased type. Named types (type RequestID string) get their own key.

Operations

  • Store: With[T](ctx, val) — store by type; Key.With(ctx, val) — store by named key
  • Retrieve: Get[T](ctx) — returns Option[T]; Key.From(ctx) — returns Option[T]
  • Keys: NewKey[T]() — create unique named key (pointer identity)

Get returns an Option, so all option operations apply: .Or("default"), .Get(), .IsOk(), .IfOk(fn).

See pkg.go.dev for complete API documentation and the orders example for a full integration demo with middleware.

Documentation

Overview

Package ctxval provides typed helpers for storing and retrieving request-scoped values in context.Context.

Values are keyed by their Go type. Each type can have at most one value in a context. Use named types to distinguish values of the same underlying type. Type aliases (=) share the same key as the aliased type:

type RequestID string
type TraceID string

ctx = ctxval.With(ctx, RequestID("abc"))
ctx = ctxval.With(ctx, TraceID("xyz"))
reqID := ctxval.Get[RequestID](ctx)  // Option[RequestID]("abc")
trID  := ctxval.Get[TraceID](ctx)    // Option[TraceID]("xyz")

For code that only needs one value per type (e.g., middleware injecting a User into context), the package-level With and Get functions are convenient. For shared or public boundaries where multiple values of the same type coexist, prefer Key — it gives each value a unique identity without requiring a new named type:

var userKey = ctxval.NewKey[User]()

ctx = userKey.With(ctx, currentUser)
userOpt := userKey.From(ctx)  // Option[User]

This package is for request-scoped data that crosses API boundaries (user IDs, trace IDs, auth tokens). It is not for optional parameters, dependency injection, or service locators.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Get added in v0.56.0

func Get[T any](ctx context.Context) option.Option[T]

Get retrieves the value of type T from ctx, returning a not-ok option if no value of that type is present. T must match the exact static type used in With; interface and concrete types are distinct keys.

Panics if ctx is nil (same as context.Context.Value).

func With

func With[T any](ctx context.Context, val T) context.Context

With returns a child context carrying val, keyed by its type T. A subsequent call with the same T shadows the parent's value.

For distinct semantic keys of the same type, use NewKey instead.

Panics if ctx is nil (same as context.WithValue).

Types

type Key

type Key[T any] struct {
	// contains filtered or unexported fields
}

Key is a named context key for values of type T. Unlike With/Get, which key by type alone, each Key is a unique identity — multiple keys can carry the same type T without collision.

Typically create with NewKey. Declare as package-level variables.

func NewKey

func NewKey[T any]() *Key[T]

NewKey returns a new unique key for values of type T. Each call returns a distinct key (pointer identity).

func (*Key[T]) From

func (k *Key[T]) From(ctx context.Context) option.Option[T]

Get retrieves the value under this key from ctx, returning a not-ok option if not present.

Panics if k is nil (use NewKey to create keys). Panics if ctx is nil (same as context.Context.Value).

func (*Key[T]) With

func (k *Key[T]) With(ctx context.Context, val T) context.Context

With returns a child context carrying val under this key.

Panics if k is nil (use NewKey to create keys). Panics if ctx is nil (same as context.WithValue).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL