package runtime import ( "encoding/json" "fmt" "regexp" "strconv" "strings" "gopkg.in/yaml.v3" ) type Decoder func(*KeyValue, map[string]any) error type Resolver func(map[string]any) error type Merge func(dst, src any) error type Option func(*options) type options struct { sources []Source decoder Decoder resolver Resolver merge Merge } func WithSource(s ...Source) Option { return func(o *options) { o.sources = s } } func WithDecoder(d Decoder) Option { return func(o *options) { o.decoder = d } } func WithResolveActualTypes(enableConvertToType bool) Option { return func(o *options) { o.resolver = newActualTypesResolver(enableConvertToType) } } func WithResolver(r Resolver) Option { return func(o *options) { o.resolver = r } } func WithMergeFunc(m Merge) Option { return func(o *options) { o.merge = m } } func defaultDecoder(src *KeyValue, target map[string]any) error { if src.Format == "" { keys := strings.Split(src.Key, ".") for i, key := range keys { if i == len(keys)-1 { target[key] = src.Value return nil } sub := make(map[string]any) target[key] = sub target = sub } return nil } switch strings.ToLower(src.Format) { case "json": return json.Unmarshal(src.Value, &target) case "yaml", "yml": return yaml.Unmarshal(src.Value, &target) } return fmt.Errorf("unsupported key: %s format: %s", src.Key, src.Format) } func newActualTypesResolver(enableConvertToType bool) func(map[string]any) error { return func(input map[string]any) error { mapper := mapper(input) return resolver(input, mapper, enableConvertToType) } } func defaultResolver(input map[string]any) error { mapper := mapper(input) return resolver(input, mapper, false) } func resolver(input map[string]any, mapper func(name string) string, toType bool) error { var resolve func(map[string]any) error resolve = func(sub map[string]any) error { for key, value := range sub { switch typed := value.(type) { case string: sub[key] = expand(typed, mapper, toType) case map[string]any: if err := resolve(typed); err != nil { return err } case []any: for i, item := range typed { switch child := item.(type) { case string: typed[i] = expand(child, mapper, toType) case map[string]any: if err := resolve(child); err != nil { return err } } } sub[key] = typed } } return nil } return resolve(input) } func mapper(input map[string]any) func(name string) string { return func(name string) string { args := strings.SplitN(strings.TrimSpace(name), ":", 2) if value, ok := readValue(input, args[0]); ok { s, _ := value.String() return s } if len(args) > 1 { return args[1] } return "" } } func convertToType(input string) any { if strings.HasPrefix(input, "\"") && strings.HasSuffix(input, "\"") { return strings.Trim(input, "\"") } if input == "true" || input == "false" { b, _ := strconv.ParseBool(input) return b } if strings.Contains(input, ".") { if f, err := strconv.ParseFloat(input, 64); err == nil { return f } } if i, err := strconv.ParseInt(input, 10, 64); err == nil { return i } return input } var placeholderRegexp = regexp.MustCompile(`\${(.*?)}`) func expand(s string, mapping func(string) string, toType bool) any { for _, match := range placeholderRegexp.FindAllStringSubmatch(s, -1) { if len(match) != 2 { continue } mapped := mapping(match[1]) if toType { return convertToType(mapped) } s = strings.ReplaceAll(s, match[0], mapped) } return s }