Skip to content

Commit

Permalink
Enable anonymous fields.
Browse files Browse the repository at this point in the history
Enable any map type with string key

add test for invalid map types

refactor funny scanValue

refactor funny scanValue
  • Loading branch information
jpeletier committed Mar 2, 2020
1 parent 0d59d07 commit d195310
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 50 deletions.
30 changes: 19 additions & 11 deletions document/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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.
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down
12 changes: 11 additions & 1 deletion document/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
54 changes: 30 additions & 24 deletions document/encoding/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,21 +64,15 @@ func TestEncodeDecode(t *testing.T) {
},
{
"Map",
document.NewFromMap(map[string]interface{}{
"age": 10,
"name": "john",
}),
userMapDoc,
`{"age": 10, "name": "john"}`,
},
{
"Nested Document",
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"}}`,
},
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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]]`,
},
Expand Down
19 changes: 7 additions & 12 deletions document/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -182,23 +177,23 @@ 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 {
ref = reflect.Indirect(ref)
}

if ref.Kind() != reflect.Map {
return &ErrUnsupportedType{ref}
return &ErrUnsupportedType{ref, "t is not a map"}
}

return mapScan(d, ref)
}

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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -319,5 +314,5 @@ func scanValue(v Value, ref reflect.Value) error {
return nil
}

return &ErrUnsupportedType{ref}
return &ErrUnsupportedType{ref, "Invalid type"}
}
11 changes: 9 additions & 2 deletions document/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit d195310

Please sign in to comment.