Add a PropertyMap/Value.MapReplace function

This adds a handy MapReplace function on pkg/resource's PropertyMap and
PropertyValue types.  This is just like the existing Mappable function,
except that it permits easy replacement of elements as the map transformation
occurs.  We need this to perform float64=>int transformations.
This commit is contained in:
joeduffy 2017-07-01 12:08:55 -07:00
parent 27b819dec6
commit 5d9f7918e9
2 changed files with 115 additions and 3 deletions

View file

@ -349,9 +349,15 @@ func (m PropertyMap) HasValue(k PropertyKey) bool {
// Mappable returns a mapper-compatible object map, suitable for deserialization into structures.
func (m PropertyMap) Mappable() map[string]interface{} {
return m.MapReplace(nil)
}
// MapReplace returns a mapper-compatible object map, suitable for deserialization into structures. A replace function
// repl may be passed that will replace elements using custom logic if appropriate.
func (m PropertyMap) MapReplace(repl func(PropertyValue) (interface{}, bool)) map[string]interface{} {
obj := make(map[string]interface{})
for _, k := range m.StableKeys() {
obj[string(k)] = m[k].Mappable()
obj[string(k)] = m[k].MapReplace(repl)
}
return obj
}
@ -640,6 +646,17 @@ func (v PropertyValue) TypeString() string {
// Mappable returns a mapper-compatible value, suitable for deserialization into structures.
func (v PropertyValue) Mappable() interface{} {
return v.MapReplace(nil)
}
// MapReplace returns a mapper-compatible object map, suitable for deserialization into structures. A replace function
// repl may be passed that will replace elements using custom logic if appropriate.
func (v PropertyValue) MapReplace(repl func(PropertyValue) (interface{}, bool)) interface{} {
if repl != nil {
if vret, vrep := repl(v); vrep {
return vret
}
}
if v.IsNull() {
return nil
} else if v.IsBool() {
@ -651,12 +668,12 @@ func (v PropertyValue) Mappable() interface{} {
} else if v.IsArray() {
var arr []interface{}
for _, e := range v.ArrayValue() {
arr = append(arr, e.Mappable())
arr = append(arr, e.MapReplace(repl))
}
return arr
}
contract.Assert(v.IsObject())
return v.ObjectValue().Mappable()
return v.ObjectValue().MapReplace(repl)
}
// String implements the fmt.Stringer interface to add slightly more information to the output.

View file

@ -0,0 +1,95 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package resource
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestMappable ensures that we properly convert from resource property maps to their "weakly typed" JSON-like
// equivalents.
func TestMappable(t *testing.T) {
ma1 := map[string]interface{}{
"a": float64(42.3),
"b": false,
"c": "foobar",
"d": []interface{}{"x", float64(99), true},
"e": map[string]interface{}{
"e.1": "z",
"e.n": float64(676.767),
"e.^": []interface{}{"bbb"},
},
}
ma1p := NewPropertyMapFromMap(ma1)
assert.Equal(t, len(ma1), len(ma1p))
ma1mm := ma1p.Mappable()
assert.Equal(t, ma1, ma1mm)
}
// TestReplace ensures that we properly convert from resource property maps to their "weakly typed" JSON-like
// equivalents, but with additional and optional functions that replace values inline as we go.
func TestMapReplace(t *testing.T) {
// First, no replacements (nil repl).
ma1 := map[string]interface{}{
"a": float64(42.3),
"b": false,
"c": "foobar",
"d": []interface{}{"x", float64(99), true},
"e": map[string]interface{}{
"e.1": "z",
"e.n": float64(676.767),
"e.^": []interface{}{"bbb"},
},
}
ma1p := NewPropertyMapFromMap(ma1)
assert.Equal(t, len(ma1), len(ma1p))
ma1mm := ma1p.MapReplace(nil)
assert.Equal(t, ma1, ma1mm)
// First, no replacements (false-returning repl).
ma2 := map[string]interface{}{
"a": float64(42.3),
"b": false,
"c": "foobar",
"d": []interface{}{"x", float64(99), true},
"e": map[string]interface{}{
"e.1": "z",
"e.n": float64(676.767),
"e.^": []interface{}{"bbb"},
},
}
ma2p := NewPropertyMapFromMap(ma2)
assert.Equal(t, len(ma2), len(ma2p))
ma2mm := ma2p.MapReplace(func(v PropertyValue) (interface{}, bool) {
return nil, false
})
assert.Equal(t, ma2, ma2mm)
// Finally, actually replace some numbers with ints.
ma3 := map[string]interface{}{
"a": float64(42.3),
"b": false,
"c": "foobar",
"d": []interface{}{"x", float64(99), true},
"e": map[string]interface{}{
"e.1": "z",
"e.n": float64(676.767),
"e.^": []interface{}{"bbb"},
},
}
ma3p := NewPropertyMapFromMap(ma3)
assert.Equal(t, len(ma3), len(ma3p))
ma3mm := ma3p.MapReplace(func(v PropertyValue) (interface{}, bool) {
if v.IsNumber() {
return int(v.NumberValue()), true
}
return nil, false
})
// patch the original map so it can compare easily
ma3["a"] = int(ma3["a"].(float64))
ma3["d"].([]interface{})[1] = int(ma3["d"].([]interface{})[1].(float64))
ma3["e"].(map[string]interface{})["e.n"] = int(ma3["e"].(map[string]interface{})["e.n"].(float64))
assert.Equal(t, ma3, ma3mm)
}