Support custom types
This commit is contained in:
parent
70ba85bf79
commit
56037c4b05
@ -23,3 +23,10 @@ func MustSetupConfigAndLogging(ctx context.Context) context.Context {
|
|||||||
zerolog.Ctx(ctx).Trace().Any("config", *cfg).Send()
|
zerolog.Ctx(ctx).Trace().Any("config", *cfg).Send()
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unmarshal config into a custom type
|
||||||
|
// Type MUST include *config.AppConfig
|
||||||
|
// Stored in context as *config.AppConfig but can be asserted back
|
||||||
|
func MustSetupConfigAndLoggingInto[T any](ctx context.Context, into T) (context.Context, T) {
|
||||||
|
return ctx, into
|
||||||
|
}
|
||||||
|
107
pkg/app/setup_custom.go
Normal file
107
pkg/app/setup_custom.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Used to unmarshal config and environment into a custom type
|
||||||
|
// 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Do the normal thing
|
||||||
|
ctx = MustSetupConfigAndLogging(ctx)
|
||||||
|
|
||||||
|
// Step 3: Extract the config
|
||||||
|
cfg := config.MustFromCtx(ctx)
|
||||||
|
|
||||||
|
// Step 4: Unmarshal custom config
|
||||||
|
configPath := flag.Lookup("config")
|
||||||
|
if configPath != nil && configPath.Value.String() != "" {
|
||||||
|
file, err := os.Open(configPath.Value.String())
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("could not open config file: %w", err))
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
decoder := yaml.NewDecoder(file)
|
||||||
|
if err := decoder.Decode(into); err != nil {
|
||||||
|
panic(fmt.Errorf("could not decode config file: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Re-apply AppConfig to custom type
|
||||||
|
if err := setAppConfig(into, cfg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Update context, return custom type
|
||||||
|
return ctx, into
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAppConfig[T any](target T, appConfig *config.AppConfig) error {
|
||||||
|
// Ensure target is a pointer to a struct
|
||||||
|
v := reflect.ValueOf(target)
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return errors.New("target must be a non-nil pointer to a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v.Elem() // Dereference the pointer
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return errors.New("target must be a pointer to a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace *config.AppConfig
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
if field.Type() == reflect.TypeOf((*config.AppConfig)(nil)) {
|
||||||
|
if !field.CanSet() {
|
||||||
|
return fmt.Errorf("field %q cannot be set", v.Type().Field(i).Name)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(appConfig))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("no *config.AppConfig field found in target struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasAppConfig[T any](target T) error {
|
||||||
|
v := reflect.ValueOf(target)
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return errors.New("target must be a non-nil pointer to a struct")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return errors.New("target must be a pointer to a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAppConfig := false
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Type().Field(i)
|
||||||
|
if field.Type == reflect.TypeOf((*config.AppConfig)(nil)) {
|
||||||
|
hasAppConfig = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasAppConfig {
|
||||||
|
return errors.New("struct does not contain a *config.AppConfig field")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user