From 2f7bc124a637ea065e6830846b05bbdee1a8addb Mon Sep 17 00:00:00 2001 From: Todd Neal Date: Fri, 17 Feb 2017 12:15:30 -0600 Subject: [PATCH] enable parsing enums from query parameters - parses enumeration strings and integer values - range checks both the string and integer form Fixes #166 --- runtime/query.go | 53 +++++++++++++++++++++++++++++++++++++++++++ runtime/query_test.go | 26 +++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/runtime/query.go b/runtime/query.go index c4b8148967b..fea54a1d5e2 100644 --- a/runtime/query.go +++ b/runtime/query.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" "reflect" + "strconv" "strings" "time" @@ -98,6 +99,12 @@ func fieldByProtoName(m reflect.Value, name string) reflect.Value { func populateRepeatedField(f reflect.Value, values []string) error { elemType := f.Type().Elem() + + // is the destination field a slice of an enumeration type? + if enumValMap := proto.EnumValueMap(elemType.String()); enumValMap != nil { + return populateFieldEnumRepeated(f, values, enumValMap) + } + conv, ok := convFromType[elemType.Kind()] if !ok { return fmt.Errorf("unsupported field type %s", elemType) @@ -137,6 +144,11 @@ func populateField(f reflect.Value, value string) error { } } + // is the destination field an enumeration type? + if enumValMap := proto.EnumValueMap(f.Type().String()); enumValMap != nil { + return populateFieldEnum(f, value, enumValMap) + } + conv, ok := convFromType[f.Kind()] if !ok { return fmt.Errorf("unsupported field type %T", f) @@ -149,6 +161,47 @@ func populateField(f reflect.Value, value string) error { return nil } +func convertEnum(value string, t reflect.Type, enumValMap map[string]int32) (reflect.Value, error) { + // see if it's an enumeration string + if enumVal, ok := enumValMap[value]; ok { + return reflect.ValueOf(enumVal).Convert(t), nil + } + + // check for an integer that matches an enumeration value + eVal, err := strconv.Atoi(value) + if err != nil { + return reflect.Value{}, fmt.Errorf("%s is not a valid %s", value, t) + } + for _, v := range enumValMap { + if v == int32(eVal) { + return reflect.ValueOf(eVal).Convert(t), nil + } + } + return reflect.Value{}, fmt.Errorf("%s is not a valid %s", value, t) +} + +func populateFieldEnum(f reflect.Value, value string, enumValMap map[string]int32) error { + cval, err := convertEnum(value, f.Type(), enumValMap) + if err != nil { + return err + } + f.Set(cval) + return nil +} + +func populateFieldEnumRepeated(f reflect.Value, values []string, enumValMap map[string]int32) error { + elemType := f.Type().Elem() + f.Set(reflect.MakeSlice(f.Type(), len(values), len(values)).Convert(f.Type())) + for i, v := range values { + result, err := convertEnum(v, elemType, enumValMap) + if err != nil { + return err + } + f.Index(i).Set(result) + } + return nil +} + var ( convFromType = map[reflect.Kind]reflect.Value{ reflect.String: reflect.ValueOf(String), diff --git a/runtime/query_test.go b/runtime/query_test.go index 1fcfef6eef7..ce08976037b 100644 --- a/runtime/query_test.go +++ b/runtime/query_test.go @@ -57,6 +57,17 @@ func TestPopulateParameters(t *testing.T) { TimestampValue: timePb, }, }, + { + values: url.Values{ + "enum_value": {"EnumValue_Z"}, + "repeated_enum": {"EnumValue_X", "2", "0"}, + }, + filter: utilities.NewDoubleArray(nil), + want: &proto3Message{ + EnumValue: EnumValue_Z, + RepeatedEnum: []EnumValue{EnumValue_X, EnumValue_Z, EnumValue_X}, + }, + }, { values: url.Values{ "float_value": {"1.5"}, @@ -343,3 +354,18 @@ const ( EnumValue_Y EnumValue = 1 EnumValue_Z EnumValue = 2 ) + +var EnumValue_name = map[int32]string{ + 0: "EnumValue_X", + 1: "EnumValue_Y", + 2: "EnumValue_Z", +} +var EnumValue_value = map[string]int32{ + "EnumValue_X": 0, + "EnumValue_Y": 1, + "EnumValue_Z": 2, +} + +func init() { + proto.RegisterEnum("runtime_test.EnumValue", EnumValue_name, EnumValue_value) +}