Files

168 lines
3.5 KiB
Go
Raw Permalink Normal View History

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
}