-
Notifications
You must be signed in to change notification settings - Fork 1
/
validate.go
131 lines (114 loc) · 3.57 KB
/
validate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package lgrep
import (
"encoding/json"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/juju/errors"
"gopkg.in/olivere/elastic.v3"
)
var (
// unvalidatableKeys removes keys that cannot be validated via the API.
unvalidatableKeys = []string{"_source", "size", "sort"}
// ErrInvalidQuery indicates that the provided query was not
// validated by Elasticsearch.
ErrInvalidQuery = errors.New("Invalid search query")
// ErrInvalidLuceneSyntax indicates that the provided lucene query
// could not be parsed by Elasticsearch.
ErrInvalidLuceneSyntax = errors.New("Invalid Lucene syntax - see http://localhost/goto/syntax")
// ErrInvalidIndex indicates that a query was attempted on a non-existent index or index pattern.
ErrInvalidIndex = errors.New("Invalid query on unknown index")
)
// ValidationResponse is the Elasticsearch validation result payload.
type ValidationResponse struct {
Valid bool `json:"valid"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []map[string]interface{} `json:"failures"`
} `json:"_shards"`
Explanations []ValidationExplanation `json:"explanations,omit"`
}
// ValidationExplanation is a per-index explanation of a invalid query
// validation result.
type ValidationExplanation struct {
Index string `json:"index"`
Valid bool `json:"valid"`
Message string `json:"error"`
Error error `json:"-"`
}
func (l LGrep) validate(query interface{}, spec SearchOptions) (result ValidationResponse, err error) {
resp, err := l.validateBody(query, spec)
if err != nil {
message := err.Error()
if strings.Contains(message, "index_not_found_exception") {
return result, ErrInvalidIndex
}
return result, err
}
result.Explanations = make([]ValidationExplanation, 0)
err = json.Unmarshal(resp.Body, &result)
if err != nil {
return result, err
}
if result.Valid && result.Shards.Successful != 0 {
return result, nil
}
errs := make(map[string]error)
for i := range result.Explanations {
exp := result.Explanations[i]
exp.Error = parseValidationError(exp.Message, exp.Index)
errs[exp.Error.Error()] = exp.Error
}
if len(errs) == 1 {
for _, e := range errs {
err = e
}
return result, err
}
return result, ErrInvalidQuery
}
func (l LGrep) validateBody(query interface{}, spec SearchOptions) (response *elastic.Response, err error) {
path, params, err := spec.buildURL("_validate/query")
if err != nil {
return response, err
}
switch v := query.(type) {
case elastic.SearchSource:
query, _ = v.Source()
case *elastic.SearchSource:
query, _ = v.Source()
case json.RawMessage:
query = &v
default:
query = v
}
var queryMap map[string]interface{}
data, err := json.Marshal(query)
if err != nil {
return response, errors.Errorf("Error during validation prep [0]: %s", err)
}
err = json.Unmarshal(data, &queryMap)
if err != nil {
return response, errors.Errorf("Error during validation prep [1]: %s", err)
}
for _, key := range unvalidatableKeys {
delete(queryMap, key)
}
params.Set("explain", "true")
log.Debugf("Validating query at '%s?%s'", path, params.Encode())
return l.Client.PerformRequest("GET", path, params, queryMap)
}
func parseValidationError(msg string, index string) (err error) {
if msg == "" {
return nil
}
// Only error as lucene when raw json isn't being used.
if strings.Contains(msg, `Cannot parse`) {
return ErrInvalidLuceneSyntax
}
if index != "" {
return errors.New(strings.Replace(msg, "["+index+"]", "", 1))
}
return errors.New(msg)
}