mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-02-15 22:00:34 -05:00
If the string to be tested is an unknown value that's been refined with a prefix and the prefix we're being asked to test is in turn a prefix of that known prefix then we can return a known answer despite the inputs not being fully known. There are also some other similar deductions we can make about other combinations of inputs. This extra analysis could be useful in a custom condition check that requires a string with a particular prefix, since it can allow the condition to fail even on partially-unknown input, thereby giving earlier feedback about a problem.
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package funcs
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
)
|
|
|
|
// StartsWithFunc constructs a function that checks if a string starts with
|
|
// a specific prefix using strings.HasPrefix
|
|
var StartsWithFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "str",
|
|
Type: cty.String,
|
|
AllowUnknown: true,
|
|
},
|
|
{
|
|
Name: "prefix",
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
Type: function.StaticReturnType(cty.Bool),
|
|
RefineResult: refineNotNull,
|
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
|
prefix := args[1].AsString()
|
|
|
|
if !args[0].IsKnown() {
|
|
// If the unknown value has a known prefix then we might be
|
|
// able to still produce a known result.
|
|
if prefix == "" {
|
|
// The empty string is a prefix of any string.
|
|
return cty.True, nil
|
|
}
|
|
if knownPrefix := args[0].Range().StringPrefix(); knownPrefix != "" {
|
|
if strings.HasPrefix(knownPrefix, prefix) {
|
|
return cty.True, nil
|
|
}
|
|
if len(knownPrefix) >= len(prefix) {
|
|
// If the prefix we're testing is no longer than the known
|
|
// prefix and it didn't match then the full string with
|
|
// that same prefix can't match either.
|
|
return cty.False, nil
|
|
}
|
|
}
|
|
return cty.UnknownVal(cty.Bool), nil
|
|
}
|
|
|
|
str := args[0].AsString()
|
|
|
|
if strings.HasPrefix(str, prefix) {
|
|
return cty.True, nil
|
|
}
|
|
|
|
return cty.False, nil
|
|
},
|
|
})
|
|
|
|
// EndsWithFunc constructs a function that checks if a string ends with
|
|
// a specific suffix using strings.HasSuffix
|
|
var EndsWithFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "str",
|
|
Type: cty.String,
|
|
},
|
|
{
|
|
Name: "suffix",
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
Type: function.StaticReturnType(cty.Bool),
|
|
RefineResult: refineNotNull,
|
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
|
str := args[0].AsString()
|
|
suffix := args[1].AsString()
|
|
|
|
if strings.HasSuffix(str, suffix) {
|
|
return cty.True, nil
|
|
}
|
|
|
|
return cty.False, nil
|
|
},
|
|
})
|
|
|
|
// ReplaceFunc constructs a function that searches a given string for another
|
|
// given substring, and replaces each occurence with a given replacement string.
|
|
var ReplaceFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "str",
|
|
Type: cty.String,
|
|
},
|
|
{
|
|
Name: "substr",
|
|
Type: cty.String,
|
|
},
|
|
{
|
|
Name: "replace",
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
Type: function.StaticReturnType(cty.String),
|
|
RefineResult: refineNotNull,
|
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
|
str := args[0].AsString()
|
|
substr := args[1].AsString()
|
|
replace := args[2].AsString()
|
|
|
|
// We search/replace using a regexp if the string is surrounded
|
|
// in forward slashes.
|
|
if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' {
|
|
re, err := regexp.Compile(substr[1 : len(substr)-1])
|
|
if err != nil {
|
|
return cty.UnknownVal(cty.String), err
|
|
}
|
|
|
|
return cty.StringVal(re.ReplaceAllString(str, replace)), nil
|
|
}
|
|
|
|
return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil
|
|
},
|
|
})
|
|
|
|
// StrContainsFunc searches a given string for another given substring,
|
|
// if found the function returns true, otherwise returns false.
|
|
var StrContainsFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "str",
|
|
Type: cty.String,
|
|
},
|
|
{
|
|
Name: "substr",
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
Type: function.StaticReturnType(cty.Bool),
|
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
|
str := args[0].AsString()
|
|
substr := args[1].AsString()
|
|
|
|
if strings.Contains(str, substr) {
|
|
return cty.True, nil
|
|
}
|
|
|
|
return cty.False, nil
|
|
},
|
|
})
|
|
|
|
// Replace searches a given string for another given substring,
|
|
// and replaces all occurences with a given replacement string.
|
|
func Replace(str, substr, replace cty.Value) (cty.Value, error) {
|
|
return ReplaceFunc.Call([]cty.Value{str, substr, replace})
|
|
}
|
|
|
|
func StrContains(str, substr cty.Value) (cty.Value, error) {
|
|
return StrContainsFunc.Call([]cty.Value{str, substr})
|
|
}
|