lib/strings.go (943 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package lib import ( "bytes" "fmt" "strings" "unicode/utf8" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" ) // Strings returns a cel.EnvOption to configure extended functions for // handling strings. // // All functions provided by Strings are methods on the string type or list<string> type // with the exception of to_valid_utf8 and valid_utf8 which are methods on the bytes type. // // Relevant documentation for the methods can obtained from the Go standard library. In all // cases the first parameter in the Go function corresponds to the CEL method receiver. // // # String Methods // // - compare: strings.Compare(a, b string) int // - contains_substr: strings.Contains(s, substr string) bool // - contains_any: strings.ContainsAny(s, chars string) bool // - count: strings.Count(s, substr string) int // - equal_fold: strings.EqualFold(s, t string) bool // - fields: strings.Fields(s string) []string // - has_prefix: strings.HasPrefix(s, prefix string) bool // - has_suffix: strings.HasSuffix(s, suffix string) bool // - index: strings.Index(s, substr string) int // - index_any: strings.IndexAny(s, chars string) int // - last_index: strings.LastIndex(s, substr string) int // - last_index_any: strings.LastIndexAny(s, chars string) int // - repeat: strings.Repeat(s string, count int) string // - replace: strings.Replace(s, old, new string, n int) string // - replace_all: strings.ReplaceAll(s, old, new string) string // - split: strings.Split(s, sep string) []string // - split_after: strings.SplitAfter(s, sep string) []string // - split_after_n: strings.SplitAfterN(s, sep string, n int) []string // - split_n: strings.SplitN(s, sep string, n int) []string // - to_lower: strings.ToLower(s string) string // - to_title: strings.ToTitle(s string) string // - to_upper: strings.ToUpper(s string) string // - trim: strings.Trim(s, cutset string) string // - trim_left: strings.TrimLeft(s, cutset string) string // - trim_prefix: strings.TrimPrefix(s, prefix string) string // - trim_right: strings.TrimRight(s, cutset string) string // - trim_space: strings.TrimSpace(s string) string // - trim_suffix: strings.TrimSuffix(s, suffix string) string // // In addition to the strings package functions, a sub-string method is provided that allows // string slicing at unicode code point boundaries. It differs from Go's string slicing // operator which may slice within a code point resulting in invalid UTF-8. The substring // method will always return a valid UTF-8 string, or an error if the indexes are out of // bounds or invalid. // // - substring: s[start:end] // // # String List Methods // // - join: strings.Join(elems []string, sep string) string // // # Bytes Methods // // The to_valid_utf8 method is equivalent to strings.ToValidUTF8 with the receiver first // converted to a Go string. This special case is required as CEL does not permit invalid // UTF-8 string conversions. // // - to_valid_utf8: strings.ToValidUTF8(s, replacement string) string // - valid_utf8: utf8.Valid(s []byte) bool // - compare: bytes.Compare(a, b []byte) int // - contains_substr: bytes.Contains(s, substr []byte) bool // - has_prefix: bytes.HasPrefix(s, prefix []byte) bool // - has_suffix: bytes.HasSuffix(s, suffix []byte) bool // - index: bytes.Index(s, substr []byte) int // - last_index: bytes.LastIndex(s, substr []byte) int // - trim: bytes.Trim(s []byte, cutset string) []byte // - trim_left: bytes.TrimLeft(s []byte, cutset string) []byte // - trim_prefix: bytes.TrimPrefix(s, prefix []byte) []byte // - trim_right: bytes.TrimRight(s []byte, cutset string) []byte // - trim_space: bytes.TrimSpace(s []byte) []byte // - trim_suffix: bytes.TrimSuffix(s, suffix []byte) []byte // // The substring method on bytes slices has the same semantics as the Go byte slice // slicing operation. // // - substring: s[start:end] func Strings() cel.EnvOption { return cel.Lib(stringLib{}) } type stringLib struct{} func (l stringLib) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Function("compare", cel.MemberOverload( "string_compare_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.compare), ), cel.MemberOverload( "bytes_compare_bytes_int", []*cel.Type{cel.BytesType, cel.BytesType}, cel.IntType, cel.BinaryBinding(l.compareBytes), ), ), cel.Function("contains_substr", /* required to disambiguate from regexp.contains.*/ cel.MemberOverload( "string_contains_substr_string_bool", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(l.contains), ), cel.MemberOverload( "bytes_contains_substr_bytes_bool", []*cel.Type{cel.BytesType, cel.BytesType}, cel.BoolType, cel.BinaryBinding(l.containsBytes), ), ), cel.Function("contains_any", cel.MemberOverload( "string_contains_any_string_bool", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(l.containsAny), ), ), cel.Function("count", cel.MemberOverload( "string_count_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.count), ), ), cel.Function("equal_fold", cel.MemberOverload( "string_equal_fold_string_bool", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(l.equalFold), ), ), cel.Function("fields", cel.MemberOverload( "string_fields_list_string", []*cel.Type{cel.StringType}, listString, cel.UnaryBinding(l.fields), ), ), cel.Function("has_prefix", cel.MemberOverload( "string_has_prefix_string_bool", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(l.hasPrefix), ), cel.MemberOverload( "bytes_has_prefix_bytes_bool", []*cel.Type{cel.BytesType, cel.BytesType}, cel.BoolType, cel.BinaryBinding(l.hasPrefixBytes), ), ), cel.Function("has_suffix", cel.MemberOverload( "string_has_suffix_string_bool", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(l.hasSuffix), ), cel.MemberOverload( "bytes_has_suffix_bytes_bool", []*cel.Type{cel.BytesType, cel.BytesType}, cel.BoolType, cel.BinaryBinding(l.hasSuffixBytes), ), ), cel.Function("index", cel.MemberOverload( "string_index_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.index), ), cel.MemberOverload( "bytes_index_bytes_int", []*cel.Type{cel.BytesType, cel.BytesType}, cel.IntType, cel.BinaryBinding(l.indexBytes), ), ), cel.Function("index_any", cel.MemberOverload( "string_index_any_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.indexAny), ), ), cel.Function("join", cel.MemberOverload( "list_string_join_string_string", []*cel.Type{listString, cel.StringType}, cel.StringType, cel.BinaryBinding(l.join), ), ), cel.Function("last_index", cel.MemberOverload( "string_last_index_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.lastIndex), ), cel.MemberOverload( "bytes_last_index_bytes_int", []*cel.Type{cel.BytesType, cel.BytesType}, cel.IntType, cel.BinaryBinding(l.lastIndexBytes), ), ), cel.Function("last_index_any", cel.MemberOverload( "string_last_index_any_string_int", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType, cel.BinaryBinding(l.lastIndexAny), ), ), cel.Function("repeat", cel.MemberOverload( "string_repeat_int_string", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType, cel.BinaryBinding(l.repeat), ), ), cel.Function("replace", cel.MemberOverload( "string_replace_string_string_int_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType, cel.IntType}, cel.StringType, cel.FunctionBinding(l.replace), ), ), cel.Function("replace_all", cel.MemberOverload( "string_replace_all_string_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, cel.StringType, cel.FunctionBinding(l.replaceAll), ), ), cel.Function("split", cel.MemberOverload( "string_split_string_list_string", []*cel.Type{cel.StringType, cel.StringType}, listString, cel.BinaryBinding(l.split), ), ), cel.Function("split_after", cel.MemberOverload( "string_split_after_string_list_string", []*cel.Type{cel.StringType, cel.StringType}, listString, cel.BinaryBinding(l.splitAfter), ), ), cel.Function("split_after_n", cel.MemberOverload( "string_split_after_n_string_int_list_string", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, listString, cel.FunctionBinding(l.splitAfterN), ), ), cel.Function("split_n", cel.MemberOverload( "string_split_n_string_int_list_string", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, listString, cel.FunctionBinding(l.splitN), ), ), cel.Function("substring", cel.MemberOverload( "string_substring_int_int_string", []*cel.Type{cel.StringType, cel.IntType, cel.IntType}, cel.StringType, cel.FunctionBinding(l.substring), ), cel.MemberOverload( "bytes_substring_int_int_bytes", []*cel.Type{cel.BytesType, cel.IntType, cel.IntType}, cel.BytesType, cel.FunctionBinding(l.substringBytes), ), ), cel.Function("to_lower", cel.MemberOverload( "string_to_lower_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(l.toLower), ), ), cel.Function("to_title", cel.MemberOverload( "string_to_title_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(l.toTitle), ), ), cel.Function("to_upper", cel.MemberOverload( "string_to_upper_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(l.toUpper), ), ), cel.Function("to_valid_utf8", cel.MemberOverload( "bytes_to_valid_utf8_string_string", []*cel.Type{cel.BytesType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.toValidUTF8), ), ), cel.Function("trim", cel.MemberOverload( "string_trim_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.trim), ), cel.MemberOverload( "bytes_trim_bytes_string", []*cel.Type{cel.BytesType, cel.StringType}, cel.BytesType, cel.BinaryBinding(l.trimBytes), ), ), cel.Function("trim_left", cel.MemberOverload( "string_trim_left_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.trimLeft), ), cel.MemberOverload( "bytes_trim_left_bytes_string", []*cel.Type{cel.BytesType, cel.StringType}, cel.BytesType, cel.BinaryBinding(l.trimLeftBytes), ), ), cel.Function("trim_prefix", cel.MemberOverload( "string_trim_prefix_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.trimPrefix), ), cel.MemberOverload( "bytes_trim_prefix_bytes_bytes", []*cel.Type{cel.BytesType, cel.BytesType}, cel.BytesType, cel.BinaryBinding(l.trimPrefixBytes), ), ), cel.Function("trim_right", cel.MemberOverload( "string_trim_right_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.trimRight), ), cel.MemberOverload( "bytes_trim_right_bytes_string", []*cel.Type{cel.BytesType, cel.StringType}, cel.BytesType, cel.BinaryBinding(l.trimRightBytes), ), ), cel.Function("trim_space", cel.MemberOverload( "string_trim_space_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(l.trimSpace), ), cel.MemberOverload( "bytes_trim_space_bytes", []*cel.Type{cel.BytesType}, cel.BytesType, cel.UnaryBinding(l.trimSpaceBytes), ), ), cel.Function("trim_suffix", cel.MemberOverload( "string_trim_suffix_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(l.trimSuffix), ), cel.MemberOverload( "bytes_trim_suffix_bytes_bytes", []*cel.Type{cel.BytesType, cel.BytesType}, cel.BytesType, cel.BinaryBinding(l.trimSuffixBytes), ), ), cel.Function("valid_utf8", cel.MemberOverload( "bytes_valid_utf8_bool", []*cel.Type{cel.BytesType}, cel.BoolType, cel.UnaryBinding(l.validString), ), ), } } func (stringLib) ProgramOptions() []cel.ProgramOption { return nil } func (l stringLib) compare(arg0, arg1 ref.Val) ref.Val { a, ok := arg0.(types.String) if !ok { return types.ValOrErr(a, "no such overload for compare") } b, ok := arg1.(types.String) if !ok { return types.ValOrErr(b, "no such overload for compare") } return types.DefaultTypeAdapter.NativeToValue(strings.Compare(string(a), string(b))) } func (l stringLib) compareBytes(arg0, arg1 ref.Val) ref.Val { a, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(a, "no such overload for compare") } b, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(b, "no such overload for compare") } return types.DefaultTypeAdapter.NativeToValue(bytes.Compare([]byte(a), []byte(b))) } func (l stringLib) contains(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for contains_substr") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for contains_substr") } return types.DefaultTypeAdapter.NativeToValue(strings.Contains(string(s), string(substr))) } func (l stringLib) containsBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for contains_substr") } substr, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(substr, "no such overload for contains_substr") } return types.DefaultTypeAdapter.NativeToValue(bytes.Contains([]byte(s), []byte(substr))) } func (l stringLib) containsAny(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for contains_any") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for contains_any") } return types.DefaultTypeAdapter.NativeToValue(strings.ContainsAny(string(s), string(substr))) } func (l stringLib) count(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for count") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for count") } return types.DefaultTypeAdapter.NativeToValue(strings.Count(string(s), string(substr))) } func (l stringLib) equalFold(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for equal_fold") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for equal_fold") } return types.DefaultTypeAdapter.NativeToValue(strings.EqualFold(string(s), string(substr))) } func (l stringLib) fields(arg ref.Val) ref.Val { s, ok := arg.(types.String) if !ok { return types.ValOrErr(s, "no such overload for fields") } return types.DefaultTypeAdapter.NativeToValue(strings.Fields(string(s))) } func (l stringLib) hasPrefix(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for has_prefix") } prefix, ok := arg1.(types.String) if !ok { return types.ValOrErr(prefix, "no such overload for has_prefix") } return types.DefaultTypeAdapter.NativeToValue(strings.HasPrefix(string(s), string(prefix))) } func (l stringLib) hasPrefixBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for has_prefix") } prefix, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(prefix, "no such overload for has_prefix") } return types.DefaultTypeAdapter.NativeToValue(bytes.HasPrefix([]byte(s), []byte(prefix))) } func (l stringLib) hasSuffix(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for has_suffix") } suffix, ok := arg1.(types.String) if !ok { return types.ValOrErr(suffix, "no such overload for has_suffix") } return types.DefaultTypeAdapter.NativeToValue(strings.HasSuffix(string(s), string(suffix))) } func (l stringLib) hasSuffixBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for has_suffix") } suffix, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(suffix, "no such overload for has_suffix") } return types.DefaultTypeAdapter.NativeToValue(bytes.HasSuffix([]byte(s), []byte(suffix))) } func (l stringLib) index(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for index") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for index") } return types.DefaultTypeAdapter.NativeToValue(strings.Index(string(s), string(substr))) } func (l stringLib) indexBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for index") } substr, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(substr, "no such overload for index") } return types.DefaultTypeAdapter.NativeToValue(bytes.Index([]byte(s), []byte(substr))) } func (l stringLib) indexAny(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for index_any") } chars, ok := arg1.(types.String) if !ok { return types.ValOrErr(chars, "no such overload for index_any") } return types.DefaultTypeAdapter.NativeToValue(strings.IndexAny(string(s), string(chars))) } func (l stringLib) join(arg0, arg1 ref.Val) ref.Val { elems, err := arg0.ConvertToNative(reflectStringSliceType) if err != nil { return types.NewErr("no such overload for index_any") } sep, ok := arg1.(types.String) if !ok { return types.ValOrErr(sep, "no such overload for index_any") } return types.DefaultTypeAdapter.NativeToValue(strings.Join(elems.([]string), string(sep))) } func (l stringLib) lastIndex(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for last_index") } substr, ok := arg1.(types.String) if !ok { return types.ValOrErr(substr, "no such overload for last_index") } return types.DefaultTypeAdapter.NativeToValue(strings.LastIndex(string(s), string(substr))) } func (l stringLib) lastIndexBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for last_index") } substr, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(substr, "no such overload for last_index") } return types.DefaultTypeAdapter.NativeToValue(bytes.LastIndex([]byte(s), []byte(substr))) } func (l stringLib) lastIndexAny(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for last_index_any") } chars, ok := arg1.(types.String) if !ok { return types.ValOrErr(chars, "no such overload for last_index_any") } return types.DefaultTypeAdapter.NativeToValue(strings.LastIndexAny(string(s), string(chars))) } func (l stringLib) repeat(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for repeat") } chars, ok := arg1.(types.Int) if !ok { return types.ValOrErr(chars, "no such overload for repeat") } return types.DefaultTypeAdapter.NativeToValue(strings.Repeat(string(s), int(chars))) } func (l stringLib) replace(args ...ref.Val) ref.Val { if len(args) != 4 { return types.NewErr("no such overload for replace") } s, ok := args[0].(types.String) if !ok { return types.ValOrErr(s, "no such overload for replace") } old, ok := args[1].(types.String) if !ok { return types.ValOrErr(old, "no such overload for replace") } new, ok := args[2].(types.String) if !ok { return types.ValOrErr(old, "no such overload for replace") } n, ok := args[3].(types.Int) if !ok { return types.ValOrErr(n, "no such overload for replace") } return types.DefaultTypeAdapter.NativeToValue(strings.Replace(string(s), string(old), string(new), int(n))) } func (l stringLib) replaceAll(args ...ref.Val) ref.Val { if len(args) != 3 { return types.NewErr("no such overload for replace_all") } s, ok := args[0].(types.String) if !ok { return types.ValOrErr(s, "no such overload for replace_all") } old, ok := args[1].(types.String) if !ok { return types.ValOrErr(old, "no such overload for replace_all") } new, ok := args[2].(types.String) if !ok { return types.ValOrErr(new, "no such overload for replace_all") } return types.DefaultTypeAdapter.NativeToValue(strings.ReplaceAll(string(s), string(old), string(new))) } func (l stringLib) split(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for split") } sep, ok := arg1.(types.String) if !ok { return types.ValOrErr(sep, "no such overload for split") } return types.DefaultTypeAdapter.NativeToValue(strings.Split(string(s), string(sep))) } func (l stringLib) splitAfter(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for split_after") } sep, ok := arg1.(types.String) if !ok { return types.ValOrErr(sep, "no such overload for split_after") } return types.DefaultTypeAdapter.NativeToValue(strings.SplitAfter(string(s), string(sep))) } func (l stringLib) splitAfterN(args ...ref.Val) ref.Val { if len(args) != 3 { return types.NewErr("no such overload for split_after_n") } s, ok := args[0].(types.String) if !ok { return types.ValOrErr(s, "no such overload for split_after_n") } sep, ok := args[1].(types.String) if !ok { return types.ValOrErr(sep, "no such overload for split_after_n") } n, ok := args[2].(types.Int) if !ok { return types.ValOrErr(n, "no such overload for split_after_n") } return types.DefaultTypeAdapter.NativeToValue(strings.SplitAfterN(string(s), string(sep), int(n))) } func (l stringLib) splitN(args ...ref.Val) ref.Val { if len(args) != 3 { return types.NewErr("no such overload for split_n") } s, ok := args[0].(types.String) if !ok { return types.ValOrErr(s, "no such overload for split_n") } sep, ok := args[1].(types.String) if !ok { return types.ValOrErr(sep, "no such overload for split_n") } n, ok := args[2].(types.Int) if !ok { return types.ValOrErr(n, "no such overload for split_n") } return types.DefaultTypeAdapter.NativeToValue(strings.SplitN(string(s), string(sep), int(n))) } // The obvious test string: 零一二三四五六七八九十 func (l stringLib) substring(args ...ref.Val) ref.Val { if len(args) != 3 { return types.NewErr("no such overload for substring") } s, ok := args[0].(types.String) if !ok { return types.ValOrErr(s, "no such overload for substring") } start, ok := args[1].(types.Int) if !ok { return types.ValOrErr(start, "no such overload for substring") } if start < 0 { return types.NewErr("substring: start out of range: %d < 0", start) } end, ok := args[2].(types.Int) if !ok { return types.ValOrErr(end, "no such overload for substring") } if end < start { return types.NewErr("substring: end out of range: %d < %d", end, start) } i, pos, left := 0, 0, -1 for ; pos <= len(s); i++ { if i == int(start) { left = pos } if i == int(end) { // TODO: Consider adding a heuristic to decide if the // substring should be cloned to avoid pinning s. return types.DefaultTypeAdapter.NativeToValue(s[left:pos]) } if pos == len(s) { break } r, size := utf8.DecodeRuneInString(string(s[pos:])) if r == utf8.RuneError { return types.NewErr("substring: invalid rune at position %d in string: %s", pos, s) } pos += size } if left == -1 { return types.NewErr("substring: start out of range: %d > %d", start, i) } return types.NewErr("substring: end out of range: %d > %d", end, i) } func (l stringLib) substringBytes(args ...ref.Val) ref.Val { if len(args) != 3 { return types.NewErr("no such overload for substring") } s, ok := args[0].(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for substring") } start, ok := args[1].(types.Int) if !ok { return types.ValOrErr(start, "no such overload for substring") } if start < 0 { return types.NewErr("substring: start out of range: %d < 0", start) } end, ok := args[2].(types.Int) if !ok { return types.ValOrErr(end, "no such overload for substring") } if end < start { return types.NewErr("substring: end out of range: %d < %d", end, start) } return types.DefaultTypeAdapter.NativeToValue(s[start:end]) } func (l stringLib) toLower(arg ref.Val) ref.Val { s, ok := arg.(types.String) if !ok { return types.ValOrErr(s, "no such overload for to_lower") } return types.DefaultTypeAdapter.NativeToValue(strings.ToLower(string(s))) } func (l stringLib) toTitle(arg ref.Val) ref.Val { s, ok := arg.(types.String) if !ok { return types.ValOrErr(s, "no such overload for to_title") } return types.DefaultTypeAdapter.NativeToValue(strings.ToTitle(string(s))) } func (l stringLib) toUpper(arg ref.Val) ref.Val { s, ok := arg.(types.String) if !ok { return types.ValOrErr(s, "no such overload for to_upper") } return types.DefaultTypeAdapter.NativeToValue(strings.ToUpper(string(s))) } func (l stringLib) toValidUTF8(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for to_valid_utf8") } replacement, ok := arg1.(types.String) if !ok { return types.ValOrErr(replacement, "no such overload for to_valid_utf8") } return types.DefaultTypeAdapter.NativeToValue(strings.ToValidUTF8(string(s), string(replacement))) } func (l stringLib) trim(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim") } return types.DefaultTypeAdapter.NativeToValue(strings.Trim(string(s), string(cutset))) } func (l stringLib) trimBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim") } return types.DefaultTypeAdapter.NativeToValue(bytes.Trim([]byte(s), string(cutset))) } func (l stringLib) trimLeft(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim_left") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim_left") } return types.DefaultTypeAdapter.NativeToValue(strings.TrimLeft(string(s), string(cutset))) } func (l stringLib) trimLeftBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim_left") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim_left") } return types.DefaultTypeAdapter.NativeToValue(bytes.TrimLeft([]byte(s), string(cutset))) } func (l stringLib) trimPrefix(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim_prefix") } prefix, ok := arg1.(types.String) if !ok { return types.ValOrErr(prefix, "no such overload for trim_prefix") } return types.DefaultTypeAdapter.NativeToValue(strings.TrimPrefix(string(s), string(prefix))) } func (l stringLib) trimPrefixBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim_prefix") } prefix, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(prefix, "no such overload for trim_prefix") } return types.DefaultTypeAdapter.NativeToValue(bytes.TrimPrefix([]byte(s), []byte(prefix))) } func (l stringLib) trimRight(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim_right") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim_right") } return types.DefaultTypeAdapter.NativeToValue(strings.TrimRight(string(s), string(cutset))) } func (l stringLib) trimRightBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim_right") } cutset, ok := arg1.(types.String) if !ok { return types.ValOrErr(cutset, "no such overload for trim_right") } return types.DefaultTypeAdapter.NativeToValue(bytes.TrimRight([]byte(s), string(cutset))) } func (l stringLib) trimSpace(arg ref.Val) ref.Val { s, ok := arg.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim_space") } return types.DefaultTypeAdapter.NativeToValue(strings.TrimSpace(string(s))) } func (l stringLib) trimSpaceBytes(arg ref.Val) ref.Val { s, ok := arg.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim_space") } return types.DefaultTypeAdapter.NativeToValue(bytes.TrimSpace([]byte(s))) } func (l stringLib) trimSuffix(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.String) if !ok { return types.ValOrErr(s, "no such overload for trim_suffix") } suffix, ok := arg1.(types.String) if !ok { return types.ValOrErr(suffix, "no such overload for trim_suffix") } return types.DefaultTypeAdapter.NativeToValue(strings.TrimSuffix(string(s), string(suffix))) } func (l stringLib) trimSuffixBytes(arg0, arg1 ref.Val) ref.Val { s, ok := arg0.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for trim_suffix") } suffix, ok := arg1.(types.Bytes) if !ok { return types.ValOrErr(suffix, "no such overload for trim_suffix") } return types.DefaultTypeAdapter.NativeToValue(bytes.TrimSuffix([]byte(s), []byte(suffix))) } func (l stringLib) validString(arg ref.Val) ref.Val { s, ok := arg.(types.Bytes) if !ok { return types.ValOrErr(s, "no such overload for valid_utf8") } return types.DefaultTypeAdapter.NativeToValue(utf8.Valid([]byte(s))) } // Printf returns a cel.EnvOption to configure an extended function for // formatting strings and associated arguments. // // # Sprintf // // Formats a string from a format string and a set of arguments using the // Go fmt syntax: // // <string>.sprintf(<list<dyn>>) -> <string> // sprintf(<string>, <list<dyn>>) -> <string> // // Since CEL does not support variadic functions, arguments are provided // as an array corresponding to the normal Go variadic slice. // // Examples: // // "Hello, %s".sprintf(["World!"]) // return "Hello, World!" // sprintf("Hello, %s", ["World!"]) // return "Hello, World!" func Printf() cel.EnvOption { return cel.Lib(printfLib{}) } type printfLib struct{} func (l printfLib) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Function("sprintf", cel.MemberOverload( "sprintf_string_list_string", []*cel.Type{types.StringType, listDyn}, types.StringType, cel.BinaryBinding(l.sprintf), ), cel.Overload( "string_sprintf_list_string", []*cel.Type{types.StringType, listDyn}, types.StringType, cel.BinaryBinding(l.sprintf), ), ), } } func (l printfLib) ProgramOptions() []cel.ProgramOption { return nil } func (l printfLib) sprintf(arg0, arg1 ref.Val) ref.Val { format, ok := arg0.(types.String) if !ok { return types.ValOrErr(format, "no such overload for sprintf: format is not a string") } argList, ok := arg1.(traits.Lister) if !ok { return types.ValOrErr(argList, "no such overload for sprintf: args is not a list") } n, _ := argList.Size().(types.Int) // Continue since this is an optimisation. args := make([]any, 0, n) it := argList.Iterator() for i := 1; it.HasNext() == types.True; i++ { v, err := it.Next().ConvertToNative(reflectAnyType) if err != nil { return types.NewErr("failed to convert argument %d: %v", i, err) } args = append(args, v) } return types.String(fmt.Sprintf(string(format), args...)) }