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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/pkg/errors"
)
// checkResponse will return an error if the request/response indicates
// an error returned from Elasticsearch.
//
// HTTP status codes between in the range [200..299] are considered successful.
// All other errors are considered errors except they are specified in
// ignoreErrors. This is necessary because for some services, HTTP status 404
// is a valid response from Elasticsearch (e.g. the Exists service).
//
// The func tries to parse error details as returned from Elasticsearch
// and encapsulates them in type elastic.Error.
func checkResponse(req *http.Request, res *http.Response, ignoreErrors ...int) error {
// 200-299 are valid status codes
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
// Ignore certain errors?
for _, code := range ignoreErrors {
if code == res.StatusCode {
return nil
}
}
return createResponseError(res)
}
// createResponseError creates an Error structure from the HTTP response,
// its status code and the error information sent by Elasticsearch.
func createResponseError(res *http.Response) error {
if res.Body == nil {
return &Error{Status: res.StatusCode}
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return &Error{Status: res.StatusCode}
}
errReply := new(Error)
err = json.Unmarshal(data, errReply)
if err != nil {
return &Error{Status: res.StatusCode}
}
if errReply != nil {
if errReply.Status == 0 {
errReply.Status = res.StatusCode
}
return errReply
}
return &Error{Status: res.StatusCode}
}
// Error encapsulates error details as returned from Elasticsearch.
type Error struct {
Status int `json:"status"`
Details *ErrorDetails `json:"error,omitempty"`
}
// ErrorDetails encapsulate error details from Elasticsearch.
// It is used in e.g. elastic.Error and elastic.BulkResponseItem.
type ErrorDetails struct {
Type string `json:"type"`
Reason string `json:"reason"`
ResourceType string `json:"resource.type,omitempty"`
ResourceId string `json:"resource.id,omitempty"`
Index string `json:"index,omitempty"`
Phase string `json:"phase,omitempty"`
Grouped bool `json:"grouped,omitempty"`
CausedBy map[string]interface{} `json:"caused_by,omitempty"`
RootCause []*ErrorDetails `json:"root_cause,omitempty"`
FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
}
// Error returns a string representation of the error.
func (e *Error) Error() string {
if e.Details != nil && e.Details.Reason != "" {
return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
}
return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
}
// IsContextErr returns true if the error is from a context that was canceled or deadline exceeded
func IsContextErr(err error) bool {
if err == context.Canceled || err == context.DeadlineExceeded {
return true
}
// This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
if ue, ok := err.(*url.Error); ok {
if ue.Temporary() {
return true
}
// Use of an AWS Signing Transport can result in a wrapped url.Error
return IsContextErr(ue.Err)
}
return false
}
// IsConnErr returns true if the error indicates that Elastic could not
// find an Elasticsearch host to connect to.
func IsConnErr(err error) bool {
return err == ErrNoClient || errors.Cause(err) == ErrNoClient
}
// IsNotFound returns true if the given error indicates that Elasticsearch
// returned HTTP status 404. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func IsNotFound(err interface{}) bool {
return IsStatusCode(err, http.StatusNotFound)
}
// IsTimeout returns true if the given error indicates that Elasticsearch
// returned HTTP status 408. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func IsTimeout(err interface{}) bool {
return IsStatusCode(err, http.StatusRequestTimeout)
}
// IsConflict returns true if the given error indicates that the Elasticsearch
// operation resulted in a version conflict. This can occur in operations like
// `update` or `index` with `op_type=create`. The err parameter can be of
// type *elastic.Error, elastic.Error, *http.Response or int (indicating the
// HTTP status code).
func IsConflict(err interface{}) bool {
return IsStatusCode(err, http.StatusConflict)
}
// IsStatusCode returns true if the given error indicates that the Elasticsearch
// operation returned the specified HTTP status code. The err parameter can be of
// type *http.Response, *Error, Error, or int (indicating the HTTP status code).
func IsStatusCode(err interface{}, code int) bool {
switch e := err.(type) {
case *http.Response:
return e.StatusCode == code
case *Error:
return e.Status == code
case Error:
return e.Status == code
case int:
return e == code
}
return false
}
// -- General errors --
// ShardsInfo represents information from a shard.
type ShardsInfo struct {
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []*ShardFailure `json:"failures,omitempty"`
}
// ShardFailure represents details about a failure.
type ShardFailure struct {
Index string `json:"_index,omitempty"`
Shard int `json:"_shard,omitempty"`
Node string `json:"_node,omitempty"`
Reason map[string]interface{} `json:"reason,omitempty"`
Status string `json:"status,omitempty"`
Primary bool `json:"primary,omitempty"`
}
|