From e290247e60ab25fda074d71a3ee6ff952bbcadfe Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 9 Jun 2021 22:25:21 +0100 Subject: [PATCH] WIP: Adjust Mapper/Bindings for Enums Signed-off-by: Dave Tucker --- example/play_with_ovs/play_with_ovs.go | 5 +- mapper/info.go | 31 +++++++-- mapper/info_test.go | 90 +++++++++++++++++++++----- ovsdb/bindings.go | 23 ++++++- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go index 54c8bb56..c84870e3 100644 --- a/example/play_with_ovs/play_with_ovs.go +++ b/example/play_with_ovs/play_with_ovs.go @@ -48,8 +48,9 @@ func play(ovs *client.OvsdbClient) { func createBridge(ovs *client.OvsdbClient, bridgeName string) { bridge := Bridge{ - UUID: "gopher", - Name: bridgeName, + UUID: "gopher", + Name: bridgeName, + Protocols: []BridgeProtocols{BridgeProtocolsOpenflow14, BridgeProtocolsOpenflow15}, } insertOp, err := ovs.Create(&bridge) if err != nil { diff --git a/mapper/info.go b/mapper/info.go index 2e034e8e..3341dc69 100644 --- a/mapper/info.go +++ b/mapper/info.go @@ -38,12 +38,28 @@ func (mi *MapperInfo) SetField(column string, value interface{}) error { return fmt.Errorf("column %s not found in orm info", column) } fieldValue := reflect.ValueOf(mi.obj).Elem().FieldByName(fieldName) - + v := reflect.ValueOf(value) if !fieldValue.Type().AssignableTo(reflect.TypeOf(value)) { - return fmt.Errorf("column %s: native value %v (%s) is not assignable to field %s (%s)", - column, value, reflect.TypeOf(value), fieldName, fieldValue.Type()) + if v.Type().ConvertibleTo(fieldValue.Type()) { + // handle enum + v = v.Convert(fieldValue.Type()) + } else if fieldValue.Kind() == reflect.Slice { + // handle set of enums + if !v.Type().Elem().ConvertibleTo(fieldValue.Type().Elem()) { + return fmt.Errorf("column %s: element %v (%s) is not convertible to field %s element (%s)", + column, value, reflect.TypeOf(value), fieldName, fieldValue.Type()) + } + nv := reflect.Zero(fieldValue.Type()) + for i := 0; i < v.Len(); i++ { + nv = reflect.Append(nv, v.Index(i).Convert(fieldValue.Type().Elem())) + } + v = nv + } else { + return fmt.Errorf("column %s: native value %v (%s) is not assignable or convertible to field %s (%s)", + column, value, reflect.TypeOf(value), fieldName, fieldValue.Type()) + } } - fieldValue.Set(reflect.ValueOf(value)) + fieldValue.Set(v) return nil } @@ -133,7 +149,12 @@ func NewMapperInfo(table *ovsdb.TableSchema, obj interface{}) (*MapperInfo, erro // Perform schema-based type checking expType := ovsdb.NativeType(column) - if expType != field.Type { + // check for slice of enums + if expType.Kind() == reflect.Slice && expType.Elem().Kind() == reflect.String { + // it's a slice of enums + } else if expType.Kind() == reflect.String && field.Type.Kind() == reflect.String { + // it' an enum + } else if expType != field.Type { return nil, &ErrMapper{ objType: objType.String(), field: field.Name, diff --git a/mapper/info_test.go b/mapper/info_test.go index 29f91111..fde4a140 100644 --- a/mapper/info_test.go +++ b/mapper/info_test.go @@ -10,25 +10,57 @@ import ( ) var sampleTable = []byte(`{ - "columns": { + "columns": { "aString": { - "type": "string" + "type": "string" }, "aInteger": { - "type": "integer" + "type": "integer" }, "aSet": { - "type": { - "key": "string", - "max": "unlimited", - "min": 0 - } + "type": { + "key": "string", + "max": "unlimited", + "min": 0 + } }, "aMap": { - "type": { - "key": "string", - "value": "string" - } + "type": { + "key": "string", + "value": "string" + } + }, + "aEnum": { + "type": { + "key": { + "enum": [ + "set", + [ + "enum1", + "enum2", + "enum3" + ] + ], + "type": "string" + } + } + }, + "aEnumSet": { + "type": { + "key": { + "enum": [ + "set", + [ + "enum1", + "enum2", + "enum3" + ] + ], + "type": "string" + }, + "max": "unlimited", + "min": 0 + } } } }`) @@ -73,11 +105,19 @@ func TestNewMapperInfo(t *testing.T) { } func TestMapperInfoSet(t *testing.T) { + type enum string + const ( + enum1 enum = "one" + enum2 enum = "two" + enum3 enum = "three" + ) type obj struct { - Ostring string `ovs:"aString"` - Oint int `ovs:"aInteger"` - Oset []string `ovs:"aSet"` - Omap map[string]string `ovs:"aMap"` + Ostring string `ovs:"aString"` + Oint int `ovs:"aInteger"` + Oset []string `ovs:"aSet"` + Omap map[string]string `ovs:"aMap"` + Oenum enum `ovs:"aEnum"` + OenumSet []enum `ovs:"aEnumSet"` } type test struct { @@ -127,13 +167,29 @@ func TestMapperInfoSet(t *testing.T) { err: false, }, { - name: "unassignalbe", + name: "unassignable", table: sampleTable, obj: &obj{}, field: []string{"foo"}, column: "aMap", err: true, }, + { + name: "enum", + table: sampleTable, + obj: &obj{}, + field: enum1, + column: "aEnum", + err: false, + }, + { + name: "enumSet", + table: sampleTable, + obj: &obj{}, + field: []enum{enum2, enum3}, + column: "aEnumSet", + err: false, + }, } for _, tt := range tests { t.Run(fmt.Sprintf("SetField_%s", tt.name), func(t *testing.T) { diff --git a/ovsdb/bindings.go b/ovsdb/bindings.go index 1d1bbb6c..401bee3b 100644 --- a/ovsdb/bindings.go +++ b/ovsdb/bindings.go @@ -168,11 +168,28 @@ func OvsToNative(column *ColumnSchema, ovsElem interface{}) (interface{}, error) // NativeToOvs transforms an native type to a ovs type based on the column type information func NativeToOvs(column *ColumnSchema, rawElem interface{}) (interface{}, error) { naType := NativeType(column) - - if t := reflect.TypeOf(rawElem); t != naType { + rawElemType := reflect.TypeOf(rawElem) + if column.Type == TypeEnum && rawElemType != strType && rawElemType.Kind() == reflect.String { + // cast type alias back to string + rawElem = rawElem.(string) + rawElemType = reflect.TypeOf(rawElem) + } + if column.Type == TypeSet && len(column.TypeObj.Key.Enum) > 0 && rawElemType.Elem() != strType && rawElemType.Elem().Kind() == reflect.String { + // convert a set of enums where the type is an alias, to a set of strings + tmp := []string{} + v := reflect.ValueOf(rawElem) + for i := 0; i < v.Len(); i++ { + // convert value to string first, because the String method won't panic + // but will instead return a string of which isn't what we want + vi := v.Index(i).Convert(strType) + tmp = append(tmp, vi.String()) + } + rawElem = tmp + rawElemType = reflect.TypeOf(rawElem) + } + if t := rawElemType; t != naType { return nil, NewErrWrongType("NativeToOvs", naType.String(), rawElem) } - switch column.Type { case TypeInteger, TypeReal, TypeString, TypeBoolean, TypeEnum: return rawElem, nil