mirror of
https://github.com/Bornholm/formidable.git
synced 2025-07-21 13:01:33 +02:00
feat: aggregate defaults and values
- Output merged defaults and values - Add "check" command
This commit is contained in:
91
internal/merge/merge.go
Normal file
91
internal/merge/merge.go
Normal 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
|
||||
}
|
69
internal/merge/merge_test.go
Normal file
69
internal/merge/merge_test.go
Normal 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user