| 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) |
| } |