183 lines
5.0 KiB
Go
183 lines
5.0 KiB
Go
|
package cty
|
||
|
|
||
|
// Walk visits all of the values in a possibly-complex structure, calling
|
||
|
// a given function for each value.
|
||
|
//
|
||
|
// For example, given a list of strings the callback would first be called
|
||
|
// with the whole list and then called once for each element of the list.
|
||
|
//
|
||
|
// The callback function may prevent recursive visits to child values by
|
||
|
// returning false. The callback function my halt the walk altogether by
|
||
|
// returning a non-nil error. If the returned error is about the element
|
||
|
// currently being visited, it is recommended to use the provided path
|
||
|
// value to produce a PathError describing that context.
|
||
|
//
|
||
|
// The path passed to the given function may not be used after that function
|
||
|
// returns, since its backing array is re-used for other calls.
|
||
|
func Walk(val Value, cb func(Path, Value) (bool, error)) error {
|
||
|
var path Path
|
||
|
return walk(path, val, cb)
|
||
|
}
|
||
|
|
||
|
func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
|
||
|
deeper, err := cb(path, val)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if !deeper {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if val.IsNull() || !val.IsKnown() {
|
||
|
// Can't recurse into null or unknown values, regardless of type
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
ty := val.Type()
|
||
|
switch {
|
||
|
case ty.IsObjectType():
|
||
|
for it := val.ElementIterator(); it.Next(); {
|
||
|
nameVal, av := it.Element()
|
||
|
path := append(path, GetAttrStep{
|
||
|
Name: nameVal.AsString(),
|
||
|
})
|
||
|
err := walk(path, av, cb)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
case val.CanIterateElements():
|
||
|
for it := val.ElementIterator(); it.Next(); {
|
||
|
kv, ev := it.Element()
|
||
|
path := append(path, IndexStep{
|
||
|
Key: kv,
|
||
|
})
|
||
|
err := walk(path, ev, cb)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Transform visits all of the values in a possibly-complex structure,
|
||
|
// calling a given function for each value which has an opportunity to
|
||
|
// replace that value.
|
||
|
//
|
||
|
// Unlike Walk, Transform visits child nodes first, so for a list of strings
|
||
|
// it would first visit the strings and then the _new_ list constructed
|
||
|
// from the transformed values of the list items.
|
||
|
//
|
||
|
// This is useful for creating the effect of being able to make deep mutations
|
||
|
// to a value even though values are immutable. However, it's the responsibility
|
||
|
// of the given function to preserve expected invariants, such as homogenity of
|
||
|
// element types in collections; this function can panic if such invariants
|
||
|
// are violated, just as if new values were constructed directly using the
|
||
|
// value constructor functions. An easy way to preserve invariants is to
|
||
|
// ensure that the transform function never changes the value type.
|
||
|
//
|
||
|
// The callback function my halt the walk altogether by
|
||
|
// returning a non-nil error. If the returned error is about the element
|
||
|
// currently being visited, it is recommended to use the provided path
|
||
|
// value to produce a PathError describing that context.
|
||
|
//
|
||
|
// The path passed to the given function may not be used after that function
|
||
|
// returns, since its backing array is re-used for other calls.
|
||
|
func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) {
|
||
|
var path Path
|
||
|
return transform(path, val, cb)
|
||
|
}
|
||
|
|
||
|
func transform(path Path, val Value, cb func(Path, Value) (Value, error)) (Value, error) {
|
||
|
ty := val.Type()
|
||
|
var newVal Value
|
||
|
|
||
|
switch {
|
||
|
|
||
|
case val.IsNull() || !val.IsKnown():
|
||
|
// Can't recurse into null or unknown values, regardless of type
|
||
|
newVal = val
|
||
|
|
||
|
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
|
||
|
l := val.LengthInt()
|
||
|
switch l {
|
||
|
case 0:
|
||
|
// No deep transform for an empty sequence
|
||
|
newVal = val
|
||
|
default:
|
||
|
elems := make([]Value, 0, l)
|
||
|
for it := val.ElementIterator(); it.Next(); {
|
||
|
kv, ev := it.Element()
|
||
|
path := append(path, IndexStep{
|
||
|
Key: kv,
|
||
|
})
|
||
|
newEv, err := transform(path, ev, cb)
|
||
|
if err != nil {
|
||
|
return DynamicVal, err
|
||
|
}
|
||
|
elems = append(elems, newEv)
|
||
|
}
|
||
|
switch {
|
||
|
case ty.IsListType():
|
||
|
newVal = ListVal(elems)
|
||
|
case ty.IsSetType():
|
||
|
newVal = SetVal(elems)
|
||
|
case ty.IsTupleType():
|
||
|
newVal = TupleVal(elems)
|
||
|
default:
|
||
|
panic("unknown sequence type") // should never happen because of the case we are in
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case ty.IsMapType():
|
||
|
l := val.LengthInt()
|
||
|
switch l {
|
||
|
case 0:
|
||
|
// No deep transform for an empty map
|
||
|
newVal = val
|
||
|
default:
|
||
|
elems := make(map[string]Value)
|
||
|
for it := val.ElementIterator(); it.Next(); {
|
||
|
kv, ev := it.Element()
|
||
|
path := append(path, IndexStep{
|
||
|
Key: kv,
|
||
|
})
|
||
|
newEv, err := transform(path, ev, cb)
|
||
|
if err != nil {
|
||
|
return DynamicVal, err
|
||
|
}
|
||
|
elems[kv.AsString()] = newEv
|
||
|
}
|
||
|
newVal = MapVal(elems)
|
||
|
}
|
||
|
|
||
|
case ty.IsObjectType():
|
||
|
switch {
|
||
|
case ty.Equals(EmptyObject):
|
||
|
// No deep transform for an empty object
|
||
|
newVal = val
|
||
|
default:
|
||
|
atys := ty.AttributeTypes()
|
||
|
newAVs := make(map[string]Value)
|
||
|
for name := range atys {
|
||
|
av := val.GetAttr(name)
|
||
|
path := append(path, GetAttrStep{
|
||
|
Name: name,
|
||
|
})
|
||
|
newAV, err := transform(path, av, cb)
|
||
|
if err != nil {
|
||
|
return DynamicVal, err
|
||
|
}
|
||
|
newAVs[name] = newAV
|
||
|
}
|
||
|
newVal = ObjectVal(newAVs)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
newVal = val
|
||
|
}
|
||
|
|
||
|
return cb(path, newVal)
|
||
|
}
|