1// Copyright 2012-present Oliver Eilhard. All rights reserved.
2// Use of this source code is governed by a MIT-license.
3// See http://olivere.mit-license.org/license.txt for details.
4
5package elastic
6
7import (
8	"context"
9	"fmt"
10	"net/url"
11
12	"gopkg.in/olivere/elastic.v5/uritemplates"
13)
14
15// IndicesAnalyzeService performs the analysis process on a text and returns
16// the tokens breakdown of the text.
17//
18// See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/indices-analyze.html
19// for detail.
20type IndicesAnalyzeService struct {
21	client      *Client
22	pretty      bool
23	index       string
24	request     *IndicesAnalyzeRequest
25	format      string
26	preferLocal *bool
27	bodyJson    interface{}
28	bodyString  string
29}
30
31// NewIndicesAnalyzeService creates a new IndicesAnalyzeService.
32func NewIndicesAnalyzeService(client *Client) *IndicesAnalyzeService {
33	return &IndicesAnalyzeService{
34		client:  client,
35		request: new(IndicesAnalyzeRequest),
36	}
37}
38
39// Index is the name of the index to scope the operation.
40func (s *IndicesAnalyzeService) Index(index string) *IndicesAnalyzeService {
41	s.index = index
42	return s
43}
44
45// Format of the output.
46func (s *IndicesAnalyzeService) Format(format string) *IndicesAnalyzeService {
47	s.format = format
48	return s
49}
50
51// PreferLocal, when true, specifies that a local shard should be used
52// if available. When false, a random shard is used (default: true).
53func (s *IndicesAnalyzeService) PreferLocal(preferLocal bool) *IndicesAnalyzeService {
54	s.preferLocal = &preferLocal
55	return s
56}
57
58// Request passes the analyze request to use.
59func (s *IndicesAnalyzeService) Request(request *IndicesAnalyzeRequest) *IndicesAnalyzeService {
60	if request == nil {
61		s.request = new(IndicesAnalyzeRequest)
62	} else {
63		s.request = request
64	}
65	return s
66}
67
68// Analyzer is the name of the analyzer to use.
69func (s *IndicesAnalyzeService) Analyzer(analyzer string) *IndicesAnalyzeService {
70	s.request.Analyzer = analyzer
71	return s
72}
73
74// Attributes is a list of token attributes to output; this parameter works
75// only with explain=true.
76func (s *IndicesAnalyzeService) Attributes(attributes ...string) *IndicesAnalyzeService {
77	s.request.Attributes = attributes
78	return s
79}
80
81// CharFilter is a list of character filters to use for the analysis.
82func (s *IndicesAnalyzeService) CharFilter(charFilter ...string) *IndicesAnalyzeService {
83	s.request.CharFilter = charFilter
84	return s
85}
86
87// Explain, when true, outputs more advanced details (default: false).
88func (s *IndicesAnalyzeService) Explain(explain bool) *IndicesAnalyzeService {
89	s.request.Explain = explain
90	return s
91}
92
93// Field specifies to use a specific analyzer configured for this field (instead of passing the analyzer name).
94func (s *IndicesAnalyzeService) Field(field string) *IndicesAnalyzeService {
95	s.request.Field = field
96	return s
97}
98
99// Filter is a list of filters to use for the analysis.
100func (s *IndicesAnalyzeService) Filter(filter ...string) *IndicesAnalyzeService {
101	s.request.Filter = filter
102	return s
103}
104
105// Text is the text on which the analysis should be performed (when request body is not used).
106func (s *IndicesAnalyzeService) Text(text ...string) *IndicesAnalyzeService {
107	s.request.Text = text
108	return s
109}
110
111// Tokenizer is the name of the tokenizer to use for the analysis.
112func (s *IndicesAnalyzeService) Tokenizer(tokenizer string) *IndicesAnalyzeService {
113	s.request.Tokenizer = tokenizer
114	return s
115}
116
117// Pretty indicates that the JSON response be indented and human readable.
118func (s *IndicesAnalyzeService) Pretty(pretty bool) *IndicesAnalyzeService {
119	s.pretty = pretty
120	return s
121}
122
123// BodyJson is the text on which the analysis should be performed.
124func (s *IndicesAnalyzeService) BodyJson(body interface{}) *IndicesAnalyzeService {
125	s.bodyJson = body
126	return s
127}
128
129// BodyString is the text on which the analysis should be performed.
130func (s *IndicesAnalyzeService) BodyString(body string) *IndicesAnalyzeService {
131	s.bodyString = body
132	return s
133}
134
135// buildURL builds the URL for the operation.
136func (s *IndicesAnalyzeService) buildURL() (string, url.Values, error) {
137	// Build URL
138	var err error
139	var path string
140
141	if s.index == "" {
142		path = "/_analyze"
143	} else {
144		path, err = uritemplates.Expand("/{index}/_analyze", map[string]string{
145			"index": s.index,
146		})
147	}
148	if err != nil {
149		return "", url.Values{}, err
150	}
151
152	// Add query string parameters
153	params := url.Values{}
154	if s.pretty {
155		params.Set("pretty", "1")
156	}
157	if s.format != "" {
158		params.Set("format", s.format)
159	}
160	if s.preferLocal != nil {
161		params.Set("prefer_local", fmt.Sprintf("%v", *s.preferLocal))
162	}
163
164	return path, params, nil
165}
166
167// Do will execute the request with the given context.
168func (s *IndicesAnalyzeService) Do(ctx context.Context) (*IndicesAnalyzeResponse, error) {
169	// Check pre-conditions
170	if err := s.Validate(); err != nil {
171		return nil, err
172	}
173
174	path, params, err := s.buildURL()
175	if err != nil {
176		return nil, err
177	}
178
179	// Setup HTTP request body
180	var body interface{}
181	if s.bodyJson != nil {
182		body = s.bodyJson
183	} else if s.bodyString != "" {
184		body = s.bodyString
185	} else {
186		// Request parameters are deprecated in 5.1.1, and we must use a JSON
187		// structure in the body to pass the parameters.
188		// See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/indices-analyze.html
189		body = s.request
190	}
191
192	res, err := s.client.PerformRequest(ctx, "POST", path, params, body)
193	if err != nil {
194		return nil, err
195	}
196
197	ret := new(IndicesAnalyzeResponse)
198	if err = s.client.decoder.Decode(res.Body, ret); err != nil {
199		return nil, err
200	}
201
202	return ret, nil
203}
204
205func (s *IndicesAnalyzeService) Validate() error {
206	var invalid []string
207	if s.bodyJson == nil && s.bodyString == "" {
208		if len(s.request.Text) == 0 {
209			invalid = append(invalid, "Text")
210		}
211	}
212	if len(invalid) > 0 {
213		return fmt.Errorf("missing required fields: %v", invalid)
214	}
215	return nil
216}
217
218// IndicesAnalyzeRequest specifies the parameters of the analyze request.
219type IndicesAnalyzeRequest struct {
220	Text       []string `json:"text,omitempty"`
221	Analyzer   string   `json:"analyzer,omitempty"`
222	Tokenizer  string   `json:"tokenizer,omitempty"`
223	Filter     []string `json:"filter,omitempty"`
224	CharFilter []string `json:"char_filter,omitempty"`
225	Field      string   `json:"field,omitempty"`
226	Explain    bool     `json:"explain,omitempty"`
227	Attributes []string `json:"attributes,omitempty"`
228}
229
230type IndicesAnalyzeResponse struct {
231	Tokens []IndicesAnalyzeResponseToken `json:"tokens"` // json part for normal message
232	Detail IndicesAnalyzeResponseDetail  `json:"detail"` // json part for verbose message of explain request
233}
234
235type IndicesAnalyzeResponseToken struct {
236	Token       string `json:"token"`
237	StartOffset int    `json:"start_offset"`
238	EndOffset   int    `json:"end_offset"`
239	Type        string `json:"type"`
240	Position    int    `json:"position"`
241}
242
243type IndicesAnalyzeResponseDetail struct {
244	CustomAnalyzer bool          `json:"custom_analyzer"`
245	Charfilters    []interface{} `json:"charfilters"`
246	Analyzer       struct {
247		Name   string `json:"name"`
248		Tokens []struct {
249			Token          string `json:"token"`
250			StartOffset    int    `json:"start_offset"`
251			EndOffset      int    `json:"end_offset"`
252			Type           string `json:"type"`
253			Position       int    `json:"position"`
254			Bytes          string `json:"bytes"`
255			PositionLength int    `json:"positionLength"`
256		} `json:"tokens"`
257	} `json:"analyzer"`
258	Tokenizer struct {
259		Name   string `json:"name"`
260		Tokens []struct {
261			Token       string `json:"token"`
262			StartOffset int    `json:"start_offset"`
263			EndOffset   int    `json:"end_offset"`
264			Type        string `json:"type"`
265			Position    int    `json:"position"`
266		} `json:"tokens"`
267	} `json:"tokenizer"`
268	Tokenfilters []struct {
269		Name   string `json:"name"`
270		Tokens []struct {
271			Token       string `json:"token"`
272			StartOffset int    `json:"start_offset"`
273			EndOffset   int    `json:"end_offset"`
274			Type        string `json:"type"`
275			Position    int    `json:"position"`
276			Keyword     bool   `json:"keyword"`
277		} `json:"tokens"`
278	} `json:"tokenfilters"`
279}
280