Recursively merge properties.

When merging inputs and defaults in order to construct the set of inputs
for a call to `Create`, we must recursively merge each property value:
the provided defaults may contain nested values that must be present in
the merged result.
This commit is contained in:
Pat Gavlin 2017-11-28 12:01:41 -08:00
parent 89839038a0
commit f5b35561c6
6 changed files with 112 additions and 0 deletions

View file

@ -0,0 +1,4 @@
name: recursive-merge
description: A test for recursive merging of defaults.
runtime: nodejs

View file

@ -0,0 +1,29 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
import * as pulumi from "pulumi";
import * as dynamic from "pulumi/dynamic";
class TestProvider implements dynamic.ResourceProvider {
diff = (id: pulumi.ID, olds: any, news: any) => Promise.resolve(new dynamic.DiffResult([], []));
delete = (id: pulumi.ID, props: any) => Promise.resolve();
update = (id: string, olds: any, news: any) => Promise.resolve(new dynamic.UpdateResult({}));
check = (inputs: any) => Promise.resolve(new dynamic.CheckResult({prop: {def: true}, arr: [{def: true}], arr1: [], arr2: [{def:true}]}, []));
create = async (inputs: any) => {
if (!inputs.prop.def || !inputs.arr[0].def || !inputs.arr1[0].def || !inputs.arr2[0].def) {
throw new Error("expected defaults to be recursively merged");
}
return new dynamic.CreateResult("0", {ok: true});
}
}
class TestResource extends dynamic.Resource {
public readonly ok: pulumi.Computed<Boolean>;
constructor(name: string) {
super(new TestProvider(), name, {prop: {unused: ""}, arr: [{unused: ""}], arr1: [{def: true}], arr2: []}, undefined);
}
}
export const ok = new TestResource("test").ok;

View file

@ -0,0 +1,11 @@
{
"name": "minimal",
"main": "bin/index.js",
"typings": "bin/index.d.ts",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"typescript": "^2.5.3"
}
}

View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"stripInternal": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true
},
"files": [
"index.ts"
]
}

View file

@ -0,0 +1,7 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
typescript@^2.5.3:
version "2.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"

View file

@ -141,6 +141,9 @@ func (m PropertyMap) Merge(other PropertyMap) PropertyMap {
new := m.Copy()
for k, v := range other {
if !v.IsNull() {
if mv, ok := m[k]; ok {
v = mv.Merge(v)
}
new[k] = v
}
}
@ -454,6 +457,42 @@ func (v PropertyValue) MapRepl(replk func(string) (string, bool),
return v.ObjectValue().MapRepl(replk, replv)
}
// Merge simply merges the value of other into v. Merging proceeds as follows:
// - If other is null, v is returned.
// - If v and other are both arrays, the corresponding elements are recurively merged. Any unmerged elements in v or other are then
// appended to the result.
// - If v and other are both maps, the corresponding key-value pairs are recursively merged.
// - Otherwise, other is returned.
func (v PropertyValue) Merge(other PropertyValue) PropertyValue {
switch {
case other.IsNull():
return v
case v.IsArray() && other.IsArray():
left, right, merged := v.ArrayValue(), other.ArrayValue(), []PropertyValue{}
for len(left) > 0 && len(right) > 0 {
merged = append(merged, left[0], right[0])
left, right = left[1:], right[1:]
}
switch {
case len(left) > 0:
contract.Assert(len(right) == 0)
for ; len(left) > 0; left = left[1:] {
merged = append(merged, left[0])
}
case len(right) > 0:
contract.Assert(len(left) == 0)
for ; len(right) > 0; right = right[1:] {
merged = append(merged, right[0])
}
}
return NewArrayProperty(merged)
case v.IsObject() && other.IsObject():
return NewObjectProperty(v.ObjectValue().Merge(other.ObjectValue()))
default:
return other
}
}
// String implements the fmt.Stringer interface to add slightly more information to the output.
func (v PropertyValue) String() string {
if v.IsComputed() || v.IsOutput() {