0
0
Fork 0

add support for mapping to recognize/use TextMarshaler interface

Sometimes you have structs which contain data which isn't
exported, or for which the correct data to index isn't just the
contents of it's exported fields.  In these cases your struct
can implement TextMarshaler to return a suitable text
representation.

Previously bleve did not recognize this interface and do anything
to use it.  Now, if the field containing such a struct is
explicitly mapped as "text" and if the struct (or pointer to it)
implements TextMarshaler, we index a text field with the
contents returned by MarshalText().

For backwards compatibilty, dynamic mappings will never use
this feature, and will continue to traverse into the struct
and index the exported fields directly.

fixes #281
This commit is contained in:
Marty Schoch 2017-05-18 15:08:33 -04:00
parent 5c9915c6f4
commit 9359a69ee5
2 changed files with 95 additions and 0 deletions

View File

@ -15,6 +15,7 @@
package mapping
import (
"encoding"
"encoding/json"
"fmt"
"reflect"
@ -481,6 +482,17 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
fieldMapping := newDateTimeFieldMappingDynamic(context.im)
fieldMapping.processTime(property, pathString, path, indexes, context)
}
case encoding.TextMarshaler:
txt, err := property.MarshalText()
if err == nil && subDocMapping != nil {
// index by explicit mapping
for _, fieldMapping := range subDocMapping.Fields {
if fieldMapping.Type == "text" {
fieldMapping.processString(string(txt), pathString, path, indexes, context)
}
}
}
dm.walkDocument(property, path, indexes, context)
default:
if subDocMapping != nil {
for _, fieldMapping := range subDocMapping.Fields {
@ -500,6 +512,23 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
}
}
dm.walkDocument(property, path, indexes, context)
case reflect.Ptr:
switch property := property.(type) {
case encoding.TextMarshaler:
txt, err := property.MarshalText()
if err == nil && subDocMapping != nil {
// index by explicit mapping
for _, fieldMapping := range subDocMapping.Fields {
if fieldMapping.Type == "text" {
fieldMapping.processString(string(txt), pathString, path, indexes, context)
}
}
} else {
dm.walkDocument(property, path, indexes, context)
}
default:
dm.walkDocument(property, path, indexes, context)
}
default:
dm.walkDocument(property, path, indexes, context)
}

View File

@ -900,3 +900,69 @@ func TestMappingForGeo(t *testing.T) {
t.Errorf("expected to find geo point, did not")
}
}
type textMarshalable struct {
body string
Extra string
}
func (t *textMarshalable) MarshalText() ([]byte, error) {
return []byte(t.body), nil
}
func TestMappingForTextMarshaler(t *testing.T) {
tm := struct {
Marshalable *textMarshalable
}{
Marshalable: &textMarshalable{
body: "text",
Extra: "stuff",
},
}
// first verify that when using a mapping that doesn't explicity
// map the stuct field as text, then we traverse inside the struct
// and do our best
m := NewIndexMapping()
doc := document.NewDocument("x")
err := m.MapDocument(doc, tm)
if err != nil {
t.Fatal(err)
}
if len(doc.Fields) != 1 {
t.Fatalf("expected 1 field, got: %d", len(doc.Fields))
}
if doc.Fields[0].Name() != "Marshalable.Extra" {
t.Errorf("expected field to be named 'Marshalable.Extra', got: '%s'", doc.Fields[0].Name())
}
if string(doc.Fields[0].Value()) != tm.Marshalable.Extra {
t.Errorf("expected field value to be '%s', got: '%s'", tm.Marshalable.Extra, string(doc.Fields[0].Value()))
}
// now verify that when a mapping explicity
m = NewIndexMapping()
txt := NewTextFieldMapping()
m.DefaultMapping.AddFieldMappingsAt("Marshalable", txt)
doc = document.NewDocument("x")
err = m.MapDocument(doc, tm)
if err != nil {
t.Fatal(err)
}
if len(doc.Fields) != 1 {
t.Fatalf("expected 1 field, got: %d", len(doc.Fields))
}
if doc.Fields[0].Name() != "Marshalable" {
t.Errorf("expected field to be named 'Marshalable', got: '%s'", doc.Fields[0].Name())
}
want, err := tm.Marshalable.MarshalText()
if err != nil {
t.Fatal(err)
}
if string(doc.Fields[0].Value()) != string(want) {
t.Errorf("expected field value to be '%s', got: '%s'", string(want), string(doc.Fields[0].Value()))
}
}