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  }