From 301c19afe17295c00d57d66ba3ee48ab91f5d794 Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Wed, 24 Dec 2025 11:09:57 -0500 Subject: [PATCH] fix custom type management --- pkg/app/setup_custom.go | 2 +- pkg/config/ctx.go | 45 ++++++++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/pkg/app/setup_custom.go b/pkg/app/setup_custom.go index daacb9d..1485553 100644 --- a/pkg/app/setup_custom.go +++ b/pkg/app/setup_custom.go @@ -17,7 +17,7 @@ import ( // that overloads *config.AppConfig. Will perform normal env // substitutions for AppConfig, but env overrides for custom type // are up to the caller. -func MustLoadConfigInto[T interface{ *config.AppConfig }](ctx context.Context, into T) (context.Context, T) { +func MustLoadConfigInto[T any](ctx context.Context, into T) (context.Context, T) { // Step 1: Check our custom type for required *config.AppConfig if err := HasAppConfig(into); err != nil { panic(err) diff --git a/pkg/config/ctx.go b/pkg/config/ctx.go index 1b19ca7..1a6b618 100644 --- a/pkg/config/ctx.go +++ b/pkg/config/ctx.go @@ -3,6 +3,7 @@ package config import ( "context" "errors" + "reflect" ) type appConfigKey uint8 @@ -15,22 +16,18 @@ func (ac *AppConfig) AddToCtx(ctx context.Context) context.Context { } // Add to Ctx for custom type that embeds AppConfig -func AddToContextFor[T interface { - *AppConfig -}](ctx context.Context, cfg T) context.Context { +func AddToContextFor[T any](ctx context.Context, cfg T) context.Context { return context.WithValue(ctx, appConfigCtxKey, cfg) } // FromCtxFor retrieves custom config that embeds AppConfig from context -func FromCtxFor[T interface { - *AppConfig -}](ctx context.Context) (T, error) { +func FromCtxFor[T any](ctx context.Context) (*T, error) { ctxData := ctx.Value(appConfigCtxKey) if ctxData == nil { return nil, errors.New("no config found in context") } - cfg, ok := ctxData.(T) + cfg, ok := ctxData.(*T) if !ok { return nil, errors.New("invalid config stored in context") } @@ -39,9 +36,7 @@ func FromCtxFor[T interface { } // MustFromCtxFor retrieves custom config that embeds AppConfig from context, or panics if not found. -func MustFromCtxFor[T interface { - *AppConfig -}](ctx context.Context) T { +func MustFromCtxFor[T any](ctx context.Context) *T { cfg, err := FromCtxFor[T](ctx) if err != nil { panic(err) @@ -59,16 +54,38 @@ func MustFromCtx(ctx context.Context) *AppConfig { } // FromCtx retrieves the AppConfig from the context. +// It handles both direct *AppConfig and custom types that embed *AppConfig. func FromCtx(ctx context.Context) (*AppConfig, error) { ctxData := ctx.Value(appConfigCtxKey) if ctxData == nil { return nil, errors.New("no config found in context") } - cfg, ok := ctxData.(*AppConfig) - if !ok { - return nil, errors.New("invalid config stored in context") + // Try direct type assertion first + if cfg, ok := ctxData.(*AppConfig); ok { + return cfg, nil } - return cfg, nil + // Try to extract *AppConfig from a custom type that embeds it + v := reflect.ValueOf(ctxData) + if v.Kind() != reflect.Pointer || v.IsNil() { + return nil, errors.New("invalid config stored in context: must be a non-nil pointer") + } + + v = v.Elem() + if v.Kind() != reflect.Struct { + return nil, errors.New("invalid config stored in context: must be a pointer to a struct") + } + + // Look for *AppConfig field + for i := range v.NumField() { + field := v.Field(i) + if field.Type() == reflect.TypeFor[*AppConfig]() { + if appConfig, ok := field.Interface().(*AppConfig); ok { + return appConfig, nil + } + } + } + + return nil, errors.New("no *AppConfig found in context value") }