Skip to content

Commit

Permalink
Add a TransformPropertyValueLimitDescent property value walker
Browse files Browse the repository at this point in the history
  • Loading branch information
VenelinMartinov committed Dec 17, 2024
1 parent a2644af commit 7da1aec
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
77 changes: 77 additions & 0 deletions unstable/propertyvalue/propertyvalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package propertyvalue

import (
"errors"

"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
)

Expand Down Expand Up @@ -108,6 +110,81 @@ func TransformPropertyValue(
return transformer(path, value)
}

type LimitDescentError struct{}

func (LimitDescentError) Error() string {
return "limit descent"
}

// TransformPropertyValueLimitDescent is a variant of TransformPropertyValue that allows the transformer
// to return a LimitDescentError to indicate that the recursion should not descend into the value without
// aborting the whole transformation.
func TransformPropertyValueLimitDescent(
path resource.PropertyPath,
transformer func(resource.PropertyPath, resource.PropertyValue) (resource.PropertyValue, error),
value resource.PropertyValue,
) (resource.PropertyValue, error) {
value, err := transformer(path, value)
if err != nil {
if errors.Is(err, LimitDescentError{}) {
return value, nil
}
return resource.NewNullProperty(), err
}

switch {
case value.IsArray():
// preserve nil arrays
if !isNilArray(value) {
tvs := []resource.PropertyValue{}
for i, v := range value.ArrayValue() {
tv, err := TransformPropertyValueLimitDescent(extendPath(path, i), transformer, v)
if err != nil {
return resource.NewNullProperty(), err
}
tvs = append(tvs, tv)
}
value = resource.NewArrayProperty(tvs)
}
case value.IsObject():
// preserve nil objects
if !isNilObject(value) {
pm := make(resource.PropertyMap)
for k, v := range value.ObjectValue() {
tv, err := TransformPropertyValueLimitDescent(extendPath(path, string(k)), transformer, v)
if err != nil {
return resource.NewNullProperty(), err
}
pm[k] = tv
}
value = resource.NewObjectProperty(pm)
}
case value.IsOutput():
o := value.OutputValue()
te, err := TransformPropertyValueLimitDescent(path, transformer, o.Element)
if err != nil {
return resource.NewNullProperty(), err
}
value = resource.NewOutputProperty(resource.Output{
Element: te,
Known: o.Known,
Secret: o.Secret,
Dependencies: o.Dependencies,
})
case value.IsSecret():
s := value.SecretValue()
te, err := TransformPropertyValueLimitDescent(path, transformer, s.Element)
if err != nil {
return resource.NewNullProperty(), err
}
value = resource.NewSecretProperty(&resource.Secret{
Element: te,
})
}

return value, nil
}

// Removes any resource.NewSecretProperty wrappers. Removes Secret: true flags from any first-class outputs.
func RemoveSecrets(pv resource.PropertyValue) resource.PropertyValue {
unsecret := func(pv resource.PropertyValue) resource.PropertyValue {
Expand Down
45 changes: 45 additions & 0 deletions unstable/propertyvalue/propertyvalue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,48 @@ func TestTransformPreservesNilObjects(t *testing.T) {
require.True(t, result.IsObject())
require.Nil(t, result.ObjectValue())
}

func TestTransformPropertyValueLimitDescent(t *testing.T) {
t.Parallel()
t.Run("simple value transformation", func(t *testing.T) {
t.Parallel()
input := resource.NewStringProperty("hello")
transformer := func(_ resource.PropertyPath, v resource.PropertyValue) (resource.PropertyValue, error) {
if v.IsString() {
return resource.NewStringProperty(v.StringValue() + " world"), nil
}
return v, nil
}

result, err := TransformPropertyValueLimitDescent(nil, transformer, input)
require.NoError(t, err)
require.Equal(t, "hello world", result.StringValue())
})

t.Run("limit descent on array", func(t *testing.T) {
t.Parallel()
input := resource.NewObjectProperty(resource.PropertyMap{
"array": resource.NewArrayProperty([]resource.PropertyValue{
resource.NewStringProperty("should not transform"),
}),
"string": resource.NewStringProperty("should"),
})

transformer := func(_ resource.PropertyPath, v resource.PropertyValue) (resource.PropertyValue, error) {
if v.IsArray() {
return v, LimitDescentError{}
}
if v.IsString() {
return resource.NewStringProperty(v.StringValue() + " transformed"), nil
}
return v, nil
}

result, err := TransformPropertyValueLimitDescent(nil, transformer, input)
require.NoError(t, err)
require.True(t, result.IsObject())
require.Equal(t, "should transformed", result.ObjectValue()["string"].StringValue())
arr := result.ObjectValue()["array"].ArrayValue()
require.Equal(t, "should not transform", arr[0].StringValue())
})
}

0 comments on commit 7da1aec

Please sign in to comment.