1 Commits

Author SHA1 Message Date
jay 03c783ba53 add try-extend variant 2026-05-10 09:59:22 -04:00
3 changed files with 63 additions and 34 deletions
+7 -5
View File
@@ -48,7 +48,7 @@ logger to carry `module` and `path` automatically.
func NewPool(ctx context.Context, id string, handler slog.Handler) (*Pool, error) { func NewPool(ctx context.Context, id string, handler slog.Handler) (*Pool, error) {
ctx = component.MustNew(ctx, "honeybee", "outbound_pool") ctx = component.MustNew(ctx, "honeybee", "outbound_pool")
c := component.FromContext(ctx) c, _ := component.Get(ctx)
logger := slog.New(handler).With(slog.Any("component", c), slog.String("pool_id", id)) logger := slog.New(handler).With(slog.Any("component", c), slog.String("pool_id", id))
return &Pool{ctx: ctx, logger: logger}, nil return &Pool{ctx: ctx, logger: logger}, nil
@@ -64,7 +64,7 @@ path. No parent identifiers need to be passed as arguments.
func NewWorker(ctx context.Context, id string, handler slog.Handler) (*Worker, error) { func NewWorker(ctx context.Context, id string, handler slog.Handler) (*Worker, error) {
ctx = component.MustExtend(ctx, "outbound_worker") ctx = component.MustExtend(ctx, "outbound_worker")
c := component.FromContext(ctx) c, _ := component.Get(ctx)
logger := slog.New(handler).With(slog.Any("component", c), slog.Any("peer_id", id)) logger := slog.New(handler).With(slog.Any("component", c), slog.Any("peer_id", id))
return &Worker{ctx: ctx, logger: logger}, nil return &Worker{ctx: ctx, logger: logger}, nil
@@ -76,17 +76,19 @@ At the connection layer, another `MustExtend` call extends the path to
### Usage Notes ### Usage Notes
**Error-Returning vs Panic Variants**: **Function Variants**:
- Use `New` and `Extend` when a missing or invalid component is a recoverable - Use `New` and `Extend` when a missing or invalid component is a recoverable
condition. condition.
- Use `MustNew` and `MustExtend` at library boundaries where a missing - Use `MustNew` and `MustExtend` at library boundaries where a missing
component is a programming error that should halt execution immediately. component is a programming error that should halt execution immediately.
- Use `TryExtend` if you only want to extend a component if it exists,
otherwise use the parent context as-is.
**Generic Output**: **Generic Output**:
`FieldsFromContext` provides the component fields as a `map[string]string` for non-slog `GetFields` provides the component fields as a `map[string]string` for non-slog
consumers such as OpenTelemetry or Prometheus. It returns `nil` if no component is present. consumers such as OpenTelemetry or Prometheus.
## Testing ## Testing
+30 -17
View File
@@ -42,25 +42,23 @@ func insert(ctx context.Context, module string, name string, path []string) cont
}) })
} }
// FromContext retrieves the current component from the context; returns nil if none is present. // Get retrieves the current component from the context; returns false if none is present.
func FromContext(ctx context.Context) Component { func Get(ctx context.Context) (Component, bool) {
c, ok := ctx.Value(storageKey).(component) t, ok := ctx.Value(storageKey).(component)
if !ok { return t, ok
return nil
}
return c
} }
// FieldsFromContext returns the component as a string map with keys "module" and "path"; returns nil if none is present. // GetFields returns the component as a string map with keys "module" and "path".
func FieldsFromContext(ctx context.Context) map[string]string { func GetFields(ctx context.Context) (map[string]string, bool) {
c := FromContext(ctx) c, ok := Get(ctx)
if c == nil { if !ok {
return nil return nil, false
} }
return map[string]string{ return map[string]string{
"module": c.Module(), "module": c.Module(),
"path": c.PathString(), "path": c.PathString(),
} }, true
} }
// New sets a new component on the context, resetting any existing component. // New sets a new component on the context, resetting any existing component.
@@ -83,8 +81,8 @@ func Extend(ctx context.Context, name string) (context.Context, error) {
return nil, fmt.Errorf("context is nil") return nil, fmt.Errorf("context is nil")
} }
c := FromContext(ctx) c, ok := Get(ctx)
if c == nil { if !ok {
return nil, fmt.Errorf("missing parent component") return nil, fmt.Errorf("missing parent component")
} }
@@ -95,6 +93,21 @@ func Extend(ctx context.Context, name string) (context.Context, error) {
return insert(ctx, c.Module(), name, c.Path()), nil return insert(ctx, c.Module(), name, c.Path()), nil
} }
// TryExtend returns ctx if no parent component is attached, otherwise
// extends the existing component path.
func TryExtend(ctx context.Context, name string) (context.Context, error) {
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
c, ok := Get(ctx)
if !ok || name == "" {
return ctx, nil
}
return insert(ctx, c.Module(), name, c.Path()), nil
}
// MustNew is New but panics on error. // MustNew is New but panics on error.
func MustNew(ctx context.Context, module string, name string) context.Context { func MustNew(ctx context.Context, module string, name string) context.Context {
if ctx == nil { if ctx == nil {
@@ -115,8 +128,8 @@ func MustExtend(ctx context.Context, name string) context.Context {
panic("context is nil") panic("context is nil")
} }
c := FromContext(ctx) c, ok := Get(ctx)
if c == nil { if !ok {
panic("missing parent component") panic("missing parent component")
} }
+26 -12
View File
@@ -22,8 +22,8 @@ func TestNew(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
c := FromContext(ctx) c, ok := Get(ctx)
if c == nil { if !ok {
t.Fatal("expected component in context") t.Fatal("expected component in context")
} }
if c.Module() != "mymodule" { if c.Module() != "mymodule" {
@@ -58,8 +58,8 @@ func TestExtend(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
c := FromContext(ctx) c, ok := Get(ctx)
if c == nil { if !ok {
t.Fatal("expected component in context") t.Fatal("expected component in context")
} }
if c.Module() != "mymodule" { if c.Module() != "mymodule" {
@@ -80,6 +80,17 @@ func TestExtend(t *testing.T) {
} }
}) })
t.Run("try passes through", func(t *testing.T) {
inCtx := context.Background()
outCtx, err := TryExtend(inCtx, "subcomponent")
if err != nil {
t.Fatal("unexpected error:", err)
}
if outCtx != inCtx {
t.Errorf("expected context to pass through")
}
})
t.Run("must variant should panic", func(t *testing.T) { t.Run("must variant should panic", func(t *testing.T) {
assertPanics(t, func() { assertPanics(t, func() {
MustExtend(context.Background(), "subcomponent") MustExtend(context.Background(), "subcomponent")
@@ -87,25 +98,28 @@ func TestExtend(t *testing.T) {
}) })
} }
func TestFromContext(t *testing.T) { func TestGet(t *testing.T) {
if FromContext(context.Background()) != nil { _, ok := Get(context.Background())
if ok {
t.Fatal("expected no component on bare context") t.Fatal("expected no component on bare context")
} }
ctx := MustNew(context.Background(), "mymodule", "mycomponent") ctx := MustNew(context.Background(), "mymodule", "mycomponent")
if FromContext(ctx) == nil { _, ok = Get(ctx)
if !ok {
t.Fatal("expected component in context") t.Fatal("expected component in context")
} }
} }
func TestFieldsFromContext(t *testing.T) { func TestGetFields(t *testing.T) {
if FieldsFromContext(context.Background()) != nil { _, ok := GetFields(context.Background())
if ok {
t.Fatal("expected no fields on bare context") t.Fatal("expected no fields on bare context")
} }
ctx := MustNew(context.Background(), "mymodule", "mycomponent") ctx := MustNew(context.Background(), "mymodule", "mycomponent")
fields := FieldsFromContext(ctx) fields, ok := GetFields(ctx)
if fields == nil { if !ok {
t.Fatal("expected fields in context") t.Fatal("expected fields in context")
} }
if fields["module"] != "mymodule" { if fields["module"] != "mymodule" {
@@ -118,7 +132,7 @@ func TestFieldsFromContext(t *testing.T) {
func TestLogValue(t *testing.T) { func TestLogValue(t *testing.T) {
ctx := MustNew(context.Background(), "mymodule", "mycomponent") ctx := MustNew(context.Background(), "mymodule", "mycomponent")
c := FromContext(ctx) c, _ := Get(ctx)
v := c.LogValue() v := c.LogValue()
attrs := v.Group() attrs := v.Group()
if len(attrs) != 2 { if len(attrs) != 2 {