TwiN 3059e3e028
feat: Support multiple configuration files (#396)
* Revert "Revert "feat: Support multiple configuration files" (#395)"

This reverts commit 87740e74a6a352ab433c59d90edebda8014cacad.

* feat: Properly implement support for config directory
2023-01-08 17:53:37 -05:00

84 lines
2.4 KiB
Go

package deepmerge
import (
"errors"
"gopkg.in/yaml.v3"
)
var (
ErrDeepMergeDuplicatePrimitiveKey = errors.New("error deep merging YAML files due to duplicate primitive key: only maps and slices/arrays can be merged, which means you cannot have define the same key twice for parameters that are not maps or slices/arrays")
)
type Config struct {
// PreventDuplicateKeysWithPrimitiveValue causes YAML to return an error if dst and src define the same key if
// said key has a value with a primitive type
// This does not apply to slices or maps. Defaults to true
PreventDuplicateKeysWithPrimitiveValue bool
}
// YAML merges the contents of src into dst
func YAML(dst, src []byte, optionalConfig ...Config) ([]byte, error) {
var cfg Config
if len(optionalConfig) > 0 {
cfg = optionalConfig[0]
} else {
cfg = Config{PreventDuplicateKeysWithPrimitiveValue: true}
}
var dstMap, srcMap map[string]interface{}
err := yaml.Unmarshal(dst, &dstMap)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(src, &srcMap)
if err != nil {
return nil, err
}
if dstMap == nil {
dstMap = make(map[string]interface{})
}
if err = deepMerge(dstMap, srcMap, cfg); err != nil {
return nil, err
}
return yaml.Marshal(dstMap)
}
func deepMerge(dst, src map[string]interface{}, config Config) error {
for srcKey, srcValue := range src {
if srcValueAsMap, ok := srcValue.(map[string]interface{}); ok { // handle maps
if dstValue, ok := dst[srcKey]; ok {
if dstValueAsMap, ok := dstValue.(map[string]interface{}); ok {
err := deepMerge(dstValueAsMap, srcValueAsMap, config)
if err != nil {
return err
}
continue
}
} else {
dst[srcKey] = make(map[string]interface{})
}
err := deepMerge(dst[srcKey].(map[string]interface{}), srcValueAsMap, config)
if err != nil {
return err
}
} else if srcValueAsSlice, ok := srcValue.([]interface{}); ok { // handle slices
if dstValue, ok := dst[srcKey]; ok {
if dstValueAsSlice, ok := dstValue.([]interface{}); ok {
// If both src and dst are slices, we'll copy the elements from that src slice over to the dst slice
dst[srcKey] = append(dstValueAsSlice, srcValueAsSlice...)
continue
}
}
dst[srcKey] = srcValueAsSlice
} else { // handle primitives
if config.PreventDuplicateKeysWithPrimitiveValue {
if _, ok := dst[srcKey]; ok {
return ErrDeepMergeDuplicatePrimitiveKey
}
}
dst[srcKey] = srcValue
}
}
return nil
}