diff --git a/internal/lang/marks/paths.go b/internal/lang/marks/paths.go index affd23e23b..461ec1bb42 100644 --- a/internal/lang/marks/paths.go +++ b/internal/lang/marks/paths.go @@ -36,3 +36,27 @@ func PathsWithMark(pvms []cty.PathValueMarks, wantMark any) (withWanted []cty.Pa return withWanted, withOthers } + +// MarkPaths transforms the given value by marking each of the given paths +// with the given mark value. +func MarkPaths(val cty.Value, mark any, paths []cty.Path) cty.Value { + if len(paths) == 0 { + // No-allocations path for the common case where there are no marked paths at all. + return val + } + + // For now we'll use cty's slightly lower-level function to achieve this + // result. This is a little inefficient due to an additional dynamic + // allocation for the intermediate data structure, so if that becomes + // a problem in practice then we may wish to write a more direct + // implementation here. + markses := make([]cty.PathValueMarks, len(paths)) + marks := cty.NewValueMarks(mark) + for i, path := range paths { + markses[i] = cty.PathValueMarks{ + Path: path, + Marks: marks, + } + } + return val.MarkWithPaths(markses) +} diff --git a/internal/lang/marks/paths_test.go b/internal/lang/marks/paths_test.go index c74a6fce5f..f5efd2829f 100644 --- a/internal/lang/marks/paths_test.go +++ b/internal/lang/marks/paths_test.go @@ -56,3 +56,55 @@ func TestPathsWithMark(t *testing.T) { t.Errorf("wrong set of entries with other marks\n%s", diff) } } + +func TestMarkPaths(t *testing.T) { + value := cty.ObjectVal(map[string]cty.Value{ + "s": cty.StringVal(".s"), + "l": cty.ListVal([]cty.Value{ + cty.StringVal(".l[0]"), + cty.StringVal(".l[1]"), + }), + "m": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal(`.m["a"]`), + "b": cty.StringVal(`.m["b"]`), + }), + "o": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal(".o.a"), + "b": cty.StringVal(".o.b"), + }), + "t": cty.TupleVal([]cty.Value{ + cty.StringVal(`.t[0]`), + cty.StringVal(`.t[1]`), + }), + }) + sensitivePaths := []cty.Path{ + cty.GetAttrPath("s"), + cty.GetAttrPath("l").IndexInt(1), + cty.GetAttrPath("m").IndexString("a"), + cty.GetAttrPath("o").GetAttr("b"), + cty.GetAttrPath("t").IndexInt(0), + } + got := MarkPaths(value, Sensitive, sensitivePaths) + want := cty.ObjectVal(map[string]cty.Value{ + "s": cty.StringVal(".s").Mark(Sensitive), + "l": cty.ListVal([]cty.Value{ + cty.StringVal(".l[0]"), + cty.StringVal(".l[1]").Mark(Sensitive), + }), + "m": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal(`.m["a"]`).Mark(Sensitive), + "b": cty.StringVal(`.m["b"]`), + }), + "o": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal(".o.a"), + "b": cty.StringVal(".o.b").Mark(Sensitive), + }), + "t": cty.TupleVal([]cty.Value{ + cty.StringVal(`.t[0]`).Mark(Sensitive), + cty.StringVal(`.t[1]`), + }), + }) + if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" { + t.Errorf("wrong result\n%s", diff) + } +}