Documentation
¶
Overview ¶
Package contextx provides business-facing request context utilities for Aisphere Kernel.
contextx is the ONLY package that business code should use for request-scoped values: request_id, trace_id, principal, tenant, and the request-bound logger. It works with logx (FromContext) and errorx (WithRequestID / WithTraceID) without creating import cycles.
Design principle ¶
contextx only DEFINES request-scoped value injection and extraction. It does NOT do logging (use logx), NOT do error wrapping (use errorx), NOT do tracing span management (use contrib/otel/tracing). It only carries values through context.Context.
contextx depends only on the Go standard library + logx (for the Logger type alias). It does NOT import errorx, otel, or any transport package.
30-second quickstart ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = contextx.WithRequestID(ctx, requestIDFromHeader(r))
ctx = contextx.WithTraceID(ctx, traceIDFromHeaders(r))
ctx = contextx.WithPrincipal(ctx, &contextx.Principal{
SubjectID: "u_123",
TenantID: "t_acme",
Roles: []string{"admin"},
})
ctx = contextx.WithLogger(ctx, logger)
// Downstream code can extract:
rid := contextx.RequestIDFromContext(ctx)
p := contextx.PrincipalFromContext(ctx)
logger := contextx.LoggerFromContext(ctx)
}
Value types ¶
contextx.WithRequestID(ctx, "req_abc") → contextx.RequestIDFromContext(ctx)
contextx.WithTraceID(ctx, "trace_xyz") → contextx.TraceIDFromContext(ctx)
contextx.WithPrincipal(ctx, &Principal{...}) → contextx.PrincipalFromContext(ctx)
contextx.WithTenant(ctx, "t_acme") → contextx.TenantFromContext(ctx)
contextx.WithLogger(ctx, logger) → contextx.LoggerFromContext(ctx)
contextx.With(ctx, "custom_key", value) → contextx.From(ctx, "custom_key")
nil safety ¶
All `XxxFromContext(nil)` and `XxxFromContext(ctx)` (when value absent) return zero values, never panic. This is critical because business code calls these in hot paths.
Merge two contexts ¶
When you need to merge values from two contexts (e.g. parent request + child goroutine), use contextx.Merge:
merged, cancel := contextx.Merge(parentCtx, childCtx) defer cancel() // merged.Done() fires when EITHER parent fires
Further reading ¶
See contextx/README.md for the single-source-of-truth user guide, and docs/ai/contextx.md for the AI coding recipe.
Example (BusinessAnonymousRequest) ¶
Example_businessAnonymousRequest shows nil-safety: code that handles both authenticated and anonymous requests without nil-checks everywhere.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := context.Background() // no principal attached
// Safe to call directly — returns nil, never panics
p := contextx.PrincipalFromContext(ctx)
if p == nil || !p.IsAuthenticated() {
fmt.Println("anonymous request")
} else {
fmt.Printf("authenticated: %s\n", p.SubjectID)
}
// SubjectIDFromContext returns "" for anonymous
fmt.Printf("subject_id=%q\n", contextx.SubjectIDFromContext(ctx))
}
Output: anonymous request subject_id=""
Example (BusinessCustomValues) ¶
Example_businessCustomValues shows how to attach domain-specific values that don't fit the typed helpers.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := context.Background()
// Attach a feature flag
ctx = contextx.With(ctx, "feature_x_enabled", true)
ctx = contextx.With(ctx, "ab_test_variant", "control")
// Extract
if v, ok := contextx.From(ctx, "feature_x_enabled").(bool); ok && v {
fmt.Println("feature X enabled")
}
fmt.Println(contextx.FromString(ctx, "ab_test_variant"))
}
Output: feature X enabled control
Example (BusinessHandlerBoundary) ¶
Example_businessHandlerBoundary shows the standard pattern at HTTP handler entry: extract request_id / trace_id / principal from transport, attach to ctx in one call.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// Simulate handler entry
ctx := context.Background()
// In real code: reqID := r.Header.Get("X-Request-ID")
// In real code: principal := authn.Verify(r.Header.Get("Authorization"))
ctx = contextx.InjectRequestContext(ctx,
contextx.WithRequestIDOption("req_abc"),
contextx.WithTraceIDOption("trace_xyz"),
contextx.WithPrincipalOption(&contextx.Principal{
SubjectID: "u_123",
TenantID: "t_acme",
Roles: []string{"viewer"},
AuthMethod: "oauth",
}),
)
// All downstream code can extract:
fmt.Println(contextx.RequestIDFromContext(ctx))
fmt.Println(contextx.PrincipalFromContext(ctx).SubjectID)
}
Output: req_abc u_123
Example (BusinessLoggerPropagation) ¶
Example_businessLoggerPropagation shows how to fetch the request-scoped logger in deep repository code without prop-drilling.
// At handler boundary:
ctx := contextx.WithLogger(context.Background(), fakeLogger{})
// Deep in repository:
logger := contextx.LoggerFromContext(ctx)
if logger != nil {
logger.Info("querying skill")
}
fmt.Println("ok")
Output: ok
Example (BusinessRepositoryErrorx) ¶
Example_businessRepositoryErrorx shows how repository extracts request_id from context to attach to errorx errors.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithRequestID(context.Background(), "req_abc")
ctx = contextx.WithTraceID(ctx, "trace_xyz")
// Common pattern when returning errorx:
rid, tid := contextx.RequestIDAndTraceIDFromContext(ctx)
fmt.Println(rid)
fmt.Println(tid)
// In real code:
// return errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "skill not found",
// errorx.WithRequestID(rid),
// errorx.WithTraceID(tid),
// )
Output:
Example (BusinessScopeAuthz) ¶
Example_businessScopeAuthz shows OAuth scope-based authorization.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{
SubjectID: "svc:agentkit",
Scopes: []string{"skill:read", "skill:download"},
})
p := contextx.PrincipalFromContext(ctx)
requiredScope := "skill:write"
if !p.HasScope(requiredScope) {
fmt.Printf("denied: missing scope %s\n", requiredScope)
// In real code: return errorx.Forbidden("AIHUB_SCOPE_DENIED", "missing scope",
// errorx.WithMetadata("required_scope", requiredScope),
// )
} else {
fmt.Println("allowed")
}
}
Output: denied: missing scope skill:write
Example (BusinessServiceAuthz) ¶
Example_businessServiceAuthz shows service-layer permission check using Principal from context.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{
SubjectID: "u_123",
Roles: []string{"admin"},
})
// In real code: if !contextx.PrincipalFromContext(ctx).HasRole("admin") { return errorx.Forbidden(...) }
p := contextx.PrincipalFromContext(ctx)
if p.HasRole("admin") {
fmt.Println("allowed")
} else {
fmt.Println("denied")
}
}
Output: allowed
Example (BusinessTenantFallback) ¶
Example_businessTenantFallback shows the tenant resolution priority: explicit WithTenant > Principal.TenantID > empty.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// Case 1: explicit tenant wins
ctx1 := contextx.WithPrincipal(context.Background(), &contextx.Principal{TenantID: "t_principal"})
ctx1 = contextx.WithTenant(ctx1, "t_explicit")
fmt.Println(contextx.TenantFromContext(ctx1))
// Case 2: falls back to principal tenant
ctx2 := contextx.WithPrincipal(context.Background(), &contextx.Principal{TenantID: "t_principal"})
fmt.Println(contextx.TenantFromContext(ctx2))
// Case 3: no tenant anywhere
ctx3 := context.Background()
fmt.Println(contextx.TenantFromContext(ctx3) == "")
}
Output: t_explicit t_principal true
Example (BusinessTenantIsolation) ¶
Example_businessTenantIsolation shows how to enforce tenant isolation in repository queries using TenantFromContext.
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithTenant(context.Background(), "t_acme")
tenantID := contextx.TenantFromContext(ctx)
if tenantID == "" {
fmt.Println("no tenant — single-tenant mode")
} else {
fmt.Printf("querying for tenant=%s\n", tenantID)
}
// In real code: db.Where("tenant_id = ?", tenantID).Find(&rows)
Output:
Example (BusinessWorkerMergedContext) ¶
Example_businessWorkerMergedContext shows a background worker that merges a job context with a parent request context (e.g. for trace propagation).
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
parentCtx := contextx.WithTraceID(context.Background(), "trace_parent")
jobCtx := contextx.WithRequestID(context.Background(), "req_job")
merged, cancel := contextx.Merge(parentCtx, jobCtx)
defer cancel()
// Merged inherits values from both parents
fmt.Println(contextx.TraceIDFromContext(merged)) // from parent1
fmt.Println(contextx.RequestIDFromContext(merged)) // from parent2
}
Output: trace_parent req_job
Index ¶
- func AuthzDecisionIDFromContext(ctx context.Context) string
- func From(ctx context.Context, key string) any
- func FromString(ctx context.Context, key string) string
- func GetDependency[T any](sc *ServiceContext) (T, bool)
- func GetDependencyFromContext[T any](ctx context.Context) (T, bool)
- func InjectRequestContext(ctx context.Context, opts ...RequestContextOption) context.Context
- func InternalTokenFromContext(ctx context.Context) string
- func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFunc)
- func MustGetDependency[T any](sc *ServiceContext) T
- func MustGetDependencyFromContext[T any](ctx context.Context) T
- func MustPutDependency(sc *ServiceContext, dep any)
- func PutDependency(sc *ServiceContext, dep any) error
- func RequestIDAndTraceIDFromContext(ctx context.Context) (requestID, traceID string)
- func RequestIDFromContext(ctx context.Context) string
- func ScopedTokenFromContext(ctx context.Context) string
- func SubjectIDFromContext(ctx context.Context) string
- func TenantFromContext(ctx context.Context) string
- func TraceIDFromContext(ctx context.Context) string
- func With(ctx context.Context, key string, value any) context.Context
- func WithAuthnPrincipal(ctx context.Context, p authn.Principal) context.Context
- func WithAuthzDecisionID(ctx context.Context, decisionID string) context.Context
- func WithInternalToken(ctx context.Context, token string) context.Context
- func WithLogger(ctx context.Context, logger Logger) context.Context
- func WithPrincipal(ctx context.Context, p *Principal) context.Context
- func WithRequestID(ctx context.Context, requestID string) context.Context
- func WithScopedToken(ctx context.Context, token string) context.Context
- func WithServiceContext(ctx context.Context, sc *ServiceContext) context.Context
- func WithTenant(ctx context.Context, tenantID string) context.Context
- func WithTraceID(ctx context.Context, traceID string) context.Context
- type Field
- type LogLevel
- type Logger
- type Principal
- type RequestContextOption
- type ServiceContext
Examples ¶
- Package (BusinessAnonymousRequest)
- Package (BusinessCustomValues)
- Package (BusinessHandlerBoundary)
- Package (BusinessLoggerPropagation)
- Package (BusinessRepositoryErrorx)
- Package (BusinessScopeAuthz)
- Package (BusinessServiceAuthz)
- Package (BusinessTenantFallback)
- Package (BusinessTenantIsolation)
- Package (BusinessWorkerMergedContext)
- From
- FromString
- InjectRequestContext
- LoggerFromContext
- LoggerFromContextOr
- Merge
- Merge (NilParents)
- Principal.HasRole
- Principal.HasScope
- Principal.IsAuthenticated
- PrincipalFromContext
- RequestIDAndTraceIDFromContext
- RequestIDFromContext
- SubjectIDFromContext
- TenantFromContext (ExplicitWins)
- TenantFromContext (FallbackToPrincipal)
- TraceIDFromContext (Nil)
- With
- WithLogger
- WithPrincipal
- WithRequestID
- WithTenant
- WithTraceID
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AuthzDecisionIDFromContext ¶ added in v0.1.10
AuthzDecisionIDFromContext returns the trusted authorization decision id.
func From ¶
From returns a custom value attached via With. Returns nil if absent.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// Returns nil when key absent
fmt.Println(contextx.From(context.Background(), "missing"))
}
Output: <nil>
func FromString ¶
FromString is a typed convenience for From returning string.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.With(context.Background(), "request_source", "api")
fmt.Println(contextx.FromString(ctx, "request_source"))
fmt.Println(contextx.FromString(ctx, "missing"))
}
Output: api
func GetDependency ¶ added in v0.1.10
func GetDependency[T any](sc *ServiceContext) (T, bool)
GetDependency fetches a dependency by type from the service context.
func GetDependencyFromContext ¶ added in v0.1.10
GetDependencyFromContext fetches a dependency by type from the ServiceContext stored in ctx.
func InjectRequestContext ¶
func InjectRequestContext(ctx context.Context, opts ...RequestContextOption) context.Context
InjectRequestContext is a convenience that attaches request_id, trace_id, principal, tenant, and logger in one call. Common at handler boundaries.
logger, _ := logx.New(cfg)
ctx = contextx.InjectRequestContext(ctx,
contextx.WithRequestIDOption(reqID),
contextx.WithTraceIDOption(traceID),
contextx.WithPrincipalOption(principal),
contextx.WithLoggerOption(logger),
)
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
// fakeLogger satisfies contextx.Logger for examples (in real code, pass logx.Logger)
type fakeLogger struct{}
func (fakeLogger) Debug(string, ...contextx.Field) {}
func (fakeLogger) Info(string, ...contextx.Field) {}
func (fakeLogger) Warn(string, ...contextx.Field) {}
func (fakeLogger) Error(string, ...contextx.Field) {}
func (l fakeLogger) With(...contextx.Field) contextx.Logger { return l }
func (l fakeLogger) Named(string) contextx.Logger { return l }
func (l fakeLogger) WithContext(context.Context) contextx.Logger { return l }
func (fakeLogger) Enabled(contextx.LogLevel) bool { return true }
func (fakeLogger) Sync() error { return nil }
func main() {
ctx := context.Background()
// Common handler-boundary pattern: attach everything in one call
ctx = contextx.InjectRequestContext(ctx,
contextx.WithRequestIDOption("req_abc"),
contextx.WithTraceIDOption("trace_xyz"),
contextx.WithPrincipalOption(&contextx.Principal{
SubjectID: "u_123",
TenantID: "t_acme",
}),
contextx.WithLoggerOption(fakeLogger{}),
)
// All values are now attached:
fmt.Println(contextx.RequestIDFromContext(ctx))
fmt.Println(contextx.TraceIDFromContext(ctx))
fmt.Println(contextx.PrincipalFromContext(ctx).SubjectID)
fmt.Println(contextx.TenantFromContext(ctx))
fmt.Println(contextx.LoggerFromContext(ctx) != nil)
}
Output: req_abc trace_xyz u_123 t_acme true
func InternalTokenFromContext ¶ added in v0.1.10
InternalTokenFromContext returns an internal token previously attached by WithInternalToken.
func Merge ¶
Merge merges two contexts into one. The merged context:
- Is done when EITHER parent is done, OR when the returned CancelFunc is called
- Returns the earliest of the two parents' deadlines (if both have one)
- Looks up values from parent1 first, then parent2
The returned CancelFunc must be called to release the internal goroutine. Calling it after the merged context is already done is a no-op.
Usage:
merged, cancel := contextx.Merge(requestCtx, backgroundCtx) defer cancel() // merged.Done() fires when requestCtx is cancelled OR cancel() is called
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
parent1, cancel1 := context.WithCancel(context.Background())
defer cancel1()
parent2 := context.Background()
merged, cancel := contextx.Merge(parent1, parent2)
defer cancel()
// Merged inherits values from parent1 first, then parent2
merged = contextx.WithRequestID(merged, "req_abc")
fmt.Println(contextx.RequestIDFromContext(merged))
// Cancelling parent1 cancels merged
cancel1()
fmt.Println(merged.Err() != nil)
}
Output: req_abc true
Example (NilParents) ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// nil parents are treated as context.Background()
merged, cancel := contextx.Merge(nil, nil)
defer cancel()
fmt.Println(merged.Err())
}
Output: <nil>
func MustGetDependency ¶ added in v0.1.10
func MustGetDependency[T any](sc *ServiceContext) T
MustGetDependency fetches a dependency by type or panics.
func MustGetDependencyFromContext ¶ added in v0.1.10
MustGetDependencyFromContext fetches a dependency from ctx or panics.
func MustPutDependency ¶ added in v0.1.10
func MustPutDependency(sc *ServiceContext, dep any)
MustPutDependency registers dep by concrete type and panics on programmer error. Prefer this during service startup.
func PutDependency ¶ added in v0.1.10
func PutDependency(sc *ServiceContext, dep any) error
PutDependency registers dep by concrete type in the service context.
func RequestIDAndTraceIDFromContext ¶
RequestIDAndTraceIDFromContext returns both request_id and trace_id in one call. Common when constructing errorx errors:
rid, tid := contextx.RequestIDAndTraceIDFromContext(ctx)
return errorx.NotFound("CODE", "msg",
errorx.WithRequestID(rid),
errorx.WithTraceID(tid),
)
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithRequestID(context.Background(), "req_abc")
ctx = contextx.WithTraceID(ctx, "trace_xyz")
// One call for both — common when building errorx errors:
rid, tid := contextx.RequestIDAndTraceIDFromContext(ctx)
fmt.Println(rid)
fmt.Println(tid)
}
Output: req_abc trace_xyz
func RequestIDFromContext ¶
RequestIDFromContext returns the request ID attached via WithRequestID. Returns "" if ctx is nil or no request ID is attached. Never panics.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// With value
ctx := contextx.WithRequestID(context.Background(), "req_abc")
fmt.Println(contextx.RequestIDFromContext(ctx))
// Without value
fmt.Println(contextx.RequestIDFromContext(context.Background()))
// nil context (safe)
fmt.Println(contextx.RequestIDFromContext(nil) == "")
}
Output: req_abc true
func ScopedTokenFromContext ¶ added in v0.1.10
ScopedTokenFromContext returns a scoped token previously attached by WithScopedToken.
func SubjectIDFromContext ¶
SubjectIDFromContext is a convenience helper returning Principal.SubjectID (or "" if no principal). Common in log fields and audit records.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{SubjectID: "u_123"})
fmt.Println(contextx.SubjectIDFromContext(ctx))
// Empty when no principal
fmt.Println(contextx.SubjectIDFromContext(context.Background()))
}
Output: u_123
func TenantFromContext ¶
TenantFromContext returns the tenant ID attached via WithTenant. Falls back to PrincipalFromContext(ctx).TenantID if no explicit tenant. Returns "" if neither is set. Never panics.
Example (ExplicitWins) ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// Explicit WithTenant wins over Principal.TenantID
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{
TenantID: "t_from_principal",
})
ctx = contextx.WithTenant(ctx, "t_explicit")
fmt.Println(contextx.TenantFromContext(ctx))
}
Output: t_explicit
Example (FallbackToPrincipal) ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// When no explicit tenant, falls back to Principal.TenantID
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{
SubjectID: "u_123",
TenantID: "t_from_principal",
})
fmt.Println(contextx.TenantFromContext(ctx))
}
Output: t_from_principal
func TraceIDFromContext ¶
TraceIDFromContext returns the trace ID attached via WithTraceID. Returns "" if ctx is nil or no trace ID is attached. Never panics.
Example (Nil) ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// nil-safe: returns "" instead of panicking
fmt.Println(contextx.TraceIDFromContext(nil) == "")
}
Output: true
func With ¶
With attaches a custom key-value pair to ctx. Use only when the typed helpers above don't fit. Prefer typed helpers for type safety.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.With(context.Background(), "custom_key", "custom_value")
fmt.Println(contextx.From(ctx, "custom_key"))
}
Output: custom_value
func WithAuthnPrincipal ¶ added in v0.1.10
WithAuthnPrincipal mirrors an authn.Principal into contextx.Principal. This keeps old authn-aware middleware and new contextx-aware business code aligned during the Gateway/BFF migration.
func WithAuthzDecisionID ¶ added in v0.1.10
WithAuthzDecisionID attaches the authorization decision id produced by IAM or an authz guard. Incoming clients must not be allowed to set this directly; it should be created by a trusted Gateway/service boundary.
func WithInternalToken ¶ added in v0.1.10
WithInternalToken attaches a service/internal token to ctx. It is used when a service identity token is pre-issued outside of a client interceptor.
func WithLogger ¶
WithLogger attaches a Logger to ctx. Use this at the handler boundary so downstream code can fetch it via LoggerFromContext without prop-drilling.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
// fakeLogger satisfies contextx.Logger for examples (in real code, pass logx.Logger)
type fakeLogger struct{}
func (fakeLogger) Debug(string, ...contextx.Field) {}
func (fakeLogger) Info(string, ...contextx.Field) {}
func (fakeLogger) Warn(string, ...contextx.Field) {}
func (fakeLogger) Error(string, ...contextx.Field) {}
func (l fakeLogger) With(...contextx.Field) contextx.Logger { return l }
func (l fakeLogger) Named(string) contextx.Logger { return l }
func (l fakeLogger) WithContext(context.Context) contextx.Logger { return l }
func (fakeLogger) Enabled(contextx.LogLevel) bool { return true }
func (fakeLogger) Sync() error { return nil }
func main() {
ctx := contextx.WithLogger(context.Background(), fakeLogger{})
fmt.Println(contextx.LoggerFromContext(ctx) != nil)
}
Output: true
func WithPrincipal ¶
WithPrincipal attaches a Principal to ctx. Pass nil to clear.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{
SubjectID: "u_123",
TenantID: "t_acme",
Roles: []string{"admin", "viewer"},
Scopes: []string{"skill:read", "skill:write"},
AuthMethod: "oauth",
})
p := contextx.PrincipalFromContext(ctx)
fmt.Println(p.SubjectID)
fmt.Println(p.TenantID)
fmt.Println(p.AuthMethod)
}
Output: u_123 t_acme oauth
func WithRequestID ¶
WithRequestID attaches a request ID to ctx. Pass empty string to clear.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithRequestID(context.Background(), "req_abc")
fmt.Println(contextx.RequestIDFromContext(ctx))
}
Output: req_abc
func WithScopedToken ¶ added in v0.1.10
WithScopedToken attaches a short-lived IAM-scoped token to ctx. gRPC/HTTP client middleware can propagate this as the outgoing Authorization header.
func WithServiceContext ¶ added in v0.1.10
func WithServiceContext(ctx context.Context, sc *ServiceContext) context.Context
WithServiceContext attaches the application service context to ctx. Use it at Gateway/BFF and worker boundaries when code receives only context.Context but still needs access to shared clients/stores/guards.
func WithTenant ¶
WithTenant attaches a tenant ID to ctx. This is separate from Principal.TenantID because some background jobs run without a Principal but still need tenant isolation.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithTenant(context.Background(), "t_acme")
fmt.Println(contextx.TenantFromContext(ctx))
}
Output: t_acme
func WithTraceID ¶
WithTraceID attaches a trace ID to ctx.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
ctx := contextx.WithTraceID(context.Background(), "trace_xyz")
fmt.Println(contextx.TraceIDFromContext(ctx))
}
Output: trace_xyz
Types ¶
type Logger ¶
Logger is the Kernel logger interface accepted by contextx.
func LoggerFromContext ¶
LoggerFromContext returns the Logger attached via WithLogger. Returns nil if ctx is nil or no logger is attached. Callers should nil-check or use LoggerFromContextOr for a fallback.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
// fakeLogger satisfies contextx.Logger for examples (in real code, pass logx.Logger)
type fakeLogger struct{}
func (fakeLogger) Debug(string, ...contextx.Field) {}
func (fakeLogger) Info(string, ...contextx.Field) {}
func (fakeLogger) Warn(string, ...contextx.Field) {}
func (fakeLogger) Error(string, ...contextx.Field) {}
func (l fakeLogger) With(...contextx.Field) contextx.Logger { return l }
func (l fakeLogger) Named(string) contextx.Logger { return l }
func (l fakeLogger) WithContext(context.Context) contextx.Logger { return l }
func (fakeLogger) Enabled(contextx.LogLevel) bool { return true }
func (fakeLogger) Sync() error { return nil }
func main() {
// With logger
ctx := contextx.WithLogger(context.Background(), fakeLogger{})
fmt.Println(contextx.LoggerFromContext(ctx) != nil)
// Without logger (returns nil)
fmt.Println(contextx.LoggerFromContext(context.Background()))
// nil context (returns nil, never panics)
fmt.Println(contextx.LoggerFromContext(nil))
}
Output: true <nil> <nil>
func LoggerFromContextOr ¶
LoggerFromContextOr returns the attached Logger, or fallback if none. If fallback is also nil, returns nil. This is the safe pattern for code that needs a non-nil logger.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
// fakeLogger satisfies contextx.Logger for examples (in real code, pass logx.Logger)
type fakeLogger struct{}
func (fakeLogger) Debug(string, ...contextx.Field) {}
func (fakeLogger) Info(string, ...contextx.Field) {}
func (fakeLogger) Warn(string, ...contextx.Field) {}
func (fakeLogger) Error(string, ...contextx.Field) {}
func (l fakeLogger) With(...contextx.Field) contextx.Logger { return l }
func (l fakeLogger) Named(string) contextx.Logger { return l }
func (l fakeLogger) WithContext(context.Context) contextx.Logger { return l }
func (fakeLogger) Enabled(contextx.LogLevel) bool { return true }
func (fakeLogger) Sync() error { return nil }
func main() {
fallback := fakeLogger{}
// Returns attached logger when present
ctx := contextx.WithLogger(context.Background(), fakeLogger{})
fmt.Println(contextx.LoggerFromContextOr(ctx, fallback) != nil)
// Returns fallback when no attached logger
fmt.Println(contextx.LoggerFromContextOr(context.Background(), fallback) != nil)
// Returns nil when neither (safe)
fmt.Println(contextx.LoggerFromContextOr(context.Background(), nil))
}
Output: true true <nil>
type Principal ¶
type Principal struct {
// SubjectID is the authenticated user/service ID (e.g. "u_123", "svc:agentkit").
SubjectID string
// TenantID is the multi-tenant tenant ID (e.g. "t_acme"). Empty for single-tenant.
TenantID string
// Roles are the roles assigned to the subject (e.g. ["admin", "viewer"]).
Roles []string
// Scopes are OAuth scopes if applicable (e.g. ["skill:read", "skill:write"]).
Scopes []string
// AuthMethod is how the subject authenticated: "oauth", "apikey", "mtls", "dev_token".
AuthMethod string
}
Principal carries the authenticated identity for the current request. It is intentionally a struct (not interface) for stable field access. Transport middleware (httpx / grpcx) populates this from authn results.
func PrincipalFromContext ¶
PrincipalFromContext returns the Principal attached via WithPrincipal. Returns nil if ctx is nil or no principal is attached. Never panics.
Example ¶
package main
import (
"context"
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
// With principal
ctx := contextx.WithPrincipal(context.Background(), &contextx.Principal{SubjectID: "u_123"})
fmt.Println(contextx.PrincipalFromContext(ctx).SubjectID)
// Without principal (returns nil)
fmt.Println(contextx.PrincipalFromContext(context.Background()))
// nil context (returns nil, never panics)
fmt.Println(contextx.PrincipalFromContext(nil))
}
Output: u_123 <nil> <nil>
func (*Principal) HasRole ¶
HasRole reports whether the principal has the given role. Returns false if p is nil.
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
p := &contextx.Principal{Roles: []string{"admin", "viewer"}}
fmt.Println(p.HasRole("admin"))
fmt.Println(p.HasRole("superadmin"))
}
Output: true false
func (*Principal) HasScope ¶
HasScope reports whether the principal has the given scope. Returns false if p is nil.
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
p := &contextx.Principal{Scopes: []string{"skill:read", "skill:write"}}
fmt.Println(p.HasScope("skill:read"))
fmt.Println(p.HasScope("skill:delete"))
}
Output: true false
func (*Principal) IsAuthenticated ¶
IsAuthenticated reports whether SubjectID is non-empty.
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/contextx"
)
func main() {
authed := &contextx.Principal{SubjectID: "u_123"}
anon := &contextx.Principal{}
var nilP *contextx.Principal
fmt.Println(authed.IsAuthenticated())
fmt.Println(anon.IsAuthenticated())
fmt.Println(nilP.IsAuthenticated())
}
Output: true false false
type RequestContextOption ¶
type RequestContextOption func(*requestContextOpts)
RequestContextOption configures InjectRequestContext.
func WithLoggerOption ¶
func WithLoggerOption(l Logger) RequestContextOption
WithLoggerOption sets logger for InjectRequestContext.
func WithPrincipalOption ¶
func WithPrincipalOption(p *Principal) RequestContextOption
WithPrincipalOption sets principal for InjectRequestContext.
func WithRequestIDOption ¶
func WithRequestIDOption(id string) RequestContextOption
WithRequestIDOption sets request_id for InjectRequestContext.
func WithTenantOption ¶
func WithTenantOption(id string) RequestContextOption
WithTenantOption sets tenant_id for InjectRequestContext.
func WithTraceIDOption ¶
func WithTraceIDOption(id string) RequestContextOption
WithTraceIDOption sets trace_id for InjectRequestContext.
type ServiceContext ¶ added in v0.1.10
type ServiceContext = servicecontextx.Context
ServiceContext is Kernel's go-zero-style dependency handoff point. It is re-exported from contextx so generated handler/logic code has one stable import for request context values and application dependencies.
func MustNewServiceContext ¶ added in v0.1.10
func MustNewServiceContext() *ServiceContext
MustNewServiceContext creates a new dependency container and mirrors the go-zero bootstrap style used by generated services.
func NewServiceContext ¶ added in v0.1.10
func NewServiceContext() *ServiceContext
NewServiceContext creates a new dependency container.
func ServiceContextFromContext ¶ added in v0.1.10
func ServiceContextFromContext(ctx context.Context) (*ServiceContext, bool)
ServiceContextFromContext returns the service context attached to ctx.