From 9359a69ee59d56ffdafbb841c83679ea9771144d Mon Sep 17 00:00:00 2001 From: Marty Schoch Date: Thu, 18 May 2017 15:08:33 -0400 Subject: [PATCH] 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 --- mapping/document.go | 29 ++++++++++++++++++ mapping/mapping_test.go | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/mapping/document.go b/mapping/document.go index 9bdb8596..6b903889 100644 --- a/mapping/document.go +++ b/mapping/document.go @@ -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) } diff --git a/mapping/mapping_test.go b/mapping/mapping_test.go index 309c0e1d..e16052b1 100644 --- a/mapping/mapping_test.go +++ b/mapping/mapping_test.go @@ -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())) + } + +}