package config import ( "errors" "fmt" "reflect" "strings" server "dynatron.me/x/blasphem/pkg/server/config" "gopkg.in/yaml.v3" ) type Config struct { DataDir *string `yaml:"data_dir,omitempty"` Server *server.Config `yaml:"server"` } const ( IncludeTag = "!include" IncludeDirNamedTag = "!include_dir_named" IncludeDirListTag = "!include_dir_list" IncludeDirMergeNamedTag = "!include_dir_merge_named" SecretTag = "!secret" ) func (c *Config) UnmarshalYAML(y *yaml.Node) error { if y.Kind != yaml.MappingNode { return yerr(y, errors.New("config root must be a map")) } cV := reflect.ValueOf(c).Elem() components := make(map[string]reflect.Value, cV.NumField()) for i := 0; i < cV.NumField(); i++ { if tag, ok := cV.Type().Field(i).Tag.Lookup("yaml"); ok { tags := strings.Split(tag, ",") components[tags[0]] = cV.Field(i) } } for i := 0; i < len(y.Content); i += 2 { k, v := y.Content[i], y.Content[i+1] switch v.LongTag() { case IncludeTag: fallthrough case IncludeDirNamedTag: fallthrough case IncludeDirMergeNamedTag: fallthrough case SecretTag: panic(fmt.Errorf("tag %s not implemented", v.LongTag())) default: var key string err := k.Decode(&key) if err != nil { return yerr(y, err) } if comp, ok := components[key]; ok { val := reflect.New(comp.Type()) err := v.Decode(val.Interface()) if err != nil { return yerr(y, err) } comp.Set(val.Elem()) } } } return nil } func yerr(y *yaml.Node, ye error) error { return fmt.Errorf("line %d: %w", y.Line, ye) }