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