Skip to content

Commit

Permalink
Simple type conversion fix (#267)
Browse files Browse the repository at this point in the history
Additional support for protobuf.Any, JSON, and Wrapper type assignments.
  • Loading branch information
TristonianJones authored Oct 8, 2019
1 parent 86d0bb1 commit 7963958
Show file tree
Hide file tree
Showing 31 changed files with 819 additions and 118 deletions.
1 change: 1 addition & 0 deletions common/types/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ go_library(
"//common/types/ref:go_default_library",
"//common/types/pb:go_default_library",
"//common/types/traits:go_default_library",
"@com_github_golang_protobuf//jsonpb:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//ptypes:go_default_library",
"@io_bazel_rules_go//proto/wkt:any_go_proto",
Expand Down
31 changes: 23 additions & 8 deletions common/types/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import (
"reflect"
"strconv"

structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/golang/protobuf/ptypes"

"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

// Bool type that implements ref.Val and supports comparison and negation.
Expand All @@ -32,6 +36,9 @@ var (
BoolType = NewTypeValue("bool",
traits.ComparerType,
traits.NegatorType)

// boolWrapperType golang reflected type for protobuf bool wrapper type.
boolWrapperType = reflect.TypeOf(&wrapperspb.BoolValue{})
)

// Boolean constants
Expand Down Expand Up @@ -61,14 +68,22 @@ func (b Bool) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
case reflect.Bool:
return bool(b), nil
case reflect.Ptr:
if typeDesc == jsonValueType {
switch typeDesc {
case anyValueType:
// Primitives must be wrapped before being set on an Any field.
return ptypes.MarshalAny(&wrapperspb.BoolValue{Value: bool(b)})
case boolWrapperType:
// Convert the bool to a protobuf.BoolValue.
return &wrapperspb.BoolValue{Value: bool(b)}, nil
case jsonValueType:
return &structpb.Value{
Kind: &structpb.Value_BoolValue{
BoolValue: b.Value().(bool)}}, nil
}
if typeDesc.Elem().Kind() == reflect.Bool {
p := bool(b)
return &p, nil
Kind: &structpb.Value_BoolValue{BoolValue: bool(b)},
}, nil
default:
if typeDesc.Elem().Kind() == reflect.Bool {
p := bool(b)
return &p, nil
}
}
case reflect.Interface:
if reflect.TypeOf(b).Implements(typeDesc) {
Expand Down
27 changes: 27 additions & 0 deletions common/types/bool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import (
"testing"

"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

func TestBool_Compare(t *testing.T) {
Expand All @@ -40,6 +43,20 @@ func TestBool_Compare(t *testing.T) {
}
}

func TestBool_ConvertToNative_Any(t *testing.T) {
val, err := True.ConvertToNative(anyValueType)
if err != nil {
t.Error(err)
}
pbVal, err := ptypes.MarshalAny(&wrapperspb.BoolValue{Value: true})
if err != nil {
t.Error(err)
}
if !proto.Equal(val.(proto.Message), pbVal) {
t.Error("Error during conversion to protobuf.Any", val)
}
}

func TestBool_ConvertToNative_Bool(t *testing.T) {
refType := reflect.TypeOf(true)
val, err := True.ConvertToNative(refType)
Expand Down Expand Up @@ -79,6 +96,16 @@ func TestBool_ConvertToNative_Ptr(t *testing.T) {
}
}

func TestBool_ConvertToNative_Wrapper(t *testing.T) {
val, err := True.ConvertToNative(boolWrapperType)
pbVal := &wrapperspb.BoolValue{Value: true}
if err != nil {
t.Error(err)
} else if !proto.Equal(val.(proto.Message), pbVal) {
t.Error("Error during conversion to wrapper value type", val)
}
}

func TestBool_ConvertToType(t *testing.T) {
if !True.ConvertToType(StringType).Equal(String("true")).(Bool) {
t.Error("Boolean could not be converted to string")
Expand Down
26 changes: 26 additions & 0 deletions common/types/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ package types

import (
"bytes"
"encoding/base64"
"fmt"
"reflect"

"github.com/golang/protobuf/ptypes"

"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

// Bytes type that implements ref.Val and supports add, compare, and size
Expand All @@ -33,6 +39,9 @@ var (
traits.AdderType,
traits.ComparerType,
traits.SizerType)

// byteWrapperType golang reflected type for protobuf bytes wrapper type.
byteWrapperType = reflect.TypeOf(&wrapperspb.BytesValue{})
)

// Add implements traits.Adder interface method by concatenating byte sequences.
Expand Down Expand Up @@ -60,6 +69,23 @@ func (b Bytes) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
if typeDesc.Elem().Kind() == reflect.Uint8 {
return b.Value(), nil
}
case reflect.Ptr:
switch typeDesc {
case anyValueType:
// Primitives must be wrapped before being set on an Any field.
return ptypes.MarshalAny(&wrapperspb.BytesValue{Value: []byte(b)})
case byteWrapperType:
// Convert the bytes to a protobuf.BytesValue.
return &wrapperspb.BytesValue{Value: []byte(b)}, nil
case jsonValueType:
// CEL follows the proto3 to JSON conversion by encoding bytes to a string via base64.
// The encoding below matches the golang 'encoding/json' behavior during marshaling,
// which uses base64.StdEncoding.
str := base64.StdEncoding.EncodeToString([]byte(b))
return &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: str},
}, nil
}
case reflect.Interface:
if reflect.TypeOf(b).Implements(typeDesc) {
return b, nil
Expand Down
42 changes: 42 additions & 0 deletions common/types/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import (
"bytes"
"reflect"
"testing"

"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

func TestBytes_Add(t *testing.T) {
Expand All @@ -44,6 +50,20 @@ func TestBytes_Compare(t *testing.T) {
}
}

func TestBytes_ConvertToNative_Any(t *testing.T) {
val, err := Bytes("123").ConvertToNative(anyValueType)
if err != nil {
t.Error(err)
}
want, err := ptypes.MarshalAny(&wrapperspb.BytesValue{Value: []byte("123")})
if err != nil {
t.Error(err)
}
if !proto.Equal(val.(proto.Message), want) {
t.Errorf("Got %v, wanted %v", val, want)
}
}

func TestBytes_ConvertToNative_ByteSlice(t *testing.T) {
val, err := Bytes("123").ConvertToNative(reflect.TypeOf([]byte{}))
if err != nil || !bytes.Equal(val.([]byte), []byte{49, 50, 51}) {
Expand All @@ -58,6 +78,28 @@ func TestBytes_ConvertToNative_Error(t *testing.T) {
}
}

func TestBytes_ConvertToNative_Json(t *testing.T) {
val, err := Bytes("123").ConvertToNative(jsonValueType)
if err != nil {
t.Error(err)
}
want := &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "MTIz"}}
if !proto.Equal(val.(proto.Message), want) {
t.Errorf("Got %v, wanted %v", val, want)
}
}

func TestBytes_ConvertToNative_Wrapper(t *testing.T) {
val, err := Bytes("123").ConvertToNative(byteWrapperType)
if err != nil {
t.Error(err)
}
want := &wrapperspb.BytesValue{Value: []byte("123")}
if !proto.Equal(val.(proto.Message), want) {
t.Errorf("Got %v, wanted %v", val, want)
}
}

func TestBytes_ConvertToType(t *testing.T) {
if !Bytes("hello world").ConvertToType(BytesType).Equal(Bytes("hello world")).(Bool) {
t.Error("Unsupported type conversion to bytes")
Expand Down
32 changes: 28 additions & 4 deletions common/types/double.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import (
"fmt"
"reflect"

structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"

"github.com/golang/protobuf/ptypes"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

// Double type that implements ref.Val, comparison, and mathematical
Expand All @@ -36,6 +40,12 @@ var (
traits.MultiplierType,
traits.NegatorType,
traits.SubtractorType)

// doubleWrapperType reflected type for protobuf double wrapper type.
doubleWrapperType = reflect.TypeOf(&wrapperspb.DoubleValue{})

// floatWrapperType reflected type for protobuf float wrapper type.
floatWrapperType = reflect.TypeOf(&wrapperspb.FloatValue{})
)

// Add implements traits.Adder.Add.
Expand Down Expand Up @@ -70,10 +80,24 @@ func (d Double) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
case reflect.Float64:
return float64(d), nil
case reflect.Ptr:
if typeDesc == jsonValueType {
switch typeDesc {
case anyValueType:
// Primitives must be wrapped before being set on an Any field.
return ptypes.MarshalAny(&wrapperspb.DoubleValue{Value: float64(d)})
case doubleWrapperType:
// Convert to a protobuf.DoubleValue
return &wrapperspb.DoubleValue{Value: float64(d)}, nil
case floatWrapperType:
// Convert to a protobuf.FloatValue (with truncation).
return &wrapperspb.FloatValue{Value: float32(d)}, nil
case jsonValueType:
// Note, there are special cases for proto3 to json conversion that
// expect the floating point value to be converted to a NaN,
// Infinity, or -Infinity string values, but the jsonpb string
// marshaling of the protobuf.Value will handle this conversion.
return &structpb.Value{
Kind: &structpb.Value_NumberValue{
NumberValue: float64(d)}}, nil
Kind: &structpb.Value_NumberValue{NumberValue: float64(d)},
}, nil
}
switch typeDesc.Elem().Kind() {
case reflect.Float32:
Expand Down
63 changes: 63 additions & 0 deletions common/types/double_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
package types

import (
"math"
"reflect"
"testing"

"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"

structpb "github.com/golang/protobuf/ptypes/struct"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
)

func TestDouble_Add(t *testing.T) {
Expand Down Expand Up @@ -48,6 +52,20 @@ func TestDouble_Compare(t *testing.T) {
}
}

func TestDouble_ConvertToNative_Any(t *testing.T) {
val, err := Double(math.MaxFloat64).ConvertToNative(anyValueType)
if err != nil {
t.Error(err)
}
want, err := ptypes.MarshalAny(&wrapperspb.DoubleValue{Value: 1.7976931348623157e+308})
if err != nil {
t.Error(err)
}
if !proto.Equal(val.(proto.Message), want) {
t.Errorf("Got '%v', wanted %v", val, want)
}
}

func TestDouble_ConvertToNative_Error(t *testing.T) {
val, err := Double(-10000).ConvertToNative(reflect.TypeOf(""))
if err == nil {
Expand Down Expand Up @@ -81,6 +99,31 @@ func TestDouble_ConvertToNative_Json(t *testing.T) {
} else if !proto.Equal(val.(proto.Message), pbVal) {
t.Errorf("Got '%v', expected -1.4", val)
}

val, err = Double(math.NaN()).ConvertToNative(jsonValueType)
if err != nil {
t.Error(err)
} else {
v := val.(*structpb.Value)
if !math.IsNaN(v.GetNumberValue()) {
t.Errorf("Got '%v', expected NaN", val)
}
}

val, err = Double(math.Inf(-1)).ConvertToNative(jsonValueType)
pbVal = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: math.Inf(-1)}}
if err != nil {
t.Error(err)
} else if !proto.Equal(val.(proto.Message), pbVal) {
t.Errorf("Got '%v', expected -Infinity", val)
}
val, err = Double(math.Inf(0)).ConvertToNative(jsonValueType)
pbVal = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: math.Inf(0)}}
if err != nil {
t.Error(err)
} else if !proto.Equal(val.(proto.Message), pbVal) {
t.Errorf("Got '%v', expected Infinity", val)
}
}

func TestDouble_ConvertToNative_Ptr_Float32(t *testing.T) {
Expand All @@ -103,6 +146,26 @@ func TestDouble_ConvertToNative_Ptr_Float64(t *testing.T) {
}
}

func TestDouble_ConvertToNative_Wrapper(t *testing.T) {
val, err := Double(3.1415).ConvertToNative(floatWrapperType)
if err != nil {
t.Error(err)
}
want := &wrapperspb.FloatValue{Value: 3.1415}
if !proto.Equal(val.(proto.Message), want) {
t.Errorf("Got '%v', wanted %v", val, want)
}

val, err = Double(math.MaxFloat64).ConvertToNative(doubleWrapperType)
if err != nil {
t.Error(err)
}
want2 := &wrapperspb.DoubleValue{Value: 1.7976931348623157e+308}
if !proto.Equal(val.(proto.Message), want2) {
t.Errorf("Got '%v', wanted %v", val, want2)
}
}

func TestDouble_ConvertToType(t *testing.T) {
if !Double(-4.5).ConvertToType(IntType).Equal(Int(-4)).(Bool) {
t.Error("Unsuccessful type conversion to int")
Expand Down
Loading

0 comments on commit 7963958

Please sign in to comment.