k8s.io/apiserver@v0.31.1/pkg/cel/library/regex.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package library
    18  
    19  import (
    20  	"regexp"
    21  
    22  	"github.com/google/cel-go/cel"
    23  	"github.com/google/cel-go/common/types"
    24  	"github.com/google/cel-go/common/types/ref"
    25  	"github.com/google/cel-go/interpreter"
    26  )
    27  
    28  // Regex provides a CEL function library extension of regex utility functions.
    29  //
    30  // find / findAll
    31  //
    32  // Returns substrings that match the provided regular expression. find returns the first match. findAll may optionally
    33  // be provided a limit. If the limit is set and >= 0, no more than the limit number of matches are returned.
    34  //
    35  //	<string>.find(<string>) <string>
    36  //	<string>.findAll(<string>) <list <string>>
    37  //	<string>.findAll(<string>, <int>) <list <string>>
    38  //
    39  // Examples:
    40  //
    41  //	"abc 123".find('[0-9]*') // returns '123'
    42  //	"abc 123".find('xyz') // returns ''
    43  //	"123 abc 456".findAll('[0-9]*') // returns ['123', '456']
    44  //	"123 abc 456".findAll('[0-9]*', 1) // returns ['123']
    45  //	"123 abc 456".findAll('xyz') // returns []
    46  func Regex() cel.EnvOption {
    47  	return cel.Lib(regexLib)
    48  }
    49  
    50  var regexLib = &regex{}
    51  
    52  type regex struct{}
    53  
    54  func (*regex) LibraryName() string {
    55  	return "k8s.regex"
    56  }
    57  
    58  var regexLibraryDecls = map[string][]cel.FunctionOpt{
    59  	"find": {
    60  		cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
    61  			cel.BinaryBinding(find))},
    62  	"findAll": {
    63  		cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
    64  			cel.ListType(cel.StringType),
    65  			cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
    66  				return findAll(str, regex, types.Int(-1))
    67  			})),
    68  		cel.MemberOverload("string_find_all_string_int",
    69  			[]*cel.Type{cel.StringType, cel.StringType, cel.IntType},
    70  			cel.ListType(cel.StringType),
    71  			cel.FunctionBinding(findAll)),
    72  	},
    73  }
    74  
    75  func (*regex) CompileOptions() []cel.EnvOption {
    76  	options := []cel.EnvOption{}
    77  	for name, overloads := range regexLibraryDecls {
    78  		options = append(options, cel.Function(name, overloads...))
    79  	}
    80  	return options
    81  }
    82  
    83  func (*regex) ProgramOptions() []cel.ProgramOption {
    84  	return []cel.ProgramOption{
    85  		cel.OptimizeRegex(FindRegexOptimization, FindAllRegexOptimization),
    86  	}
    87  }
    88  
    89  func find(strVal ref.Val, regexVal ref.Val) ref.Val {
    90  	str, ok := strVal.Value().(string)
    91  	if !ok {
    92  		return types.MaybeNoSuchOverloadErr(strVal)
    93  	}
    94  	regex, ok := regexVal.Value().(string)
    95  	if !ok {
    96  		return types.MaybeNoSuchOverloadErr(regexVal)
    97  	}
    98  	re, err := regexp.Compile(regex)
    99  	if err != nil {
   100  		return types.NewErr("Illegal regex: %v", err.Error())
   101  	}
   102  	result := re.FindString(str)
   103  	return types.String(result)
   104  }
   105  
   106  func findAll(args ...ref.Val) ref.Val {
   107  	argn := len(args)
   108  	if argn < 2 || argn > 3 {
   109  		return types.NoSuchOverloadErr()
   110  	}
   111  	str, ok := args[0].Value().(string)
   112  	if !ok {
   113  		return types.MaybeNoSuchOverloadErr(args[0])
   114  	}
   115  	regex, ok := args[1].Value().(string)
   116  	if !ok {
   117  		return types.MaybeNoSuchOverloadErr(args[1])
   118  	}
   119  	n := int64(-1)
   120  	if argn == 3 {
   121  		n, ok = args[2].Value().(int64)
   122  		if !ok {
   123  			return types.MaybeNoSuchOverloadErr(args[2])
   124  		}
   125  	}
   126  
   127  	re, err := regexp.Compile(regex)
   128  	if err != nil {
   129  		return types.NewErr("Illegal regex: %v", err.Error())
   130  	}
   131  
   132  	result := re.FindAllString(str, int(n))
   133  
   134  	return types.NewStringList(types.DefaultTypeAdapter, result)
   135  }
   136  
   137  // FindRegexOptimization optimizes the 'find' function by compiling the regex pattern and
   138  // reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
   139  // call invocations.
   140  var FindRegexOptimization = &interpreter.RegexOptimization{
   141  	Function:   "find",
   142  	RegexIndex: 1,
   143  	Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
   144  		compiledRegex, err := regexp.Compile(regexPattern)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  		return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
   149  			if len(args) != 2 {
   150  				return types.NoSuchOverloadErr()
   151  			}
   152  			in, ok := args[0].Value().(string)
   153  			if !ok {
   154  				return types.MaybeNoSuchOverloadErr(args[0])
   155  			}
   156  			return types.String(compiledRegex.FindString(in))
   157  		}), nil
   158  	},
   159  }
   160  
   161  // FindAllRegexOptimization optimizes the 'findAll' function by compiling the regex pattern and
   162  // reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
   163  // call invocations.
   164  var FindAllRegexOptimization = &interpreter.RegexOptimization{
   165  	Function:   "findAll",
   166  	RegexIndex: 1,
   167  	Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
   168  		compiledRegex, err := regexp.Compile(regexPattern)
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  		return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
   173  			argn := len(args)
   174  			if argn < 2 || argn > 3 {
   175  				return types.NoSuchOverloadErr()
   176  			}
   177  			str, ok := args[0].Value().(string)
   178  			if !ok {
   179  				return types.MaybeNoSuchOverloadErr(args[0])
   180  			}
   181  			n := int64(-1)
   182  			if argn == 3 {
   183  				n, ok = args[2].Value().(int64)
   184  				if !ok {
   185  					return types.MaybeNoSuchOverloadErr(args[2])
   186  				}
   187  			}
   188  
   189  			result := compiledRegex.FindAllString(str, int(n))
   190  			return types.NewStringList(types.DefaultTypeAdapter, result)
   191  		}), nil
   192  	},
   193  }