package types
import (
"fmt"
"reflect"
"strings"
"github.com/stoewer/go-strcase"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/google/cel-go/common/types/pb"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
anypb "google.golang.org/protobuf/types/known/anypb"
structpb "google.golang.org/protobuf/types/known/structpb"
)
func NewDynamicMap(adapter Adapter, value any) traits.Mapper {
refValue := reflect.ValueOf(value)
return &baseMap{
Adapter: adapter,
mapAccessor: newReflectMapAccessor(adapter, refValue),
value: value,
size: refValue.Len(),
}
}
func NewJSONStruct(adapter Adapter, value *structpb.Struct) traits.Mapper {
fields := value.GetFields()
return &baseMap{
Adapter: adapter,
mapAccessor: newJSONStructAccessor(adapter, fields),
value: value,
size: len(fields),
}
}
func NewRefValMap(adapter Adapter, value map[ref.Val]ref.Val) traits.Mapper {
return &baseMap{
Adapter: adapter,
mapAccessor: newRefValMapAccessor(value),
value: value,
size: len(value),
}
}
func NewStringInterfaceMap(adapter Adapter, value map[string]any) traits.Mapper {
return &baseMap{
Adapter: adapter,
mapAccessor: newStringIfaceMapAccessor(adapter, value),
value: value,
size: len(value),
}
}
func NewStringStringMap(adapter Adapter, value map[string]string) traits.Mapper {
return &baseMap{
Adapter: adapter,
mapAccessor: newStringMapAccessor(value),
value: value,
size: len(value),
}
}
func NewProtoMap(adapter Adapter, value *pb.Map) traits.Mapper {
return &protoMap{
Adapter: adapter,
value: value,
}
}
type mapAccessor interface {
Find(ref.Val) (ref.Val, bool)
Iterator() traits.Iterator
}
type baseMap struct {
Adapter
mapAccessor
value any
size int
}
func (m *baseMap) Contains(index ref.Val) ref.Val {
_, found := m.Find(index)
return Bool(found)
}
func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) {
if reflect.TypeOf(m.value).AssignableTo(typeDesc) {
return m.value, nil
}
if reflect.TypeOf(m).AssignableTo(typeDesc) {
return m, nil
}
switch typeDesc {
case anyValueType:
json, err := m.ConvertToNative(jsonStructType)
if err != nil {
return nil, err
}
return anypb.New(json.(proto.Message))
case jsonValueType, jsonStructType:
jsonEntries, err :=
m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{}))
if err != nil {
return nil, err
}
jsonMap := &structpb.Struct{Fields: jsonEntries.(map[string]*structpb.Value)}
if typeDesc == jsonStructType {
return jsonMap, nil
}
return structpb.NewStructValue(jsonMap), nil
}
isPtr := false
if typeDesc.Kind() == reflect.Ptr {
tk := typeDesc
typeDesc = typeDesc.Elem()
if typeDesc.Kind() == reflect.Ptr {
return nil, fmt.Errorf("unsupported type conversion to '%v'", tk)
}
isPtr = true
}
switch typeDesc.Kind() {
case reflect.Map:
otherKey := typeDesc.Key()
otherElem := typeDesc.Elem()
nativeMap := reflect.MakeMapWithSize(typeDesc, m.size)
it := m.Iterator()
for it.HasNext() == True {
key := it.Next()
refKeyValue, err := key.ConvertToNative(otherKey)
if err != nil {
return nil, err
}
refElemValue, err := m.Get(key).ConvertToNative(otherElem)
if err != nil {
return nil, err
}
nativeMap.SetMapIndex(reflect.ValueOf(refKeyValue), reflect.ValueOf(refElemValue))
}
return nativeMap.Interface(), nil
case reflect.Struct:
nativeStructPtr := reflect.New(typeDesc)
nativeStruct := nativeStructPtr.Elem()
it := m.Iterator()
for it.HasNext() == True {
key := it.Next()
fieldName := key.ConvertToType(StringType)
if IsError(fieldName) {
return nil, fieldName.(*Err)
}
name := string(fieldName.(String))
name = strcase.UpperCamelCase(name)
fieldRef := nativeStruct.FieldByName(name)
if !fieldRef.IsValid() {
return nil, fmt.Errorf("type conversion error, no such field '%s' in type '%v'", name, typeDesc)
}
fieldValue, err := m.Get(key).ConvertToNative(fieldRef.Type())
if err != nil {
return nil, err
}
fieldRef.Set(reflect.ValueOf(fieldValue))
}
if isPtr {
return nativeStructPtr.Interface(), nil
}
return nativeStruct.Interface(), nil
}
return nil, fmt.Errorf("type conversion error from map to '%v'", typeDesc)
}
func (m *baseMap) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case MapType:
return m
case TypeType:
return MapType
}
return NewErr("type conversion error from '%s' to '%s'", MapType, typeVal)
}
func (m *baseMap) Equal(other ref.Val) ref.Val {
otherMap, ok := other.(traits.Mapper)
if !ok {
return False
}
if m.Size() != otherMap.Size() {
return False
}
it := m.Iterator()
for it.HasNext() == True {
key := it.Next()
thisVal, _ := m.Find(key)
otherVal, found := otherMap.Find(key)
if !found {
return False
}
valEq := Equal(thisVal, otherVal)
if valEq == False {
return False
}
}
return True
}
func (m *baseMap) Get(key ref.Val) ref.Val {
v, found := m.Find(key)
if !found {
return ValOrErr(v, "no such key: %v", key)
}
return v
}
func (m *baseMap) IsZeroValue() bool {
return m.size == 0
}
func (m *baseMap) Size() ref.Val {
return Int(m.size)
}
func (m *baseMap) String() string {
var sb strings.Builder
sb.WriteString("{")
it := m.Iterator()
i := 0
for it.HasNext() == True {
k := it.Next()
v, _ := m.Find(k)
sb.WriteString(fmt.Sprintf("%v: %v", k, v))
if i != m.size-1 {
sb.WriteString(", ")
}
i++
}
sb.WriteString("}")
return sb.String()
}
func (m *baseMap) Type() ref.Type {
return MapType
}
func (m *baseMap) Value() any {
return m.value
}
func newJSONStructAccessor(adapter Adapter, st map[string]*structpb.Value) mapAccessor {
return &jsonStructAccessor{
Adapter: adapter,
st: st,
}
}
type jsonStructAccessor struct {
Adapter
st map[string]*structpb.Value
}
func (a *jsonStructAccessor) Find(key ref.Val) (ref.Val, bool) {
strKey, ok := key.(String)
if !ok {
return nil, false
}
keyVal, found := a.st[string(strKey)]
if !found {
return nil, false
}
return a.NativeToValue(keyVal), true
}
func (a *jsonStructAccessor) Iterator() traits.Iterator {
mapKeys := make([]string, len(a.st))
i := 0
for k := range a.st {
mapKeys[i] = k
i++
}
return &stringKeyIterator{
mapKeys: mapKeys,
len: len(mapKeys),
}
}
func newReflectMapAccessor(adapter Adapter, value reflect.Value) mapAccessor {
keyType := value.Type().Key()
return &reflectMapAccessor{
Adapter: adapter,
refValue: value,
keyType: keyType,
}
}
type reflectMapAccessor struct {
Adapter
refValue reflect.Value
keyType reflect.Type
}
func (m *reflectMapAccessor) Find(key ref.Val) (ref.Val, bool) {
if m.refValue.Len() == 0 {
return nil, false
}
if keyVal, found := m.findInternal(key); found {
return keyVal, true
}
switch k := key.(type) {
case Double:
if ik, ok := doubleToInt64Lossless(float64(k)); ok {
if keyVal, found := m.findInternal(Int(ik)); found {
return keyVal, true
}
}
if uk, ok := doubleToUint64Lossless(float64(k)); ok {
return m.findInternal(Uint(uk))
}
case Int:
if uk, ok := int64ToUint64Lossless(int64(k)); ok {
return m.findInternal(Uint(uk))
}
case Uint:
if ik, ok := uint64ToInt64Lossless(uint64(k)); ok {
return m.findInternal(Int(ik))
}
}
return nil, false
}
func (m *reflectMapAccessor) findInternal(key ref.Val) (ref.Val, bool) {
k, err := key.ConvertToNative(m.keyType)
if err != nil {
return nil, false
}
refKey := reflect.ValueOf(k)
val := m.refValue.MapIndex(refKey)
if val.IsValid() {
return m.NativeToValue(val.Interface()), true
}
return nil, false
}
func (m *reflectMapAccessor) Iterator() traits.Iterator {
return &mapIterator{
Adapter: m.Adapter,
mapKeys: m.refValue.MapRange(),
len: m.refValue.Len(),
}
}
func newRefValMapAccessor(mapVal map[ref.Val]ref.Val) mapAccessor {
return &refValMapAccessor{mapVal: mapVal}
}
type refValMapAccessor struct {
mapVal map[ref.Val]ref.Val
}
func (a *refValMapAccessor) Find(key ref.Val) (ref.Val, bool) {
if len(a.mapVal) == 0 {
return nil, false
}
if keyVal, found := a.mapVal[key]; found {
return keyVal, true
}
switch k := key.(type) {
case Double:
if ik, ok := doubleToInt64Lossless(float64(k)); ok {
if keyVal, found := a.mapVal[Int(ik)]; found {
return keyVal, found
}
}
if uk, ok := doubleToUint64Lossless(float64(k)); ok {
keyVal, found := a.mapVal[Uint(uk)]
return keyVal, found
}
case Int:
if uk, ok := int64ToUint64Lossless(int64(k)); ok {
keyVal, found := a.mapVal[Uint(uk)]
return keyVal, found
}
case Uint:
if ik, ok := uint64ToInt64Lossless(uint64(k)); ok {
keyVal, found := a.mapVal[Int(ik)]
return keyVal, found
}
}
return nil, false
}
func (a *refValMapAccessor) Iterator() traits.Iterator {
return &mapIterator{
Adapter: DefaultTypeAdapter,
mapKeys: reflect.ValueOf(a.mapVal).MapRange(),
len: len(a.mapVal),
}
}
func newStringMapAccessor(strMap map[string]string) mapAccessor {
return &stringMapAccessor{mapVal: strMap}
}
type stringMapAccessor struct {
mapVal map[string]string
}
func (a *stringMapAccessor) Find(key ref.Val) (ref.Val, bool) {
strKey, ok := key.(String)
if !ok {
return nil, false
}
keyVal, found := a.mapVal[string(strKey)]
if !found {
return nil, false
}
return String(keyVal), true
}
func (a *stringMapAccessor) Iterator() traits.Iterator {
mapKeys := make([]string, len(a.mapVal))
i := 0
for k := range a.mapVal {
mapKeys[i] = k
i++
}
return &stringKeyIterator{
mapKeys: mapKeys,
len: len(mapKeys),
}
}
func newStringIfaceMapAccessor(adapter Adapter, mapVal map[string]any) mapAccessor {
return &stringIfaceMapAccessor{
Adapter: adapter,
mapVal: mapVal,
}
}
type stringIfaceMapAccessor struct {
Adapter
mapVal map[string]any
}
func (a *stringIfaceMapAccessor) Find(key ref.Val) (ref.Val, bool) {
strKey, ok := key.(String)
if !ok {
return nil, false
}
keyVal, found := a.mapVal[string(strKey)]
if !found {
return nil, false
}
return a.NativeToValue(keyVal), true
}
func (a *stringIfaceMapAccessor) Iterator() traits.Iterator {
mapKeys := make([]string, len(a.mapVal))
i := 0
for k := range a.mapVal {
mapKeys[i] = k
i++
}
return &stringKeyIterator{
mapKeys: mapKeys,
len: len(mapKeys),
}
}
type protoMap struct {
Adapter
value *pb.Map
}
func (m *protoMap) Contains(key ref.Val) ref.Val {
_, found := m.Find(key)
return Bool(found)
}
func (m *protoMap) ConvertToNative(typeDesc reflect.Type) (any, error) {
switch typeDesc {
case anyValueType:
json, err := m.ConvertToNative(jsonStructType)
if err != nil {
return nil, err
}
return anypb.New(json.(proto.Message))
case jsonValueType, jsonStructType:
jsonEntries, err :=
m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{}))
if err != nil {
return nil, err
}
jsonMap := &structpb.Struct{
Fields: jsonEntries.(map[string]*structpb.Value)}
if typeDesc == jsonStructType {
return jsonMap, nil
}
return structpb.NewStructValue(jsonMap), nil
}
switch typeDesc.Kind() {
case reflect.Struct, reflect.Ptr:
if reflect.TypeOf(m.value).AssignableTo(typeDesc) {
return m.value, nil
}
if reflect.TypeOf(m).AssignableTo(typeDesc) {
return m, nil
}
}
if typeDesc.Kind() != reflect.Map {
return nil, fmt.Errorf("unsupported type conversion: %v to map", typeDesc)
}
keyType := m.value.KeyType.ReflectType()
valType := m.value.ValueType.ReflectType()
otherKeyType := typeDesc.Key()
otherValType := typeDesc.Elem()
mapVal := reflect.MakeMapWithSize(typeDesc, m.value.Len())
var err error
m.value.Range(func(key protoreflect.MapKey, val protoreflect.Value) bool {
ntvKey := key.Interface()
ntvVal := val.Interface()
switch pv := ntvVal.(type) {
case protoreflect.Message:
ntvVal = pv.Interface()
}
if keyType == otherKeyType && valType == otherValType {
mapVal.SetMapIndex(reflect.ValueOf(ntvKey), reflect.ValueOf(ntvVal))
return true
}
celKey := m.NativeToValue(ntvKey)
celVal := m.NativeToValue(ntvVal)
ntvKey, err = celKey.ConvertToNative(otherKeyType)
if err != nil {
return false
}
ntvVal, err = celVal.ConvertToNative(otherValType)
if err != nil {
return false
}
mapVal.SetMapIndex(reflect.ValueOf(ntvKey), reflect.ValueOf(ntvVal))
return true
})
if err != nil {
return nil, err
}
return mapVal.Interface(), nil
}
func (m *protoMap) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case MapType:
return m
case TypeType:
return MapType
}
return NewErr("type conversion error from '%s' to '%s'", MapType, typeVal)
}
func (m *protoMap) Equal(other ref.Val) ref.Val {
otherMap, ok := other.(traits.Mapper)
if !ok {
return False
}
if m.value.Map.Len() != int(otherMap.Size().(Int)) {
return False
}
var retVal ref.Val = True
m.value.Range(func(key protoreflect.MapKey, val protoreflect.Value) bool {
keyVal := m.NativeToValue(key.Interface())
valVal := m.NativeToValue(val)
otherVal, found := otherMap.Find(keyVal)
if !found {
retVal = False
return false
}
valEq := Equal(valVal, otherVal)
if valEq != True {
retVal = valEq
return false
}
return true
})
return retVal
}
func (m *protoMap) Find(key ref.Val) (ref.Val, bool) {
if keyVal, found := m.findInternal(key); found {
return keyVal, true
}
switch k := key.(type) {
case Double:
if ik, ok := doubleToInt64Lossless(float64(k)); ok {
if keyVal, found := m.findInternal(Int(ik)); found {
return keyVal, true
}
}
if uk, ok := doubleToUint64Lossless(float64(k)); ok {
return m.findInternal(Uint(uk))
}
case Int:
if uk, ok := int64ToUint64Lossless(int64(k)); ok {
return m.findInternal(Uint(uk))
}
case Uint:
if ik, ok := uint64ToInt64Lossless(uint64(k)); ok {
return m.findInternal(Int(ik))
}
}
return nil, false
}
func (m *protoMap) findInternal(key ref.Val) (ref.Val, bool) {
ntvKey, err := key.ConvertToNative(m.value.KeyType.ReflectType())
if err != nil {
return nil, false
}
val := m.value.Get(protoreflect.ValueOf(ntvKey).MapKey())
if !val.IsValid() {
return nil, false
}
switch v := val.Interface().(type) {
case protoreflect.List, protoreflect.Map:
return nil, false
default:
return m.NativeToValue(v), true
}
}
func (m *protoMap) Get(key ref.Val) ref.Val {
v, found := m.Find(key)
if !found {
return ValOrErr(v, "no such key: %v", key)
}
return v
}
func (m *protoMap) IsZeroValue() bool {
return m.value.Len() == 0
}
func (m *protoMap) Iterator() traits.Iterator {
mapKeys := make([]protoreflect.MapKey, 0, m.value.Len())
m.value.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
mapKeys = append(mapKeys, k)
return true
})
return &protoMapIterator{
Adapter: m.Adapter,
mapKeys: mapKeys,
len: m.value.Len(),
}
}
func (m *protoMap) Size() ref.Val {
return Int(m.value.Len())
}
func (m *protoMap) Type() ref.Type {
return MapType
}
func (m *protoMap) Value() any {
return m.value
}
type mapIterator struct {
*baseIterator
Adapter
mapKeys *reflect.MapIter
cursor int
len int
}
func (it *mapIterator) HasNext() ref.Val {
return Bool(it.cursor < it.len)
}
func (it *mapIterator) Next() ref.Val {
if it.HasNext() == True && it.mapKeys.Next() {
it.cursor++
refKey := it.mapKeys.Key()
return it.NativeToValue(refKey.Interface())
}
return nil
}
type protoMapIterator struct {
*baseIterator
Adapter
mapKeys []protoreflect.MapKey
cursor int
len int
}
func (it *protoMapIterator) HasNext() ref.Val {
return Bool(it.cursor < it.len)
}
func (it *protoMapIterator) Next() ref.Val {
if it.HasNext() == True {
index := it.cursor
it.cursor++
refKey := it.mapKeys[index]
return it.NativeToValue(refKey.Interface())
}
return nil
}
type stringKeyIterator struct {
*baseIterator
mapKeys []string
cursor int
len int
}
func (it *stringKeyIterator) HasNext() ref.Val {
return Bool(it.cursor < it.len)
}
func (it *stringKeyIterator) Next() ref.Val {
if it.HasNext() == True {
index := it.cursor
it.cursor++
return String(it.mapKeys[index])
}
return nil
}