feat: aggregate defaults and values

- Output merged defaults and values
- Add "check" command
This commit is contained in:
2022-09-27 22:20:44 +02:00
committed by Bornholm
parent 53b2bba28b
commit f4b3d8f532
26 changed files with 3229 additions and 65 deletions

91
internal/merge/merge.go Normal file
View File

@ -0,0 +1,91 @@
package merge
import (
"reflect"
"github.com/imdario/mergo"
"github.com/pkg/errors"
)
var (
ErrNonPointerDst = errors.New("dst is not a pointer")
ErrUnsupportedMerge = errors.New("unsupported merge")
ErrUnexpectedFailedCast = errors.New("unexpected failed cast")
)
func Merge(dst interface{}, sources ...interface{}) error {
if reflect.TypeOf(dst).Kind() != reflect.Ptr {
return errors.WithStack(ErrNonPointerDst)
}
dstPointedKind := reflect.Indirect(reflect.ValueOf(dst)).Elem().Kind()
for _, src := range sources {
srcKind := reflect.ValueOf(src).Kind()
switch dstPointedKind {
case reflect.Map:
if srcKind != dstPointedKind {
return errors.WithStack(unsupportedMergeError(dstPointedKind, srcKind))
}
if err := mergeMaps(dst, src); err != nil {
return errors.WithStack(err)
}
case reflect.Slice:
if srcKind != dstPointedKind {
return errors.WithStack(unsupportedMergeError(dstPointedKind, srcKind))
}
if err := mergeSlices(dst, src); err != nil {
return errors.WithStack(err)
}
default:
return errors.WithStack(unsupportedMergeError(dstPointedKind, srcKind))
}
}
return nil
}
func unsupportedMergeError(dstKind reflect.Kind, defaultsKind reflect.Kind) error {
return errors.Wrapf(ErrUnsupportedMerge, "could not merge '%s' with defaults '%s'", dstKind, defaultsKind)
}
func mergeMaps(dst interface{}, defaults interface{}) error {
dstMap, ok := reflect.Indirect(reflect.ValueOf(dst)).Elem().Interface().(map[string]interface{})
if !ok {
return errors.WithStack(ErrUnexpectedFailedCast)
}
defaultsMap, ok := defaults.(map[string]interface{})
if !ok {
return errors.WithStack(ErrUnexpectedFailedCast)
}
if err := mergo.Merge(&dstMap, defaultsMap, mergo.WithOverride); err != nil {
return errors.WithStack(err)
}
return nil
}
func mergeSlices(dst interface{}, defaults interface{}) error {
dstSlice, ok := reflect.Indirect(reflect.ValueOf(dst)).Elem().Interface().([]interface{})
if !ok {
return errors.WithStack(ErrUnexpectedFailedCast)
}
defaultsSlice, ok := defaults.([]interface{})
if !ok {
return errors.WithStack(ErrUnexpectedFailedCast)
}
if err := mergo.Merge(&dstSlice, defaultsSlice, mergo.WithOverride); err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@ -0,0 +1,69 @@
package merge
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
type mergeTestCase struct {
Name string
Dst interface{}
Dflts interface{}
ExpectedResult interface{}
ShouldFail bool
}
var mergeTestCases = []mergeTestCase{
{
Name: "simple-maps",
Dst: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "test",
},
},
Dflts: map[string]interface{}{
"other": true,
"foo": map[string]interface{}{
"bar": true,
"baz": 1,
},
},
ExpectedResult: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "test",
"baz": 1,
},
"other": true,
},
},
{
Name: "string-slices",
Dst: []string{"foo"},
Dflts: []string{"bar"},
ExpectedResult: []string{"foo"},
},
}
func TestMerge(t *testing.T) {
t.Parallel()
for _, tc := range mergeTestCases {
func(tc *mergeTestCase) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
err := Merge(&tc.Dst, tc.Dflts)
if tc.ShouldFail && err == nil {
t.Error("merge should have failed")
}
if !reflect.DeepEqual(tc.Dst, tc.ExpectedResult) {
t.Errorf("tc.Dst should have been the same as tc.ExpectedResult. Expected: %s, got %s", spew.Sdump(tc.ExpectedResult), spew.Sdump(tc.Dst))
}
})
}(&tc)
}
}