blob: 740b4e4167c93e74931285e297a4e694c723b7fb [file] [log] [blame]
// Copyright 2018 Google LLC
//
// Licensed 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 types
import (
"fmt"
"reflect"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
anypb "google.golang.org/protobuf/types/known/anypb"
structpb "google.golang.org/protobuf/types/known/structpb"
)
var (
// ListType singleton.
ListType = NewTypeValue("list",
traits.AdderType,
traits.ContainerType,
traits.IndexerType,
traits.IterableType,
traits.SizerType)
)
// NewDynamicList returns a traits.Lister with heterogenous elements.
// value should be an array of "native" types, i.e. any type that
// NativeToValue() can convert to a ref.Val.
func NewDynamicList(adapter ref.TypeAdapter, value interface{}) traits.Lister {
refValue := reflect.ValueOf(value)
return &baseList{
TypeAdapter: adapter,
value: value,
size: refValue.Len(),
get: func(i int) interface{} {
return refValue.Index(i).Interface()
},
}
}
// NewStringList returns a traits.Lister containing only strings.
func NewStringList(adapter ref.TypeAdapter, elems []string) traits.Lister {
return &baseList{
TypeAdapter: adapter,
value: elems,
size: len(elems),
get: func(i int) interface{} { return elems[i] },
}
}
// NewRefValList returns a traits.Lister with ref.Val elements.
//
// This type specialization is used with list literals within CEL expressions.
func NewRefValList(adapter ref.TypeAdapter, elems []ref.Val) traits.Lister {
return &baseList{
TypeAdapter: adapter,
value: elems,
size: len(elems),
get: func(i int) interface{} { return elems[i] },
}
}
// NewProtoList returns a traits.Lister based on a pb.List instance.
func NewProtoList(adapter ref.TypeAdapter, list protoreflect.List) traits.Lister {
return &baseList{
TypeAdapter: adapter,
value: list,
size: list.Len(),
get: func(i int) interface{} { return list.Get(i).Interface() },
}
}
// NewJSONList returns a traits.Lister based on structpb.ListValue instance.
func NewJSONList(adapter ref.TypeAdapter, l *structpb.ListValue) traits.Lister {
vals := l.GetValues()
return &baseList{
TypeAdapter: adapter,
value: l,
size: len(vals),
get: func(i int) interface{} { return vals[i] },
}
}
// baseList points to a list containing elements of any type.
// The `value` is an array of native values, and refValue is its reflection object.
// The `ref.TypeAdapter` enables native type to CEL type conversions.
type baseList struct {
ref.TypeAdapter
value interface{}
// size indicates the number of elements within the list.
// Since objects are immutable the size of a list is static.
size int
// get returns a value at the specified integer index.
// The index is guaranteed to be checked against the list index range.
get func(int) interface{}
}
// Add implements the traits.Adder interface method.
func (l *baseList) Add(other ref.Val) ref.Val {
otherList, ok := other.(traits.Lister)
if !ok {
return MaybeNoSuchOverloadErr(other)
}
if l.Size() == IntZero {
return other
}
if otherList.Size() == IntZero {
return l
}
return &concatList{
TypeAdapter: l.TypeAdapter,
prevList: l,
nextList: otherList}
}
// Contains implements the traits.Container interface method.
func (l *baseList) Contains(elem ref.Val) ref.Val {
if IsUnknownOrError(elem) {
return elem
}
var err ref.Val
for i := 0; i < l.size; i++ {
val := l.NativeToValue(l.get(i))
cmp := elem.Equal(val)
b, ok := cmp.(Bool)
// When there is an error on the contain check, this is not necessarily terminal.
// The contains call could find the element and return True, just as though the user
// had written a per-element comparison in an exists() macro or logical ||, e.g.
// list.exists(e, e == elem)
if !ok && err == nil {
err = ValOrErr(cmp, "no such overload")
}
if b == True {
return True
}
}
if err != nil {
return err
}
return False
}
// ConvertToNative implements the ref.Val interface method.
func (l *baseList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
// If the underlying list value is assignable to the reflected type return it.
if reflect.TypeOf(l.value).AssignableTo(typeDesc) {
return l.value, nil
}
// If the list wrapper is assignable to the desired type return it.
if reflect.TypeOf(l).AssignableTo(typeDesc) {
return l, nil
}
// Attempt to convert the list to a set of well known protobuf types.
switch typeDesc {
case anyValueType:
json, err := l.ConvertToNative(jsonListValueType)
if err != nil {
return nil, err
}
return anypb.New(json.(proto.Message))
case jsonValueType, jsonListValueType:
jsonValues, err :=
l.ConvertToNative(reflect.TypeOf([]*structpb.Value{}))
if err != nil {
return nil, err
}
jsonList := &structpb.ListValue{Values: jsonValues.([]*structpb.Value)}
if typeDesc == jsonListValueType {
return jsonList, nil
}
return structpb.NewListValue(jsonList), nil
}
// Non-list conversion.
if typeDesc.Kind() != reflect.Slice && typeDesc.Kind() != reflect.Array {
return nil, fmt.Errorf("type conversion error from list to '%v'", typeDesc)
}
// List conversion.
// Allow the element ConvertToNative() function to determine whether conversion is possible.
otherElemType := typeDesc.Elem()
elemCount := l.size
nativeList := reflect.MakeSlice(typeDesc, elemCount, elemCount)
for i := 0; i < elemCount; i++ {
elem := l.NativeToValue(l.get(i))
nativeElemVal, err := elem.ConvertToNative(otherElemType)
if err != nil {
return nil, err
}
nativeList.Index(i).Set(reflect.ValueOf(nativeElemVal))
}
return nativeList.Interface(), nil
}
// ConvertToType implements the ref.Val interface method.
func (l *baseList) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case ListType:
return l
case TypeType:
return ListType
}
return NewErr("type conversion error from '%s' to '%s'", ListType, typeVal)
}
// Equal implements the ref.Val interface method.
func (l *baseList) Equal(other ref.Val) ref.Val {
otherList, ok := other.(traits.Lister)
if !ok {
return MaybeNoSuchOverloadErr(other)
}
if l.Size() != otherList.Size() {
return False
}
for i := IntZero; i < l.Size().(Int); i++ {
thisElem := l.Get(i)
otherElem := otherList.Get(i)
elemEq := thisElem.Equal(otherElem)
if elemEq != True {
return elemEq
}
}
return True
}
// Get implements the traits.Indexer interface method.
func (l *baseList) Get(index ref.Val) ref.Val {
i, ok := index.(Int)
if !ok {
return ValOrErr(index, "unsupported index type '%s' in list", index.Type())
}
iv := int(i)
if iv < 0 || iv >= l.size {
return NewErr("index '%d' out of range in list size '%d'", i, l.Size())
}
elem := l.get(iv)
return l.NativeToValue(elem)
}
// Iterator implements the traits.Iterable interface method.
func (l *baseList) Iterator() traits.Iterator {
return newListIterator(l)
}
// Size implements the traits.Sizer interface method.
func (l *baseList) Size() ref.Val {
return Int(l.size)
}
// Type implements the ref.Val interface method.
func (l *baseList) Type() ref.Type {
return ListType
}
// Value implements the ref.Val interface method.
func (l *baseList) Value() interface{} {
return l.value
}
// concatList combines two list implementations together into a view.
// The `ref.TypeAdapter` enables native type to CEL type conversions.
type concatList struct {
ref.TypeAdapter
value interface{}
prevList traits.Lister
nextList traits.Lister
}
// Add implements the traits.Adder interface method.
func (l *concatList) Add(other ref.Val) ref.Val {
otherList, ok := other.(traits.Lister)
if !ok {
return MaybeNoSuchOverloadErr(other)
}
if l.Size() == IntZero {
return other
}
if otherList.Size() == IntZero {
return l
}
return &concatList{
TypeAdapter: l.TypeAdapter,
prevList: l,
nextList: otherList}
}
// Contains implments the traits.Container interface method.
func (l *concatList) Contains(elem ref.Val) ref.Val {
// The concat list relies on the IsErrorOrUnknown checks against the input element to be
// performed by the `prevList` and/or `nextList`.
prev := l.prevList.Contains(elem)
// Short-circuit the return if the elem was found in the prev list.
if prev == True {
return prev
}
// Return if the elem was found in the next list.
next := l.nextList.Contains(elem)
if next == True {
return next
}
// Handle the case where an error or unknown was encountered before checking next.
if IsUnknownOrError(prev) {
return prev
}
// Otherwise, rely on the next value as the representative result.
return next
}
// ConvertToNative implements the ref.Val interface method.
func (l *concatList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
combined := NewDynamicList(l.TypeAdapter, l.Value().([]interface{}))
return combined.ConvertToNative(typeDesc)
}
// ConvertToType implements the ref.Val interface method.
func (l *concatList) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case ListType:
return l
case TypeType:
return ListType
}
return NewErr("type conversion error from '%s' to '%s'", ListType, typeVal)
}
// Equal implements the ref.Val interface method.
func (l *concatList) Equal(other ref.Val) ref.Val {
otherList, ok := other.(traits.Lister)
if !ok {
return MaybeNoSuchOverloadErr(other)
}
if l.Size() != otherList.Size() {
return False
}
for i := IntZero; i < l.Size().(Int); i++ {
thisElem := l.Get(i)
otherElem := otherList.Get(i)
elemEq := thisElem.Equal(otherElem)
if elemEq != True {
return elemEq
}
}
return True
}
// Get implements the traits.Indexer interface method.
func (l *concatList) Get(index ref.Val) ref.Val {
i, ok := index.(Int)
if !ok {
return MaybeNoSuchOverloadErr(index)
}
if i < l.prevList.Size().(Int) {
return l.prevList.Get(i)
}
offset := i - l.prevList.Size().(Int)
return l.nextList.Get(offset)
}
// Iterator implements the traits.Iterable interface method.
func (l *concatList) Iterator() traits.Iterator {
return newListIterator(l)
}
// Size implements the traits.Sizer interface method.
func (l *concatList) Size() ref.Val {
return l.prevList.Size().(Int).Add(l.nextList.Size())
}
// Type implements the ref.Val interface method.
func (l *concatList) Type() ref.Type {
return ListType
}
// Value implements the ref.Val interface method.
func (l *concatList) Value() interface{} {
if l.value == nil {
merged := make([]interface{}, l.Size().(Int))
prevLen := l.prevList.Size().(Int)
for i := Int(0); i < prevLen; i++ {
merged[i] = l.prevList.Get(i).Value()
}
nextLen := l.nextList.Size().(Int)
for j := Int(0); j < nextLen; j++ {
merged[prevLen+j] = l.nextList.Get(j).Value()
}
l.value = merged
}
return l.value
}
func newListIterator(listValue traits.Lister) traits.Iterator {
return &listIterator{
listValue: listValue,
len: listValue.Size().(Int),
}
}
type listIterator struct {
*baseIterator
listValue traits.Lister
cursor Int
len Int
}
// HasNext implements the traits.Iterator interface method.
func (it *listIterator) HasNext() ref.Val {
return Bool(it.cursor < it.len)
}
// Next implements the traits.Iterator interface method.
func (it *listIterator) Next() ref.Val {
if it.HasNext() == True {
index := it.cursor
it.cursor++
return it.listValue.Get(index)
}
return nil
}