diff --git a/changelog/pending/20221017--sdk-go--decode-ptr-asset-and-ptr-archive-values.yaml b/changelog/pending/20221017--sdk-go--decode-ptr-asset-and-ptr-archive-values.yaml new file mode 100644 index 000000000000..162254463c86 --- /dev/null +++ b/changelog/pending/20221017--sdk-go--decode-ptr-asset-and-ptr-archive-values.yaml @@ -0,0 +1,4 @@ +changes: +- type: fix + scope: sdk/go + description: Allow decoding *asset and *archive values diff --git a/sdk/go/common/resource/mapper_test.go b/sdk/go/common/resource/mapper_test.go new file mode 100644 index 000000000000..58853c0a38e5 --- /dev/null +++ b/sdk/go/common/resource/mapper_test.go @@ -0,0 +1,85 @@ +// Copyright 2016-2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource_test + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper" +) + +type complexBag struct { + asset resource.Asset + archive resource.Archive + + optionalAsset *resource.Asset + optionalArchive *resource.Archive +} + +func TestAssetsAndArchives(t *testing.T) { + t.Parallel() + + newAsset := func(s string) *resource.Asset { + a, err := resource.NewTextAsset(s) + require.NoError(t, err, "creating asset %s", s) + return a + } + newArchive := func(m map[string]interface{}) *resource.Archive { + a, err := resource.NewAssetArchive(m) + require.NoError(t, err, "creating asset %#v", m) + return a + } + + bigArchive := func() *resource.Archive { + return newArchive(map[string]interface{}{ + "asset1": newAsset("asset1"), + "archive1": newArchive(map[string]interface{}{ + "asset2": newAsset("asset2"), + "asset3": newAsset("asset3"), + }), + }) + } + tree := map[string]interface{}{ + "asset": newAsset("simple asset"), + "optionalAsset": newAsset("simple optional asset"), + "archive": bigArchive(), + "optionalArchive": bigArchive(), + } + + bag := complexBag{} + md := mapper.New(nil) + + t.Run("asset", func(t *testing.T) { //nolint:parallelTest + err := md.DecodeValue(tree, reflect.TypeOf(complexBag{}), "asset", &bag.asset, false) + assert.Nil(t, err) + }) + t.Run("optionalAsset", func(t *testing.T) { //nolint:parallelTest + err := md.DecodeValue(tree, reflect.TypeOf(complexBag{}), "optionalAsset", &bag.optionalAsset, false) + assert.Nil(t, err) + }) + t.Run("archive", func(t *testing.T) { //nolint:parallelTest + err := md.DecodeValue(tree, reflect.TypeOf(complexBag{}), "archive", &bag.archive, false) + assert.Nil(t, err) + }) + t.Run("optionalArchive", func(t *testing.T) { //nolint:parallelTest + err := md.DecodeValue(tree, reflect.TypeOf(complexBag{}), "optionalArchive", &bag.optionalArchive, false) + assert.Nil(t, err) + }) +} diff --git a/sdk/go/common/util/mapper/mapper_decode.go b/sdk/go/common/util/mapper/mapper_decode.go index 023742bc9053..5d539a493f35 100644 --- a/sdk/go/common/util/mapper/mapper_decode.go +++ b/sdk/go/common/util/mapper/mapper_decode.go @@ -88,13 +88,21 @@ func (md *mapper) DecodeValue(obj map[string]interface{}, ty reflect.Type, key s if v, has := obj[key]; has { // The field exists; okay, try to map it to the right type. vsrc := reflect.ValueOf(v) + newValue: // Ensure the source is valid; this is false if the value reflects the zero value. if vsrc.IsValid() { vdstType := vdst.Type().Elem() + // If the source is a ptr, dereference it as necessary to get the underlying + // value. + if vsrc.Type().Kind() == reflect.Ptr && !vsrc.IsNil() { + vsrc = vsrc.Elem() + goto newValue + } + // So long as the target element is a pointer, we have a pointer to pointer; dig through until we bottom out // on the non-pointer type that matches the source. This assumes the source isn't itself a pointer! - contract.Assertf(vsrc.Type().Kind() != reflect.Ptr, "source is a pointer") + contract.Assertf(vsrc.Type().Kind() != reflect.Ptr, "source is a null pointer") for vdstType.Kind() == reflect.Ptr { vdst = vdst.Elem() vdstType = vdstType.Elem()