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 }