github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/funcs/string.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package funcs 5 6 import ( 7 "regexp" 8 "strings" 9 10 "github.com/zclconf/go-cty/cty" 11 "github.com/zclconf/go-cty/cty/function" 12 ) 13 14 // StartsWithFunc constructs a function that checks if a string starts with 15 // a specific prefix using strings.HasPrefix 16 var StartsWithFunc = function.New(&function.Spec{ 17 Params: []function.Parameter{ 18 { 19 Name: "str", 20 Type: cty.String, 21 AllowUnknown: true, 22 }, 23 { 24 Name: "prefix", 25 Type: cty.String, 26 }, 27 }, 28 Type: function.StaticReturnType(cty.Bool), 29 RefineResult: refineNotNull, 30 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 31 prefix := args[1].AsString() 32 33 if !args[0].IsKnown() { 34 // If the unknown value has a known prefix then we might be 35 // able to still produce a known result. 36 if prefix == "" { 37 // The empty string is a prefix of any string. 38 return cty.True, nil 39 } 40 if knownPrefix := args[0].Range().StringPrefix(); knownPrefix != "" { 41 if strings.HasPrefix(knownPrefix, prefix) { 42 return cty.True, nil 43 } 44 if len(knownPrefix) >= len(prefix) { 45 // If the prefix we're testing is no longer than the known 46 // prefix and it didn't match then the full string with 47 // that same prefix can't match either. 48 return cty.False, nil 49 } 50 } 51 return cty.UnknownVal(cty.Bool), nil 52 } 53 54 str := args[0].AsString() 55 56 if strings.HasPrefix(str, prefix) { 57 return cty.True, nil 58 } 59 60 return cty.False, nil 61 }, 62 }) 63 64 // EndsWithFunc constructs a function that checks if a string ends with 65 // a specific suffix using strings.HasSuffix 66 var EndsWithFunc = function.New(&function.Spec{ 67 Params: []function.Parameter{ 68 { 69 Name: "str", 70 Type: cty.String, 71 }, 72 { 73 Name: "suffix", 74 Type: cty.String, 75 }, 76 }, 77 Type: function.StaticReturnType(cty.Bool), 78 RefineResult: refineNotNull, 79 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 80 str := args[0].AsString() 81 suffix := args[1].AsString() 82 83 if strings.HasSuffix(str, suffix) { 84 return cty.True, nil 85 } 86 87 return cty.False, nil 88 }, 89 }) 90 91 // ReplaceFunc constructs a function that searches a given string for another 92 // given substring, and replaces each occurence with a given replacement string. 93 var ReplaceFunc = function.New(&function.Spec{ 94 Params: []function.Parameter{ 95 { 96 Name: "str", 97 Type: cty.String, 98 }, 99 { 100 Name: "substr", 101 Type: cty.String, 102 }, 103 { 104 Name: "replace", 105 Type: cty.String, 106 }, 107 }, 108 Type: function.StaticReturnType(cty.String), 109 RefineResult: refineNotNull, 110 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 111 str := args[0].AsString() 112 substr := args[1].AsString() 113 replace := args[2].AsString() 114 115 // We search/replace using a regexp if the string is surrounded 116 // in forward slashes. 117 if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' { 118 re, err := regexp.Compile(substr[1 : len(substr)-1]) 119 if err != nil { 120 return cty.UnknownVal(cty.String), err 121 } 122 123 return cty.StringVal(re.ReplaceAllString(str, replace)), nil 124 } 125 126 return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil 127 }, 128 }) 129 130 // StrContainsFunc searches a given string for another given substring, 131 // if found the function returns true, otherwise returns false. 132 var StrContainsFunc = function.New(&function.Spec{ 133 Params: []function.Parameter{ 134 { 135 Name: "str", 136 Type: cty.String, 137 }, 138 { 139 Name: "substr", 140 Type: cty.String, 141 }, 142 }, 143 Type: function.StaticReturnType(cty.Bool), 144 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 145 str := args[0].AsString() 146 substr := args[1].AsString() 147 148 if strings.Contains(str, substr) { 149 return cty.True, nil 150 } 151 152 return cty.False, nil 153 }, 154 }) 155 156 // Replace searches a given string for another given substring, 157 // and replaces all occurences with a given replacement string. 158 func Replace(str, substr, replace cty.Value) (cty.Value, error) { 159 return ReplaceFunc.Call([]cty.Value{str, substr, replace}) 160 } 161 162 func StrContains(str, substr cty.Value) (cty.Value, error) { 163 return StrContainsFunc.Call([]cty.Value{str, substr}) 164 }