Skip to content

Commit

Permalink
rework transformer and rename skip children error
Browse files Browse the repository at this point in the history
  • Loading branch information
VenelinMartinov committed Dec 18, 2024
1 parent f9d8688 commit d32594b
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 79 deletions.
95 changes: 28 additions & 67 deletions unstable/propertyvalue/propertyvalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,87 +58,49 @@ func TransformPropertyValue(
transformer func(resource.PropertyPath, resource.PropertyValue) (resource.PropertyValue, error),
value resource.PropertyValue,
) (resource.PropertyValue, error) {
switch {
case value.IsArray():
// preserve nil arrays
if !isNilArray(value) {
tvs := []resource.PropertyValue{}
for i, v := range value.ArrayValue() {
tv, err := TransformPropertyValue(extendPath(path, i), transformer, v)
if err != nil {
return resource.NewNullProperty(), err
}
tvs = append(tvs, tv)
return TransformPropertyValueDirectional(
path, func(path resource.PropertyPath, value resource.PropertyValue, entering bool) (resource.PropertyValue, error) {
if !entering {
return transformer(path, value)
}
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 := TransformPropertyValue(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 := TransformPropertyValue(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 := TransformPropertyValue(path, transformer, s.Element)
if err != nil {
return resource.NewNullProperty(), err
}
value = resource.NewSecretProperty(&resource.Secret{
Element: te,
})
}
return transformer(path, value)
return value, nil
}, value,
)
}

type LimitDescentError struct{}
type SkipChildrenError struct{}

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

// 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(
type TransformerDirectional = func(
resource.PropertyPath, resource.PropertyValue, bool,
) (resource.PropertyValue, error)

// TransformPropertyValueDirectional is a variant of TransformPropertyValue that allows the transformer
// to visit nodes both on the way down and on the way up.
//
// The transformer can return a SkipChildrenError to indicate that the recursion should not descend into the value.
func TransformPropertyValueDirectional(
path resource.PropertyPath,
transformer func(resource.PropertyPath, resource.PropertyValue) (resource.PropertyValue, error),
transformer TransformerDirectional,
value resource.PropertyValue,
) (resource.PropertyValue, error) {
value, err := transformer(path, value)
value, err := transformer(path, value, true)
if err != nil {
if errors.Is(err, LimitDescentError{}) {
if errors.Is(err, SkipChildrenError{}) {
return value, nil
}
return resource.NewNullProperty(), err
return value, 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)
tv, err := TransformPropertyValueDirectional(extendPath(path, i), transformer, v)
if err != nil {
return resource.NewNullProperty(), err
}
Expand All @@ -151,7 +113,7 @@ func TransformPropertyValueLimitDescent(
if !isNilObject(value) {
pm := make(resource.PropertyMap)
for k, v := range value.ObjectValue() {
tv, err := TransformPropertyValueLimitDescent(extendPath(path, string(k)), transformer, v)
tv, err := TransformPropertyValueDirectional(extendPath(path, string(k)), transformer, v)
if err != nil {
return resource.NewNullProperty(), err
}
Expand All @@ -161,7 +123,7 @@ func TransformPropertyValueLimitDescent(
}
case value.IsOutput():
o := value.OutputValue()
te, err := TransformPropertyValueLimitDescent(path, transformer, o.Element)
te, err := TransformPropertyValueDirectional(path, transformer, o.Element)
if err != nil {
return resource.NewNullProperty(), err
}
Expand All @@ -173,16 +135,15 @@ func TransformPropertyValueLimitDescent(
})
case value.IsSecret():
s := value.SecretValue()
te, err := TransformPropertyValueLimitDescent(path, transformer, s.Element)
te, err := TransformPropertyValueDirectional(path, transformer, s.Element)
if err != nil {
return resource.NewNullProperty(), err
}
value = resource.NewSecretProperty(&resource.Secret{
Element: te,
})
}

return value, nil
return transformer(path, value, false)
}

// Removes any resource.NewSecretProperty wrappers. Removes Secret: true flags from any first-class outputs.
Expand Down
32 changes: 20 additions & 12 deletions unstable/propertyvalue/propertyvalue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,23 @@ func TestTransformPreservesNilObjects(t *testing.T) {
require.Nil(t, result.ObjectValue())
}

func TestTransformPropertyValueLimitDescent(t *testing.T) {
func TestTransformPropertyValueDirectional(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
transformer := func(
_ resource.PropertyPath, v resource.PropertyValue, entering bool,
) (resource.PropertyValue, error) {
if entering {
if v.IsString() {
return resource.NewStringProperty(v.StringValue() + " world"), nil
}
}
return v, nil
}

result, err := TransformPropertyValueLimitDescent(nil, transformer, input)
result, err := TransformPropertyValueDirectional(nil, transformer, input)
require.NoError(t, err)
require.Equal(t, "hello world", result.StringValue())
})
Expand All @@ -116,17 +120,21 @@ func TestTransformPropertyValueLimitDescent(t *testing.T) {
"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
transformer := func(
_ resource.PropertyPath, v resource.PropertyValue, entering bool,
) (resource.PropertyValue, error) {
if entering {
if v.IsArray() {
return v, SkipChildrenError{}
}
if v.IsString() {
return resource.NewStringProperty(v.StringValue() + " transformed"), nil
}
}
return v, nil
}

result, err := TransformPropertyValueLimitDescent(nil, transformer, input)
result, err := TransformPropertyValueDirectional(nil, transformer, input)
require.NoError(t, err)
require.True(t, result.IsObject())
require.Equal(t, "should transformed", result.ObjectValue()["string"].StringValue())
Expand Down

0 comments on commit d32594b

Please sign in to comment.