Merge pull request #28644 from hashicorp/alisdair/length-lookup-marks-fixes
Improve marks support for `length` and `lookup`
This commit is contained in:
commit
fb53259907
|
@ -20,6 +20,7 @@ var LengthFunc = function.New(&function.Spec{
|
||||||
Type: cty.DynamicPseudoType,
|
Type: cty.DynamicPseudoType,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
AllowUnknown: true,
|
AllowUnknown: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (cty.Type, error) {
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
|
@ -34,15 +35,16 @@ var LengthFunc = function.New(&function.Spec{
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
coll := args[0]
|
coll := args[0]
|
||||||
collTy := args[0].Type()
|
collTy := args[0].Type()
|
||||||
|
marks := coll.Marks()
|
||||||
switch {
|
switch {
|
||||||
case collTy == cty.DynamicPseudoType:
|
case collTy == cty.DynamicPseudoType:
|
||||||
return cty.UnknownVal(cty.Number), nil
|
return cty.UnknownVal(cty.Number).WithMarks(marks), nil
|
||||||
case collTy.IsTupleType():
|
case collTy.IsTupleType():
|
||||||
l := len(collTy.TupleElementTypes())
|
l := len(collTy.TupleElementTypes())
|
||||||
return cty.NumberIntVal(int64(l)), nil
|
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
|
||||||
case collTy.IsObjectType():
|
case collTy.IsObjectType():
|
||||||
l := len(collTy.AttributeTypes())
|
l := len(collTy.AttributeTypes())
|
||||||
return cty.NumberIntVal(int64(l)), nil
|
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
|
||||||
case collTy == cty.String:
|
case collTy == cty.String:
|
||||||
// We'll delegate to the cty stdlib strlen function here, because
|
// We'll delegate to the cty stdlib strlen function here, because
|
||||||
// it deals with all of the complexities of tokenizing unicode
|
// it deals with all of the complexities of tokenizing unicode
|
||||||
|
@ -212,12 +214,14 @@ var IndexFunc = function.New(&function.Spec{
|
||||||
var LookupFunc = function.New(&function.Spec{
|
var LookupFunc = function.New(&function.Spec{
|
||||||
Params: []function.Parameter{
|
Params: []function.Parameter{
|
||||||
{
|
{
|
||||||
Name: "inputMap",
|
Name: "inputMap",
|
||||||
Type: cty.DynamicPseudoType,
|
Type: cty.DynamicPseudoType,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "key",
|
Name: "key",
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
VarParam: &function.Parameter{
|
VarParam: &function.Parameter{
|
||||||
|
@ -226,6 +230,7 @@ var LookupFunc = function.New(&function.Spec{
|
||||||
AllowUnknown: true,
|
AllowUnknown: true,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
AllowNull: true,
|
AllowNull: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
||||||
if len(args) < 1 || len(args) > 3 {
|
if len(args) < 1 || len(args) > 3 {
|
||||||
|
@ -240,7 +245,8 @@ var LookupFunc = function.New(&function.Spec{
|
||||||
return cty.DynamicPseudoType, nil
|
return cty.DynamicPseudoType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
key := args[1].AsString()
|
keyVal, _ := args[1].Unmark()
|
||||||
|
key := keyVal.AsString()
|
||||||
if ty.HasAttribute(key) {
|
if ty.HasAttribute(key) {
|
||||||
return args[0].GetAttr(key).Type(), nil
|
return args[0].GetAttr(key).Type(), nil
|
||||||
} else if len(args) == 3 {
|
} else if len(args) == 3 {
|
||||||
|
@ -266,23 +272,35 @@ var LookupFunc = function.New(&function.Spec{
|
||||||
defaultValueSet := false
|
defaultValueSet := false
|
||||||
|
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
|
// intentionally leave default value marked
|
||||||
defaultVal = args[2]
|
defaultVal = args[2]
|
||||||
defaultValueSet = true
|
defaultValueSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
mapVar := args[0]
|
// keep track of marks from the collection and key
|
||||||
lookupKey := args[1].AsString()
|
var markses []cty.ValueMarks
|
||||||
|
|
||||||
|
// unmark collection, retain marks to reapply later
|
||||||
|
mapVar, mapMarks := args[0].Unmark()
|
||||||
|
markses = append(markses, mapMarks)
|
||||||
|
|
||||||
|
// include marks on the key in the result
|
||||||
|
keyVal, keyMarks := args[1].Unmark()
|
||||||
|
if len(keyMarks) > 0 {
|
||||||
|
markses = append(markses, keyMarks)
|
||||||
|
}
|
||||||
|
lookupKey := keyVal.AsString()
|
||||||
|
|
||||||
if !mapVar.IsKnown() {
|
if !mapVar.IsKnown() {
|
||||||
return cty.UnknownVal(retType), nil
|
return cty.UnknownVal(retType).WithMarks(markses...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapVar.Type().IsObjectType() {
|
if mapVar.Type().IsObjectType() {
|
||||||
if mapVar.Type().HasAttribute(lookupKey) {
|
if mapVar.Type().HasAttribute(lookupKey) {
|
||||||
return mapVar.GetAttr(lookupKey), nil
|
return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil
|
||||||
}
|
}
|
||||||
} else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
|
} else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
|
||||||
return mapVar.Index(cty.StringVal(lookupKey)), nil
|
return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if defaultValueSet {
|
if defaultValueSet {
|
||||||
|
@ -290,10 +308,10 @@ var LookupFunc = function.New(&function.Spec{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.NilVal, err
|
return cty.NilVal, err
|
||||||
}
|
}
|
||||||
return defaultVal, nil
|
return defaultVal.WithMarks(markses...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
|
return cty.UnknownVal(cty.DynamicPseudoType).WithMarks(markses...), fmt.Errorf(
|
||||||
"lookup failed to find '%s'", lookupKey)
|
"lookup failed to find '%s'", lookupKey)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -122,6 +122,54 @@ func TestLength(t *testing.T) {
|
||||||
cty.DynamicVal,
|
cty.DynamicVal,
|
||||||
cty.UnknownVal(cty.Number),
|
cty.UnknownVal(cty.Number),
|
||||||
},
|
},
|
||||||
|
{ // Marked collections return a marked length
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("hello"),
|
||||||
|
cty.StringVal("world"),
|
||||||
|
}).Mark("secret"),
|
||||||
|
cty.NumberIntVal(2).Mark("secret"),
|
||||||
|
},
|
||||||
|
{ // Marks on values in unmarked collections do not propagate
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("hello").Mark("a"),
|
||||||
|
cty.StringVal("world").Mark("b"),
|
||||||
|
}),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
},
|
||||||
|
{ // Marked strings return a marked length
|
||||||
|
cty.StringVal("hello world").Mark("secret"),
|
||||||
|
cty.NumberIntVal(11).Mark("secret"),
|
||||||
|
},
|
||||||
|
{ // Marked tuples return a marked length
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("hello"),
|
||||||
|
cty.StringVal("world"),
|
||||||
|
}).Mark("secret"),
|
||||||
|
cty.NumberIntVal(2).Mark("secret"),
|
||||||
|
},
|
||||||
|
{ // Marks on values in unmarked tuples do not propagate
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("hello").Mark("a"),
|
||||||
|
cty.StringVal("world").Mark("b"),
|
||||||
|
}),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
},
|
||||||
|
{ // Marked objects return a marked length
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.StringVal("hello"),
|
||||||
|
"b": cty.StringVal("world"),
|
||||||
|
"c": cty.StringVal("nice to meet you"),
|
||||||
|
}).Mark("secret"),
|
||||||
|
cty.NumberIntVal(3).Mark("secret"),
|
||||||
|
},
|
||||||
|
{ // Marks on object attribute values do not propagate
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.StringVal("hello").Mark("a"),
|
||||||
|
"b": cty.StringVal("world").Mark("b"),
|
||||||
|
"c": cty.StringVal("nice to meet you").Mark("c"),
|
||||||
|
}),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -747,6 +795,88 @@ func TestLookup(t *testing.T) {
|
||||||
cty.DynamicVal, // if the key is unknown then we don't know which object attribute and thus can't know the type
|
cty.DynamicVal, // if the key is unknown then we don't know which object attribute and thus can't know the type
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{ // successful marked collection lookup returns marked value
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep"),
|
||||||
|
}).Mark("a"),
|
||||||
|
cty.StringVal("boop"),
|
||||||
|
cty.StringVal("nope"),
|
||||||
|
},
|
||||||
|
cty.StringVal("beep").Mark("a"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // apply collection marks to unknown return vaue
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep"),
|
||||||
|
"frob": cty.UnknownVal(cty.String),
|
||||||
|
}).Mark("a"),
|
||||||
|
cty.StringVal("frob"),
|
||||||
|
cty.StringVal("nope"),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.String).Mark("a"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // propagate collection marks to default when returning
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep"),
|
||||||
|
}).Mark("a"),
|
||||||
|
cty.StringVal("frob"),
|
||||||
|
cty.StringVal("nope").Mark("b"),
|
||||||
|
},
|
||||||
|
cty.StringVal("nope").WithMarks(cty.NewValueMarks("a", "b")),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // on unmarked collection, return only marks from found value
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep").Mark("a"),
|
||||||
|
"frob": cty.StringVal("honk").Mark("b"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("frob"),
|
||||||
|
cty.StringVal("nope").Mark("c"),
|
||||||
|
},
|
||||||
|
cty.StringVal("honk").Mark("b"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // on unmarked collection, return default exactly on missing
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep").Mark("a"),
|
||||||
|
"frob": cty.StringVal("honk").Mark("b"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("squish"),
|
||||||
|
cty.StringVal("nope").Mark("c"),
|
||||||
|
},
|
||||||
|
cty.StringVal("nope").Mark("c"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // retain marks on default if converted
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep").Mark("a"),
|
||||||
|
"frob": cty.StringVal("honk").Mark("b"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("squish"),
|
||||||
|
cty.NumberIntVal(5).Mark("c"),
|
||||||
|
},
|
||||||
|
cty.StringVal("5").Mark("c"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // propagate marks from key
|
||||||
|
[]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("beep"),
|
||||||
|
"frob": cty.StringVal("honk"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("boop").Mark("a"),
|
||||||
|
cty.StringVal("nope"),
|
||||||
|
},
|
||||||
|
cty.StringVal("beep").Mark("a"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
Loading…
Reference in New Issue