Bump cel-go to v0.12.0

This commit is contained in:
Cici Huang
2022-07-07 17:13:57 +00:00
committed by cici37
parent aebe28b5cf
commit 772a252b06
131 changed files with 3842 additions and 1769 deletions

View File

@@ -15,12 +15,8 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cel:go_default_library",
"//checker/decls:go_default_library",
"//common/types:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//interpreter/functions:go_default_library",
"@org_golang_google_genproto//googleapis/api/expr/v1alpha1:go_default_library",
],
)

View File

@@ -16,12 +16,11 @@ package ext
import (
"encoding/base64"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// Encoders returns a cel.EnvOption to configure extended functions for string, byte, and object
@@ -57,42 +56,23 @@ type encoderLib struct{}
func (encoderLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Declarations(
decls.NewFunction("base64.decode",
decls.NewOverload("base64_decode_string",
[]*exprpb.Type{decls.String},
decls.Bytes)),
decls.NewFunction("base64.encode",
decls.NewOverload("base64_encode_bytes",
[]*exprpb.Type{decls.Bytes},
decls.String)),
),
cel.Function("base64.decode",
cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return bytesOrError(base64DecodeString(string(s)))
}))),
cel.Function("base64.encode",
cel.Overload("base64_encode_bytes", []*cel.Type{cel.BytesType}, cel.StringType,
cel.UnaryBinding(func(bytes ref.Val) ref.Val {
b := bytes.(types.Bytes)
return stringOrError(base64EncodeBytes([]byte(b)))
}))),
}
}
func (encoderLib) ProgramOptions() []cel.ProgramOption {
wrappedBase64EncodeBytes := callInBytesOutString(base64EncodeBytes)
wrappedBase64DecodeString := callInStrOutBytes(base64DecodeString)
return []cel.ProgramOption{
cel.Functions(
&functions.Overload{
Operator: "base64.decode",
Unary: wrappedBase64DecodeString,
},
&functions.Overload{
Operator: "base64_decode_string",
Unary: wrappedBase64DecodeString,
},
&functions.Overload{
Operator: "base64.encode",
Unary: wrappedBase64EncodeBytes,
},
&functions.Overload{
Operator: "base64_encode_bytes",
Unary: wrappedBase64EncodeBytes,
},
),
}
return []cel.ProgramOption{}
}
func base64DecodeString(str string) ([]byte, error) {
@@ -102,3 +82,7 @@ func base64DecodeString(str string) ([]byte, error) {
func base64EncodeBytes(bytes []byte) (string, error) {
return base64.StdEncoding.EncodeToString(bytes), nil
}
var (
bytesListType = reflect.TypeOf([]byte{})
)

View File

@@ -17,287 +17,34 @@ package ext
import (
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/interpreter/functions"
)
// function invocation guards for common call signatures within extension functions.
func callInBytesOutString(fn func([]byte) (string, error)) functions.UnaryOp {
return func(val ref.Val) ref.Val {
vVal, ok := val.(types.Bytes)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
str, err := fn([]byte(vVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(str)
func intOrError(i int64, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.Int(i)
}
func callInStrOutBytes(fn func(string) ([]byte, error)) functions.UnaryOp {
return func(val ref.Val) ref.Val {
vVal, ok := val.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
byt, err := fn(string(vVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.Bytes(byt)
func bytesOrError(bytes []byte, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.Bytes(bytes)
}
func callInStrOutStr(fn func(string) (string, error)) functions.UnaryOp {
return func(val ref.Val) ref.Val {
vVal, ok := val.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
str, err := fn(string(vVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(str)
func stringOrError(str string, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.String(str)
}
func callInStrIntOutStr(fn func(string, int64) (string, error)) functions.BinaryOp {
return func(val, arg ref.Val) ref.Val {
vVal, ok := val.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
argVal, ok := arg.(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
out, err := fn(string(vVal), int64(argVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(out)
}
}
func callInStrStrOutInt(fn func(string, string) (int64, error)) functions.BinaryOp {
return func(val, arg ref.Val) ref.Val {
vVal, ok := val.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
argVal, ok := arg.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
out, err := fn(string(vVal), string(argVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.Int(out)
}
}
func callInStrStrOutListStr(fn func(string, string) ([]string, error)) functions.BinaryOp {
return func(val, arg ref.Val) ref.Val {
vVal, ok := val.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
argVal, ok := arg.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
out, err := fn(string(vVal), string(argVal))
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(out)
}
}
func callInStrIntIntOutStr(fn func(string, int64, int64) (string, error)) functions.FunctionOp {
return func(args ...ref.Val) ref.Val {
if len(args) != 3 {
return types.NoSuchOverloadErr()
}
vVal, ok := args[0].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
arg1Val, ok := args[1].(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
arg2Val, ok := args[2].(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
out, err := fn(string(vVal), int64(arg1Val), int64(arg2Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(out)
}
}
func callInStrStrStrOutStr(fn func(string, string, string) (string, error)) functions.FunctionOp {
return func(args ...ref.Val) ref.Val {
if len(args) != 3 {
return types.NoSuchOverloadErr()
}
vVal, ok := args[0].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
arg1Val, ok := args[1].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
arg2Val, ok := args[2].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
out, err := fn(string(vVal), string(arg1Val), string(arg2Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(out)
}
}
func callInStrStrIntOutInt(fn func(string, string, int64) (int64, error)) functions.FunctionOp {
return func(args ...ref.Val) ref.Val {
if len(args) != 3 {
return types.NoSuchOverloadErr()
}
vVal, ok := args[0].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
arg1Val, ok := args[1].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
arg2Val, ok := args[2].(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
out, err := fn(string(vVal), string(arg1Val), int64(arg2Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.Int(out)
}
}
func callInStrStrIntOutListStr(fn func(string, string, int64) ([]string, error)) functions.FunctionOp {
return func(args ...ref.Val) ref.Val {
if len(args) != 3 {
return types.NoSuchOverloadErr()
}
vVal, ok := args[0].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
arg1Val, ok := args[1].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
arg2Val, ok := args[2].(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
out, err := fn(string(vVal), string(arg1Val), int64(arg2Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(out)
}
}
func callInStrStrStrIntOutStr(fn func(string, string, string, int64) (string, error)) functions.FunctionOp {
return func(args ...ref.Val) ref.Val {
if len(args) != 4 {
return types.NoSuchOverloadErr()
}
vVal, ok := args[0].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
arg1Val, ok := args[1].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
arg2Val, ok := args[2].(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
arg3Val, ok := args[3].(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(args[3])
}
out, err := fn(string(vVal), string(arg1Val), string(arg2Val), int64(arg3Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.String(out)
}
}
func callInListStrOutStr(fn func([]string) (string, error)) functions.UnaryOp {
return func(args1 ref.Val) ref.Val {
vVal, ok := args1.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(args1)
}
strings := make([]string, vVal.Size().Value().(int64))
i := 0
for it := vVal.Iterator(); it.HasNext() == types.True; {
next := it.Next()
v, ok := next.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(next)
}
strings[i] = string(v)
i++
}
out, err := fn(strings)
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(out)
}
}
func callInListStrStrOutStr(fn func([]string, string) (string, error)) functions.BinaryOp {
return func(args1, args2 ref.Val) ref.Val {
vVal, ok := args1.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(args1)
}
arg1Val, ok := args2.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(args2)
}
strings := make([]string, vVal.Size().Value().(int64))
i := 0
for it := vVal.Iterator(); it.HasNext() == types.True; {
next := it.Next()
v, ok := next.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(next)
}
strings[i] = string(v)
i++
}
out, err := fn(strings, string(arg1Val))
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(out)
func listStringOrError(strs []string, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(strs)
}

View File

@@ -19,16 +19,13 @@ package ext
import (
"fmt"
"reflect"
"strings"
"unicode"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// Strings returns a cel.EnvOption to configure extended functions for string manipulation.
@@ -207,194 +204,128 @@ type stringLib struct{}
func (stringLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Declarations(
decls.NewFunction("charAt",
decls.NewInstanceOverload("string_char_at_int",
[]*exprpb.Type{decls.String, decls.Int},
decls.String)),
decls.NewFunction("indexOf",
decls.NewInstanceOverload("string_index_of_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Int),
decls.NewInstanceOverload("string_index_of_string_int",
[]*exprpb.Type{decls.String, decls.String, decls.Int},
decls.Int)),
decls.NewFunction("lastIndexOf",
decls.NewInstanceOverload("string_last_index_of_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Int),
decls.NewInstanceOverload("string_last_index_of_string_int",
[]*exprpb.Type{decls.String, decls.String, decls.Int},
decls.Int)),
decls.NewFunction("lowerAscii",
decls.NewInstanceOverload("string_lower_ascii",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("replace",
decls.NewInstanceOverload("string_replace_string_string",
[]*exprpb.Type{decls.String, decls.String, decls.String},
decls.String),
decls.NewInstanceOverload("string_replace_string_string_int",
[]*exprpb.Type{decls.String, decls.String, decls.String, decls.Int},
decls.String)),
decls.NewFunction("split",
decls.NewInstanceOverload("string_split_string",
[]*exprpb.Type{decls.String, decls.String},
decls.NewListType(decls.String)),
decls.NewInstanceOverload("string_split_string_int",
[]*exprpb.Type{decls.String, decls.String, decls.Int},
decls.NewListType(decls.String))),
decls.NewFunction("substring",
decls.NewInstanceOverload("string_substring_int",
[]*exprpb.Type{decls.String, decls.Int},
decls.String),
decls.NewInstanceOverload("string_substring_int_int",
[]*exprpb.Type{decls.String, decls.Int, decls.Int},
decls.String)),
decls.NewFunction("trim",
decls.NewInstanceOverload("string_trim",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("upperAscii",
decls.NewInstanceOverload("string_upper_ascii",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("join",
decls.NewInstanceOverload("list_join",
[]*exprpb.Type{decls.NewListType(decls.String)},
decls.String),
decls.NewInstanceOverload("list_join_string",
[]*exprpb.Type{decls.NewListType(decls.String), decls.String},
decls.String),
),
),
cel.Function("charAt",
cel.MemberOverload("string_char_at_int", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType,
cel.BinaryBinding(func(str, ind ref.Val) ref.Val {
s := str.(types.String)
i := ind.(types.Int)
return stringOrError(charAt(string(s), int64(i)))
}))),
cel.Function("indexOf",
cel.MemberOverload("string_index_of_string", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType,
cel.BinaryBinding(func(str, substr ref.Val) ref.Val {
s := str.(types.String)
sub := substr.(types.String)
return intOrError(indexOf(string(s), string(sub)))
})),
cel.MemberOverload("string_index_of_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.IntType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sub := args[1].(types.String)
offset := args[2].(types.Int)
return intOrError(indexOfOffset(string(s), string(sub), int64(offset)))
}))),
cel.Function("lastIndexOf",
cel.MemberOverload("string_last_index_of_string", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType,
cel.BinaryBinding(func(str, substr ref.Val) ref.Val {
s := str.(types.String)
sub := substr.(types.String)
return intOrError(lastIndexOf(string(s), string(sub)))
})),
cel.MemberOverload("string_last_index_of_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.IntType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sub := args[1].(types.String)
offset := args[2].(types.Int)
return intOrError(lastIndexOfOffset(string(s), string(sub), int64(offset)))
}))),
cel.Function("lowerAscii",
cel.MemberOverload("string_lower_ascii", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(lowerASCII(string(s)))
}))),
cel.Function("replace",
cel.MemberOverload(
"string_replace_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
str := args[0].(types.String)
old := args[1].(types.String)
new := args[2].(types.String)
return stringOrError(replace(string(str), string(old), string(new)))
})),
cel.MemberOverload(
"string_replace_string_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.StringType, cel.IntType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
str := args[0].(types.String)
old := args[1].(types.String)
new := args[2].(types.String)
n := args[3].(types.Int)
return stringOrError(replaceN(string(str), string(old), string(new), int64(n)))
}))),
cel.Function("split",
cel.MemberOverload("string_split_string", []*cel.Type{cel.StringType, cel.StringType}, cel.ListType(cel.StringType),
cel.BinaryBinding(func(str, separator ref.Val) ref.Val {
s := str.(types.String)
sep := separator.(types.String)
return listStringOrError(split(string(s), string(sep)))
})),
cel.MemberOverload("string_split_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.ListType(cel.StringType),
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sep := args[1].(types.String)
n := args[2].(types.Int)
return listStringOrError(splitN(string(s), string(sep), int64(n)))
}))),
cel.Function("substring",
cel.MemberOverload("string_substring_int", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType,
cel.BinaryBinding(func(str, offset ref.Val) ref.Val {
s := str.(types.String)
off := offset.(types.Int)
return stringOrError(substr(string(s), int64(off)))
})),
cel.MemberOverload("string_substring_int_int", []*cel.Type{cel.StringType, cel.IntType, cel.IntType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
start := args[1].(types.Int)
end := args[2].(types.Int)
return stringOrError(substrRange(string(s), int64(start), int64(end)))
}))),
cel.Function("trim",
cel.MemberOverload("string_trim", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(trimSpace(string(s)))
}))),
cel.Function("upperAscii",
cel.MemberOverload("string_upper_ascii", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(upperASCII(string(s)))
}))),
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
return stringOrError(join(l.([]string)))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
d := delim.(types.String)
return stringOrError(joinSeparator(l.([]string), string(d)))
}))),
}
}
func (stringLib) ProgramOptions() []cel.ProgramOption {
wrappedReplace := callInStrStrStrOutStr(replace)
wrappedReplaceN := callInStrStrStrIntOutStr(replaceN)
return []cel.ProgramOption{
cel.Functions(
&functions.Overload{
Operator: "charAt",
Binary: callInStrIntOutStr(charAt),
},
&functions.Overload{
Operator: "string_char_at_int",
Binary: callInStrIntOutStr(charAt),
},
&functions.Overload{
Operator: "indexOf",
Binary: callInStrStrOutInt(indexOf),
Function: callInStrStrIntOutInt(indexOfOffset),
},
&functions.Overload{
Operator: "string_index_of_string",
Binary: callInStrStrOutInt(indexOf),
},
&functions.Overload{
Operator: "string_index_of_string_int",
Function: callInStrStrIntOutInt(indexOfOffset),
},
&functions.Overload{
Operator: "lastIndexOf",
Binary: callInStrStrOutInt(lastIndexOf),
Function: callInStrStrIntOutInt(lastIndexOfOffset),
},
&functions.Overload{
Operator: "string_last_index_of_string",
Binary: callInStrStrOutInt(lastIndexOf),
},
&functions.Overload{
Operator: "string_last_index_of_string_int",
Function: callInStrStrIntOutInt(lastIndexOfOffset),
},
&functions.Overload{
Operator: "lowerAscii",
Unary: callInStrOutStr(lowerASCII),
},
&functions.Overload{
Operator: "string_lower_ascii",
Unary: callInStrOutStr(lowerASCII),
},
&functions.Overload{
Operator: "replace",
Function: func(values ...ref.Val) ref.Val {
if len(values) == 3 {
return wrappedReplace(values...)
}
if len(values) == 4 {
return wrappedReplaceN(values...)
}
return types.NoSuchOverloadErr()
},
},
&functions.Overload{
Operator: "string_replace_string_string",
Function: wrappedReplace,
},
&functions.Overload{
Operator: "string_replace_string_string_int",
Function: wrappedReplaceN,
},
&functions.Overload{
Operator: "split",
Binary: callInStrStrOutListStr(split),
Function: callInStrStrIntOutListStr(splitN),
},
&functions.Overload{
Operator: "string_split_string",
Binary: callInStrStrOutListStr(split),
},
&functions.Overload{
Operator: "string_split_string_int",
Function: callInStrStrIntOutListStr(splitN),
},
&functions.Overload{
Operator: "substring",
Binary: callInStrIntOutStr(substr),
Function: callInStrIntIntOutStr(substrRange),
},
&functions.Overload{
Operator: "string_substring_int",
Binary: callInStrIntOutStr(substr),
},
&functions.Overload{
Operator: "string_substring_int_int",
Function: callInStrIntIntOutStr(substrRange),
},
&functions.Overload{
Operator: "trim",
Unary: callInStrOutStr(trimSpace),
},
&functions.Overload{
Operator: "string_trim",
Unary: callInStrOutStr(trimSpace),
},
&functions.Overload{
Operator: "upperAscii",
Unary: callInStrOutStr(upperASCII),
},
&functions.Overload{
Operator: "string_upper_ascii",
Unary: callInStrOutStr(upperASCII),
},
&functions.Overload{
Operator: "join",
Unary: callInListStrOutStr(join),
Binary: callInListStrStrOutStr(joinSeparator),
},
&functions.Overload{
Operator: "list_join",
Unary: callInListStrOutStr(join),
},
&functions.Overload{
Operator: "list_join_string",
Binary: callInListStrStrOutStr(joinSeparator),
},
),
}
return []cel.ProgramOption{}
}
func charAt(str string, ind int64) (string, error) {
@@ -546,3 +477,7 @@ func joinSeparator(strs []string, separator string) (string, error) {
func join(strs []string) (string, error) {
return strings.Join(strs, ""), nil
}
var (
stringListType = reflect.TypeOf([]string{})
)