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/http"
11	"net/url"
12
13	"gopkg.in/olivere/elastic.v5/uritemplates"
14)
15
16// ExistsService checks for the existence of a document using HEAD.
17//
18// See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/docs-get.html
19// for details.
20type ExistsService struct {
21	client     *Client
22	pretty     bool
23	id         string
24	index      string
25	typ        string
26	preference string
27	realtime   *bool
28	refresh    string
29	routing    string
30	parent     string
31}
32
33// NewExistsService creates a new ExistsService.
34func NewExistsService(client *Client) *ExistsService {
35	return &ExistsService{
36		client: client,
37	}
38}
39
40// Id is the document ID.
41func (s *ExistsService) Id(id string) *ExistsService {
42	s.id = id
43	return s
44}
45
46// Index is the name of the index.
47func (s *ExistsService) Index(index string) *ExistsService {
48	s.index = index
49	return s
50}
51
52// Type is the type of the document (use `_all` to fetch the first document
53// matching the ID across all types).
54func (s *ExistsService) Type(typ string) *ExistsService {
55	s.typ = typ
56	return s
57}
58
59// Preference specifies the node or shard the operation should be performed on (default: random).
60func (s *ExistsService) Preference(preference string) *ExistsService {
61	s.preference = preference
62	return s
63}
64
65// Realtime specifies whether to perform the operation in realtime or search mode.
66func (s *ExistsService) Realtime(realtime bool) *ExistsService {
67	s.realtime = &realtime
68	return s
69}
70
71// Refresh the shard containing the document before performing the operation.
72func (s *ExistsService) Refresh(refresh string) *ExistsService {
73	s.refresh = refresh
74	return s
75}
76
77// Routing is a specific routing value.
78func (s *ExistsService) Routing(routing string) *ExistsService {
79	s.routing = routing
80	return s
81}
82
83// Parent is the ID of the parent document.
84func (s *ExistsService) Parent(parent string) *ExistsService {
85	s.parent = parent
86	return s
87}
88
89// Pretty indicates that the JSON response be indented and human readable.
90func (s *ExistsService) Pretty(pretty bool) *ExistsService {
91	s.pretty = pretty
92	return s
93}
94
95// buildURL builds the URL for the operation.
96func (s *ExistsService) buildURL() (string, url.Values, error) {
97	// Build URL
98	path, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
99		"id":    s.id,
100		"index": s.index,
101		"type":  s.typ,
102	})
103	if err != nil {
104		return "", url.Values{}, err
105	}
106
107	// Add query string parameters
108	params := url.Values{}
109	if s.pretty {
110		params.Set("pretty", "1")
111	}
112	if s.realtime != nil {
113		params.Set("realtime", fmt.Sprintf("%v", *s.realtime))
114	}
115	if s.refresh != "" {
116		params.Set("refresh", s.refresh)
117	}
118	if s.routing != "" {
119		params.Set("routing", s.routing)
120	}
121	if s.parent != "" {
122		params.Set("parent", s.parent)
123	}
124	if s.preference != "" {
125		params.Set("preference", s.preference)
126	}
127	return path, params, nil
128}
129
130// Validate checks if the operation is valid.
131func (s *ExistsService) Validate() error {
132	var invalid []string
133	if s.id == "" {
134		invalid = append(invalid, "Id")
135	}
136	if s.index == "" {
137		invalid = append(invalid, "Index")
138	}
139	if s.typ == "" {
140		invalid = append(invalid, "Type")
141	}
142	if len(invalid) > 0 {
143		return fmt.Errorf("missing required fields: %v", invalid)
144	}
145	return nil
146}
147
148// Do executes the operation.
149func (s *ExistsService) Do(ctx context.Context) (bool, error) {
150	// Check pre-conditions
151	if err := s.Validate(); err != nil {
152		return false, err
153	}
154
155	// Get URL for request
156	path, params, err := s.buildURL()
157	if err != nil {
158		return false, err
159	}
160
161	// Get HTTP response
162	res, err := s.client.PerformRequest(ctx, "HEAD", path, params, nil, 404)
163	if err != nil {
164		return false, err
165	}
166
167	// Return operation response
168	switch res.StatusCode {
169	case http.StatusOK:
170		return true, nil
171	case http.StatusNotFound:
172		return false, nil
173	default:
174		return false, fmt.Errorf("elastic: got HTTP code %d when it should have been either 200 or 404", res.StatusCode)
175	}
176}
177