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 ToAuthnPrincipal(p *Principal) authn.Principal
- 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 AttributeSet
- type Field
- type LogLevel
- type Logger
- type Principal
- func (p *Principal) Attribute(key string) any
- func (p *Principal) Expired(now time.Time) bool
- func (p *Principal) HasGroup(group string) bool
- func (p *Principal) HasRole(role string) bool
- func (p *Principal) HasScope(scope string) bool
- func (p *Principal) IsAnonymous() bool
- func (p *Principal) IsAuthenticated() bool
- func (p Principal) Normalize() 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 and then OrgID if no explicit tenant is attached. 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 ToAuthnPrincipal ¶ added in v0.4.1
ToAuthnPrincipal converts a contextx principal mirror back to authn.Principal. This is mainly useful for framework adapters that accept a generic contextx request context but need to call authn/authz APIs.
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 authn-aware middleware and contextx-aware business/framework code aligned without dropping identity fields.
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 AttributeSet ¶ added in v0.4.1
AttributeSet carries provider-specific claims or request identity metadata. Values should be JSON-serializable when they are persisted or sent to remote systems.
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 string
SubjectType string
Provider string
ExternalID string
Issuer string
Audience []string
TenantID string
OrgID string
AppID string
ProjectID string
Username string
Name string
Email string
Phone string
Roles []string
Groups []string
Scopes []string
AuthMethod string
Attributes AttributeSet
IssuedAt time.Time
ExpiresAt time.Time
}
Principal carries the authenticated identity for the current request.
authn.PrincipalFromContext(ctx) remains the authoritative authn entrypoint for new business code. contextx.Principal is a lossless, request-context mirror for generic framework packages and older contextx-aware business code.
func FromAuthnPrincipal ¶ added in v0.4.1
FromAuthnPrincipal converts authn.Principal to the contextx mirror type.
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) Attribute ¶ added in v0.4.1
Attribute returns a provider-specific attribute value.
func (*Principal) Expired ¶ added in v0.4.1
Expired reports whether the principal has an ExpiresAt timestamp in the past.
func (*Principal) HasGroup ¶ added in v0.4.1
HasGroup reports whether the principal belongs to the given group. Returns false if p is 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) IsAnonymous ¶ added in v0.4.1
IsAnonymous reports whether the principal is absent or unauthenticated.
func (*Principal) IsAuthenticated ¶
IsAuthenticated reports whether the principal represents an authenticated caller.
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.