From d195310d351a2347e5c9718f8644718c1d909b78 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 3 Mar 2020 00:27:12 +0100 Subject: [PATCH] Enable anonymous fields. Enable any map type with string key add test for invalid map types refactor funny scanValue refactor funny scanValue --- document/document.go | 30 +++++++++++------ document/document_test.go | 12 ++++++- document/encoding/encoding_test.go | 54 +++++++++++++++++------------- document/scan.go | 19 ++++------- document/value.go | 11 ++++-- 5 files changed, 76 insertions(+), 50 deletions(-) diff --git a/document/document.go b/document/document.go index f2368d33a..5113f0898 100644 --- a/document/document.go +++ b/document/document.go @@ -42,22 +42,29 @@ type Document interface { // NewFromMap creates a document from a map. // Due to the way maps are designed, iteration order is not guaranteed. -func NewFromMap(m map[string]interface{}) Document { - return mapDocument(m) +func NewFromMap(m interface{}) (Document, error) { + M := reflect.ValueOf(m) + if M.Kind() != reflect.Map || M.Type().Key().Kind() != reflect.String { + return nil, &ErrUnsupportedType{m, "parameter must be a map with a string key"} + } + return mapDocument(M), nil } -type mapDocument map[string]interface{} +type mapDocument reflect.Value var _ Document = (*mapDocument)(nil) func (m mapDocument) Iterate(fn func(f string, v Value) error) error { - for mk, mv := range m { - v, err := NewValue(mv) + M := reflect.Value(m) + it := M.MapRange() + + for it.Next() { + v, err := NewValue(it.Value().Interface()) if err != nil { return err } - err = fn(mk, v) + err = fn(it.Key().String(), v) if err != nil { return err } @@ -66,11 +73,12 @@ func (m mapDocument) Iterate(fn func(f string, v Value) error) error { } func (m mapDocument) GetByField(field string) (Value, error) { - v, ok := m[field] - if !ok { + M := reflect.Value(m) + v := M.MapIndex(reflect.ValueOf(field)) + if v == (reflect.Value{}) { return Value{}, ErrFieldNotFound } - return NewValue(v) + return NewValue(v.Interface()) } // NewFromStruct creates a document from a struct using reflection. @@ -97,7 +105,7 @@ func (s structDocument) Iterate(fn func(f string, v Value) error) error { for i := 0; i < l; i++ { sf := tp.Field(i) - if sf.Anonymous || sf.PkgPath != "" { + if sf.PkgPath != "" { continue } @@ -150,7 +158,7 @@ func (s structDocument) GetByField(field string) (Value, error) { } } - if !ok || sf.Anonymous || sf.PkgPath != "" { + if !ok || sf.PkgPath != "" { return Value{}, ErrFieldNotFound } diff --git a/document/document_test.go b/document/document_test.go index 9c99524e8..2e3a81697 100644 --- a/document/document_test.go +++ b/document/document_test.go @@ -196,7 +196,8 @@ func TestNewFromMap(t *testing.T) { "nilField": nil, } - doc := document.NewFromMap(m) + doc, err := document.NewFromMap(m) + require.NoError(t, err) t.Run("Iterate", func(t *testing.T) { counter := make(map[string]int) @@ -234,6 +235,15 @@ func TestNewFromMap(t *testing.T) { _, err = doc.GetByField("bar") require.Equal(t, document.ErrFieldNotFound, err) }) + + t.Run("Invalid types", func(t *testing.T) { + + // test NewFromMap rejects invalid types + _, err = document.NewFromMap(8) + require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map") + _, err = document.NewFromMap(map[int]float64{2: 4.3}) + require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map with a string key type") + }) } func TestNewFromStruct(t *testing.T) { diff --git a/document/encoding/encoding_test.go b/document/encoding/encoding_test.go index e7b6acb99..9eebb3125 100644 --- a/document/encoding/encoding_test.go +++ b/document/encoding/encoding_test.go @@ -38,6 +38,18 @@ func TestDecodeValueFromDocument(t *testing.T) { } func TestEncodeDecode(t *testing.T) { + userMapDoc, err := document.NewFromMap(map[string]interface{}{ + "age": 10, + "name": "john", + }) + require.NoError(t, err) + + addressMapDoc, err := document.NewFromMap(map[string]string{ + "city": "Ajaccio", + "country": "France", + }) + require.NoError(t, err) + tests := []struct { name string d document.Document @@ -52,10 +64,7 @@ func TestEncodeDecode(t *testing.T) { }, { "Map", - document.NewFromMap(map[string]interface{}{ - "age": 10, - "name": "john", - }), + userMapDoc, `{"age": 10, "name": "john"}`, }, { @@ -63,10 +72,7 @@ func TestEncodeDecode(t *testing.T) { document.NewFieldBuffer(). Add("age", document.NewInt64Value(10)). Add("name", document.NewTextValue("john")). - Add("address", document.NewDocumentValue(document.NewFromMap(map[string]interface{}{ - "city": "Ajaccio", - "country": "France", - }))), + Add("address", document.NewDocumentValue(addressMapDoc)), `{"age": 10, "name": "john", "address": {"city": "Ajaccio", "country": "France"}}`, }, } @@ -84,13 +90,16 @@ func TestEncodeDecode(t *testing.T) { } func TestDecodeDocument(t *testing.T) { + mapDoc, err := document.NewFromMap(map[string]string{ + "city": "Ajaccio", + "country": "France", + }) + require.NoError(t, err) + doc := document.NewFieldBuffer(). Add("age", document.NewInt64Value(10)). Add("name", document.NewTextValue("john")). - Add("address", document.NewDocumentValue(document.NewFromMap(map[string]interface{}{ - "city": "Ajaccio", - "country": "France", - }))) + Add("address", document.NewDocumentValue(mapDoc)) data, err := EncodeDocument(doc) require.NoError(t, err) @@ -102,10 +111,7 @@ func TestDecodeDocument(t *testing.T) { v, err = ec.GetByField("address") require.NoError(t, err) var expected, actual bytes.Buffer - err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(document.NewFromMap(map[string]interface{}{ - "city": "Ajaccio", - "country": "France", - })))) + err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc))) require.NoError(t, err) err = document.ToJSON(&actual, document.NewFieldBuffer().Add("address", v)) require.NoError(t, err) @@ -118,10 +124,7 @@ func TestDecodeDocument(t *testing.T) { require.Equal(t, document.NewInt64Value(10), v) case "address": var expected, actual bytes.Buffer - err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(document.NewFromMap(map[string]interface{}{ - "city": "Ajaccio", - "country": "France", - })))) + err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc))) require.NoError(t, err) err = document.ToJSON(&actual, document.NewFieldBuffer().Add(f, v)) require.NoError(t, err) @@ -137,6 +140,12 @@ func TestDecodeDocument(t *testing.T) { } func TestEncodeArray(t *testing.T) { + mapDoc, err := document.NewFromMap(map[string]string{ + "city": "Ajaccio", + "country": "France", + }) + require.NoError(t, err) + tests := []struct { name string a document.Array @@ -147,10 +156,7 @@ func TestEncodeArray(t *testing.T) { document.NewValueBuffer(). Append(document.NewInt64Value(10)). Append(document.NewTextValue("john")). - Append(document.NewDocumentValue(document.NewFromMap(map[string]interface{}{ - "city": "Ajaccio", - "country": "France", - }))). + Append(document.NewDocumentValue(mapDoc)). Append(document.NewArrayValue(document.NewValueBuffer().Append(document.NewInt64Value(11)))), `[10, "john", {"city": "Ajaccio", "country": "France"}, [11]]`, }, diff --git a/document/scan.go b/document/scan.go index 9f2124173..45829784a 100644 --- a/document/scan.go +++ b/document/scan.go @@ -26,7 +26,7 @@ func Scan(d Document, targets ...interface{}) error { ref := reflect.ValueOf(target) if !ref.IsValid() { - return &ErrUnsupportedType{target} + return &ErrUnsupportedType{target, fmt.Sprintf("Parameter %d is not valid", i)} } return scanValue(v, ref) @@ -85,12 +85,7 @@ func structScan(d Document, ref reflect.Value) error { return err } - if f.Type().Kind() == reflect.Ptr { - err = scanValue(v, f) - } else { - err = scanValue(v, f) - } - if err != nil { + if err := scanValue(v, f); err != nil { return err } } @@ -182,7 +177,7 @@ func sliceScan(a Array, ref reflect.Value) error { func MapScan(d Document, t interface{}) error { ref := reflect.ValueOf(t) if !ref.IsValid() { - return &ErrUnsupportedType{ref} + return &ErrUnsupportedType{ref, "t must be a valid reference"} } if ref.Kind() == reflect.Ptr { @@ -190,7 +185,7 @@ func MapScan(d Document, t interface{}) error { } if ref.Kind() != reflect.Map { - return &ErrUnsupportedType{ref} + return &ErrUnsupportedType{ref, "t is not a map"} } return mapScan(d, ref) @@ -198,7 +193,7 @@ func MapScan(d Document, t interface{}) error { func mapScan(d Document, ref reflect.Value) error { if ref.Type().Key().Kind() != reflect.String { - return &ErrUnsupportedType{ref} + return &ErrUnsupportedType{ref, "map key must be a string"} } if ref.IsNil() { @@ -225,7 +220,7 @@ func ScanValue(v Value, t interface{}) error { func scanValue(v Value, ref reflect.Value) error { if !ref.IsValid() { - return &ErrUnsupportedType{ref} + return &ErrUnsupportedType{ref, "parameter is not a valid reference"} } if ref.Type().Kind() == reflect.Ptr && ref.IsNil() { @@ -319,5 +314,5 @@ func scanValue(v Value, ref reflect.Value) error { return nil } - return &ErrUnsupportedType{ref} + return &ErrUnsupportedType{ref, "Invalid type"} } diff --git a/document/value.go b/document/value.go index 5b2a7e9d9..d3417573d 100644 --- a/document/value.go +++ b/document/value.go @@ -27,10 +27,11 @@ var ( // this error is used to skip struct or array fields that are not supported. type ErrUnsupportedType struct { Value interface{} + Msg string } func (e *ErrUnsupportedType) Error() string { - return fmt.Sprintf("unsupported type %T", e.Value) + return fmt.Sprintf("unsupported type %T. %s", e.Value, e.Msg) } // ValueType represents a value type supported by the database. @@ -164,10 +165,16 @@ func NewValue(x interface{}) (Value, error) { return NewNullValue(), nil } return NewArrayValue(&sliceArray{ref: v}), nil + case reflect.Map: + doc, err := NewFromMap(x) + if err != nil { + return Value{}, err + } + return NewDocumentValue(doc), nil } - return Value{}, &ErrUnsupportedType{x} + return Value{}, &ErrUnsupportedType{x, ""} } // NewBlobValue encodes x and returns a value.