go-app/pkg/app/setup_custom.go

108 lines
2.7 KiB
Go

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
}