1// Copyright 2018 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package types 16 17import ( 18 "fmt" 19 "reflect" 20 21 "github.com/golang/protobuf/proto" 22 "github.com/golang/protobuf/ptypes" 23 structpb "github.com/golang/protobuf/ptypes/struct" 24 25 "github.com/google/cel-go/common/types/ref" 26 "github.com/google/cel-go/common/types/traits" 27) 28 29var ( 30 jsonListValueType = reflect.TypeOf(&structpb.ListValue{}) 31) 32 33type jsonListValue struct { 34 *structpb.ListValue 35 ref.TypeAdapter 36} 37 38// NewJSONList creates a traits.Lister implementation backed by a JSON list that has been encoded 39// in protocol buffer form. 40// 41// The `adapter` argument provides type adaptation capabilities from proto to CEL. 42func NewJSONList(adapter ref.TypeAdapter, l *structpb.ListValue) traits.Lister { 43 return &jsonListValue{TypeAdapter: adapter, ListValue: l} 44} 45 46// Add implements the traits.Adder interface method. 47func (l *jsonListValue) Add(other ref.Val) ref.Val { 48 if other.Type() != ListType { 49 return ValOrErr(other, "no such overload") 50 } 51 switch other.(type) { 52 case *jsonListValue: 53 otherList := other.(*jsonListValue) 54 concatElems := append(l.GetValues(), otherList.GetValues()...) 55 return NewJSONList(l.TypeAdapter, &structpb.ListValue{Values: concatElems}) 56 } 57 return &concatList{ 58 TypeAdapter: l.TypeAdapter, 59 prevList: l, 60 nextList: other.(traits.Lister)} 61} 62 63// Contains implements the traits.Container interface method. 64func (l *jsonListValue) Contains(elem ref.Val) ref.Val { 65 if IsUnknownOrError(elem) { 66 return elem 67 } 68 var err ref.Val 69 for i := Int(0); i < l.Size().(Int); i++ { 70 val := l.Get(i) 71 cmp := elem.Equal(val) 72 b, ok := cmp.(Bool) 73 // When there is an error on the contain check, this is not necessarily terminal. 74 // The contains call could find the element and return True, just as though the user 75 // had written a per-element comparison in an exists() macro or logical ||, e.g. 76 // list.exists(e, e == elem) 77 if !ok && err == nil { 78 err = ValOrErr(cmp, "no such overload") 79 } 80 if b == True { 81 return True 82 } 83 } 84 if err != nil { 85 return err 86 } 87 return False 88} 89 90// ConvertToNative implements the ref.Val interface method. 91func (l *jsonListValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) { 92 switch typeDesc.Kind() { 93 case reflect.Array, reflect.Slice: 94 elemCount := int(l.Size().(Int)) 95 nativeList := reflect.MakeSlice(typeDesc, elemCount, elemCount) 96 for i := 0; i < elemCount; i++ { 97 elem := l.Get(Int(i)) 98 nativeElemVal, err := elem.ConvertToNative(typeDesc.Elem()) 99 if err != nil { 100 return nil, err 101 } 102 nativeList.Index(i).Set(reflect.ValueOf(nativeElemVal)) 103 } 104 return nativeList.Interface(), nil 105 106 case reflect.Ptr: 107 switch typeDesc { 108 case anyValueType: 109 return ptypes.MarshalAny(l.Value().(proto.Message)) 110 case jsonValueType: 111 return &structpb.Value{ 112 Kind: &structpb.Value_ListValue{ListValue: l.ListValue}, 113 }, nil 114 case jsonListValueType: 115 return l.ListValue, nil 116 } 117 118 case reflect.Interface: 119 // If the list is already assignable to the desired type return it. 120 if reflect.TypeOf(l).Implements(typeDesc) { 121 return l, nil 122 } 123 } 124 return nil, fmt.Errorf("no conversion found from list type to native type."+ 125 " list elem: google.protobuf.Value, native type: %v", typeDesc) 126} 127 128// ConvertToType implements the ref.Val interface method. 129func (l *jsonListValue) ConvertToType(typeVal ref.Type) ref.Val { 130 switch typeVal { 131 case ListType: 132 return l 133 case TypeType: 134 return ListType 135 } 136 return NewErr("type conversion error from '%s' to '%s'", ListType, typeVal) 137} 138 139// Equal implements the ref.Val interface method. 140func (l *jsonListValue) Equal(other ref.Val) ref.Val { 141 otherList, ok := other.(traits.Lister) 142 if !ok { 143 return ValOrErr(other, "no such overload") 144 } 145 if l.Size() != otherList.Size() { 146 return False 147 } 148 for i := IntZero; i < l.Size().(Int); i++ { 149 thisElem := l.Get(i) 150 otherElem := otherList.Get(i) 151 elemEq := thisElem.Equal(otherElem) 152 if elemEq != True { 153 return elemEq 154 } 155 } 156 return True 157} 158 159// Get implements the traits.Indexer interface method. 160func (l *jsonListValue) Get(index ref.Val) ref.Val { 161 i, ok := index.(Int) 162 if !ok { 163 return ValOrErr(index, "unsupported index type: '%v", index.Type()) 164 } 165 if i < 0 || i >= l.Size().(Int) { 166 return NewErr("index '%d' out of range in list size '%d'", i, l.Size()) 167 } 168 elem := l.GetValues()[i] 169 return l.NativeToValue(elem) 170} 171 172// Iterator implements the traits.Iterable interface method. 173func (l *jsonListValue) Iterator() traits.Iterator { 174 return &jsonValueListIterator{ 175 baseIterator: &baseIterator{}, 176 TypeAdapter: l.TypeAdapter, 177 elems: l.GetValues(), 178 len: len(l.GetValues())} 179} 180 181// Size implements the traits.Sizer interface method. 182func (l *jsonListValue) Size() ref.Val { 183 return Int(len(l.GetValues())) 184} 185 186// Type implements the ref.Val interface method. 187func (l *jsonListValue) Type() ref.Type { 188 return ListType 189} 190 191// Value implements the ref.Val interface method. 192func (l *jsonListValue) Value() interface{} { 193 return l.ListValue 194} 195 196type jsonValueListIterator struct { 197 *baseIterator 198 ref.TypeAdapter 199 cursor int 200 elems []*structpb.Value 201 len int 202} 203 204// HasNext implements the traits.Iterator interface method. 205func (it *jsonValueListIterator) HasNext() ref.Val { 206 return Bool(it.cursor < it.len) 207} 208 209// Next implements the traits.Iterator interface method. 210func (it *jsonValueListIterator) Next() ref.Val { 211 if it.HasNext() == True { 212 index := it.cursor 213 it.cursor++ 214 return it.NativeToValue(it.elems[index]) 215 } 216 return nil 217} 218