diff --git a/highlight.go b/highlight.go new file mode 100644 index 0000000..9c076bc --- /dev/null +++ b/highlight.go @@ -0,0 +1,341 @@ +package esquery + +import ( + "github.com/fatih/structs" +) + +// Map returns a map representation of the highlight; implementing the +// Mappable interface. +func (q *QueryHighlight) Map() map[string]interface{} { + results := structs.Map(q.params) + if q.highlightQuery != nil { + results["query"] = q.highlightQuery.Map() + } + if q.fields != nil && len(q.fields) > 0 { + fields := make(map[string]interface{}) + for k, v := range q.fields { + fields[k] = v.Map() + } + results["fields"] = fields + } + return results +} + +type QueryHighlight struct { + highlightQuery Mappable `structs:"highlight_query,omitempty"` + fields map[string]*QueryHighlight `structs:"fields"` + params highlighParams +} + +type highlighParams struct { + PreTags []string `structs:"pre_tags,omitempty"` + PostTags []string `structs:"post_tags,omitempty"` + + FragmentSize uint16 `structs:"fragment_size,omitempty"` + NumberOfFragments uint16 `structs:"number_of_fragments,omitempty"` + Type HighlightType `structs:"type,string,omitempty"` + BoundaryChars string `structs:"boundary_chars,omitempty"` + BoundaryMaxScan uint16 `structs:"boundary_max_scan,omitempty"` + BoundaryScanner HighlightBoundaryScanner `structs:"boundary_scanner,string,omitempty"` + BoundaryScannerLocale string `structs:"boundary_scanner_locale,omitempty"` + Encoder HighlightEncoder `structs:"encoder,string,omitempty"` + ForceSource *bool `structs:"force_source,omitempty"` + Fragmenter HighlightFragmenter `structs:"fragmenter,string,omitempty"` + FragmentOffset uint16 `structs:"fragment_offset,omitempty"` + MatchedFields []string `structs:"matched_fields,omitempty"` + NoMatchSize uint16 `structs:"no_match_size,omitempty"` + Order HighlightOrder `structs:"order,string,omitempty"` + PhraseLimit uint16 `structs:"phrase_limit,omitempty"` + RequireFieldMatch *bool `structs:"require_field_match,omitempty"` + TagsSchema HighlightTagsSchema `structs:"tags_schema,string,omitempty"` +} + +// Highlight creates a new "query" of type "highlight" +func Highlight() *QueryHighlight { + return newHighlight() +} + +func newHighlight() *QueryHighlight { + return &QueryHighlight{ + fields: make(map[string]*QueryHighlight), + params: highlighParams{}, + } +} + +// PreTags sets the highlight query's pre_tags ignore unmapped field +func (q *QueryHighlight) PreTags(s ...string) *QueryHighlight { + q.params.PreTags = append(q.params.PreTags,s...) + return q +} + +// PostTags sets the highlight query's post_tags ignore unmapped field +func (q *QueryHighlight) PostTags(s ...string) *QueryHighlight { + q.params.PostTags = append(q.params.PostTags,s...) + return q +} + +// Field sets an entry the highlight query's fields +func (q *QueryHighlight) Field(name string, h ...*QueryHighlight) *QueryHighlight { + var fld *QueryHighlight + if len(h) > 0 { + fld = h[len(h)-1] + } else { + fld = &QueryHighlight{} + } + q.fields[name] = fld + return q +} + +// Fields sets all entries for the highlight query's fields +func (q *QueryHighlight) Fields(h map[string]*QueryHighlight) *QueryHighlight { + q.fields = h + return q +} + +// FragmentSize sets the highlight query's fragment_size ignore unmapped field +func (q *QueryHighlight) FragmentSize(i uint16) *QueryHighlight { + q.params.FragmentSize = i + return q +} + +// NumberOfFragments sets the highlight query's number_of_fragments ignore unmapped field +func (q *QueryHighlight) NumberOfFragments(i uint16) *QueryHighlight { + q.params.NumberOfFragments = i + return q +} + +// Type sets the highlight query's type ignore unmapped field +func (q *QueryHighlight) Type(t HighlightType) *QueryHighlight { + q.params.Type = t + return q +} + +// BoundaryChars sets the highlight query's boundary_chars ignore unmapped field +func (q *QueryHighlight) BoundaryChars(s string) *QueryHighlight { + q.params.BoundaryChars = s + return q +} + +// BoundaryMaxScan sets the highlight query's boundary_max_scan ignore unmapped field +func (q *QueryHighlight) BoundaryMaxScan(i uint16) *QueryHighlight { + q.params.BoundaryMaxScan = i + return q +} + +// BoundaryScanner sets the highlight query's boundary_scanner ignore unmapped field +func (q *QueryHighlight) BoundaryScanner(t HighlightBoundaryScanner) *QueryHighlight { + q.params.BoundaryScanner = t + return q +} + +// BoundaryScannerLocale sets the highlight query's boundary_scanner_locale ignore unmapped field +func (q *QueryHighlight) BoundaryScannerLocale(l string) *QueryHighlight { + q.params.BoundaryScannerLocale = l + return q +} + +// Encoder sets the highlight query's encoder ignore unmapped field +func (q *QueryHighlight) Encoder(e HighlightEncoder) *QueryHighlight { + q.params.Encoder = e + return q +} + +// ForceSource sets the highlight query's force_source ignore unmapped field +func (q *QueryHighlight) ForceSource(b bool) *QueryHighlight { + q.params.ForceSource = &b + return q +} + +// Fragmenter sets the highlight query's fragmenter ignore unmapped field +func (q *QueryHighlight) Fragmenter(f HighlightFragmenter) *QueryHighlight { + q.params.Fragmenter = f + return q +} + +// FragmentOffset sets the highlight query's fragment_offset ignore unmapped field +func (q *QueryHighlight) FragmentOffset(i uint16) *QueryHighlight { + q.params.FragmentOffset = i + return q +} + +// HighlightQuery sets the highlight query's highlight_query ignore unmapped field +func (q *QueryHighlight) HighlightQuery(b Mappable) *QueryHighlight { + q.highlightQuery = b + return q +} + +// MatchedFields sets the highlight query's matched_fields ignore unmapped field +func (q *QueryHighlight) MatchedFields(s ...string) *QueryHighlight { + q.params.MatchedFields = append(q.params.MatchedFields,s...) + return q +} + +// NoMatchSize sets the highlight query's no_match_size ignore unmapped field +func (q *QueryHighlight) NoMatchSize(i uint16) *QueryHighlight { + q.params.NoMatchSize = i + return q +} + +// Order sets the nested highlight's score order unmapped field +func (q *QueryHighlight) Order(o HighlightOrder) *QueryHighlight { + q.params.Order = o + return q +} + +// PhraseLimit sets the highlight query's phrase_limit ignore unmapped field +func (q *QueryHighlight) PhraseLimit(i uint16) *QueryHighlight { + q.params.PhraseLimit = i + return q +} + +// RequireFieldMatch sets the highlight query's require_field_match ignore unmapped field +func (q *QueryHighlight) RequireFieldMatch(b bool) *QueryHighlight { + q.params.RequireFieldMatch = &b + return q +} + +// TagsSchema sets the highlight query's tags_schema ignore unmapped field +func (q *QueryHighlight) TagsSchema(s HighlightTagsSchema) *QueryHighlight { + q.params.TagsSchema = s + return q +} + +type HighlightType uint8 + +const ( + // HighlighterUnified is the "unified" value + HighlighterUnified HighlightType = iota + + // HighlighterPlain is the "plain" value + HighlighterPlain + + // HighlighterFvh is the "fvh" value + HighlighterFvh +) + +// String returns a string representation of the type parameter, as +// known to ElasticSearch. +func (a HighlightType) String() string { + switch a { + case HighlighterUnified: + return "unified" + case HighlighterPlain: + return "plain" + case HighlighterFvh: + return "fvh" + } + return "" +} + +type HighlightBoundaryScanner uint8 + +const ( + BoundaryScannerDefault HighlightBoundaryScanner = iota + + // BoundaryScannerChars is the "chars" value + BoundaryScannerChars + + // BoundaryScannerSentence is the "sentence" value + BoundaryScannerSentence + + // BoundaryScannerWord is the "word" value + BoundaryScannerWord +) + +// String returns a string representation of the boundary_scanner parameter, as +// known to ElasticSearch. +func (a HighlightBoundaryScanner) String() string { + switch a { + case BoundaryScannerChars: + return "chars" + case BoundaryScannerSentence: + return "sentence" + case BoundaryScannerWord: + return "word" + } + return "" +} + +type HighlightEncoder uint8 + +const ( + // EncoderDefault is the "default" value + EncoderDefault HighlightEncoder = iota + + // EncoderHtml is the "html" value + EncoderHtml +) + +// String returns a string representation of the encoder parameter, as +// known to ElasticSearch. +func (a HighlightEncoder) String() string { + switch a { + case EncoderDefault: + return "default" + case EncoderHtml: + return "html" + } + return "" +} + +type HighlightFragmenter uint8 + +const ( + // FragmentSpan is the "span" value + FragmenterSpan HighlightFragmenter = iota + + // FragmenterSimple is the "simple" value + FragmenterSimple +) + +// String returns a string representation of the fragmenter parameter, as +// known to ElasticSearch. +func (a HighlightFragmenter) String() string { + switch a { + case FragmenterSpan: + return "span" + case FragmenterSimple: + return "simple" + } + return "" +} + +type HighlightOrder uint8 + +const ( + // OrderNone is the "none" value + OrderNone HighlightOrder = iota + + // OrderScore is the "score" value + OrderScore +) + +// String returns a string representation of the order parameter, as +// known to ElasticSearch. +func (a HighlightOrder) String() string { + switch a { + case OrderNone: + return "none" + case OrderScore: + return "score" + } + return "" +} + +type HighlightTagsSchema uint8 + +const ( + TagsSchemaDefault HighlightTagsSchema = iota + // TagsSchemaStyled is the "styled" value + TagsSchemaStyled +) + +// String returns a string representation of the tags_schema parameter, as +// known to ElasticSearch. +func (a HighlightTagsSchema) String() string { + switch a { + case TagsSchemaStyled: + return "styled" + } + return "" +} diff --git a/highlight_test.go b/highlight_test.go new file mode 100644 index 0000000..76456bf --- /dev/null +++ b/highlight_test.go @@ -0,0 +1,93 @@ +package esquery + +import ( + "testing" +) + +func TestHighlight(t *testing.T) { + runMapTests(t, []mapTest{ + { + "simple highlight", + Highlight().Field("content"), + map[string]interface{}{ + "fields": map[string]interface{} { + "content": map[string]interface{}{}, + }, + }, + }, + { + "highlight all params", + Highlight(). + PreTags("
","").
+ PostTags("
","
").
+ Field("content",
+ Highlight().
+ BoundaryChars(".;,")).
+ FragmentSize(150).
+ NumberOfFragments(4).
+ Type(HighlighterPlain).
+ BoundaryChars("()[]").
+ BoundaryMaxScan(32).
+ BoundaryScanner(BoundaryScannerChars).
+ BoundaryScannerLocale("en-US").
+ Encoder(EncoderHtml).
+ ForceSource(true).
+ Fragmenter(FragmenterSimple).
+ FragmentOffset(6).
+ HighlightQuery(
+ Bool().
+ Must(
+ Match("author").
+ Query("some guy").
+ Analyzer("analyzer?").
+ Fuzziness("fuzz"))).
+ MatchedFields("title","body").
+ NoMatchSize(64).
+ Order(OrderScore).
+ PhraseLimit(512).
+ RequireFieldMatch(false).
+ TagsSchema(TagsSchemaStyled),
+ map[string]interface{}{
+ "pre_tags": []string{"",""},
+ "post_tags": []string{"
","
"},
+ "fragment_size": 150,
+ "number_of_fragments": 4,
+ "type": "plain",
+ "boundary_chars": "()[]",
+ "boundary_scanner": "chars",
+ "boundary_max_scan": 32,
+ "boundary_scanner_locale": "en-US",
+ "encoder": "html",
+ "force_source": true,
+ "fragment_offset": 6,
+ "fragmenter": "simple",
+ "matched_fields": []string{"title","body"},
+ "no_match_size": 64,
+ "order": "score",
+ "phrase_limit": 512,
+ "require_field_match": false,
+ "tags_schema": "styled",
+ "fields": map[string]interface{}{
+ "content": map[string]interface{}{
+ "boundary_chars": ".;,",
+ },
+ },
+ "query": map[string]interface{} {
+ "bool": map[string]interface{} {
+ "must": []map[string]interface{} {
+ {
+ "match": map[string]interface{} {
+ "author": map[string]interface{} {
+ "analyzer": "analyzer?",
+ "fuzziness": "fuzz",
+ "query": "some guy",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ })
+}
diff --git a/search.go b/search.go
index 3da937e..39dabf6 100644
--- a/search.go
+++ b/search.go
@@ -15,15 +15,16 @@ import (
// Not all features of the search API are currently supported, but a request can
// currently include a query, aggregations, and more.
type SearchRequest struct {
- query Mappable
aggs []Aggregation
- postFilter Mappable
- from *uint64
- size *uint64
explain *bool
- timeout *time.Duration
- source Source
+ from *uint64
+ highlight Mappable
+ postFilter Mappable
+ query Mappable
+ size *uint64
sort Sort
+ source Source
+ timeout *time.Duration
}
// Search creates a new SearchRequest object, to be filled via method chaining.
@@ -98,6 +99,13 @@ func (req *SearchRequest) SourceExcludes(keys ...string) *SearchRequest {
return req
}
+// Highlight sets a highlight for the request.
+func (req *SearchRequest) Highlight(highlight Mappable) *SearchRequest {
+ req.highlight = highlight
+ return req
+}
+
+
// Map implements the Mappable interface. It converts the request to into a
// nested map[string]interface{}, as expected by the go-elasticsearch library.
func (req *SearchRequest) Map() map[string]interface{} {
@@ -131,6 +139,9 @@ func (req *SearchRequest) Map() map[string]interface{} {
if req.timeout != nil {
m["timeout"] = fmt.Sprintf("%.0fs", req.timeout.Seconds())
}
+ if req.highlight != nil {
+ m["highlight"] = req.highlight.Map()
+ }
source := req.source.Map()
if len(source) > 0 {