errors.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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. package elastic
  5. import (
  6. "context"
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "github.com/pkg/errors"
  13. )
  14. // checkResponse will return an error if the request/response indicates
  15. // an error returned from Elasticsearch.
  16. //
  17. // HTTP status codes between in the range [200..299] are considered successful.
  18. // All other errors are considered errors except they are specified in
  19. // ignoreErrors. This is necessary because for some services, HTTP status 404
  20. // is a valid response from Elasticsearch (e.g. the Exists service).
  21. //
  22. // The func tries to parse error details as returned from Elasticsearch
  23. // and encapsulates them in type elastic.Error.
  24. func checkResponse(req *http.Request, res *http.Response, ignoreErrors ...int) error {
  25. // 200-299 are valid status codes
  26. if res.StatusCode >= 200 && res.StatusCode <= 299 {
  27. return nil
  28. }
  29. // Ignore certain errors?
  30. for _, code := range ignoreErrors {
  31. if code == res.StatusCode {
  32. return nil
  33. }
  34. }
  35. return createResponseError(res)
  36. }
  37. // createResponseError creates an Error structure from the HTTP response,
  38. // its status code and the error information sent by Elasticsearch.
  39. func createResponseError(res *http.Response) error {
  40. if res.Body == nil {
  41. return &Error{Status: res.StatusCode}
  42. }
  43. data, err := ioutil.ReadAll(res.Body)
  44. if err != nil {
  45. return &Error{Status: res.StatusCode}
  46. }
  47. errReply := new(Error)
  48. err = json.Unmarshal(data, errReply)
  49. if err != nil {
  50. return &Error{Status: res.StatusCode}
  51. }
  52. if errReply != nil {
  53. if errReply.Status == 0 {
  54. errReply.Status = res.StatusCode
  55. }
  56. return errReply
  57. }
  58. return &Error{Status: res.StatusCode}
  59. }
  60. // Error encapsulates error details as returned from Elasticsearch.
  61. type Error struct {
  62. Status int `json:"status"`
  63. Details *ErrorDetails `json:"error,omitempty"`
  64. }
  65. // ErrorDetails encapsulate error details from Elasticsearch.
  66. // It is used in e.g. elastic.Error and elastic.BulkResponseItem.
  67. type ErrorDetails struct {
  68. Type string `json:"type"`
  69. Reason string `json:"reason"`
  70. ResourceType string `json:"resource.type,omitempty"`
  71. ResourceId string `json:"resource.id,omitempty"`
  72. Index string `json:"index,omitempty"`
  73. Phase string `json:"phase,omitempty"`
  74. Grouped bool `json:"grouped,omitempty"`
  75. CausedBy map[string]interface{} `json:"caused_by,omitempty"`
  76. RootCause []*ErrorDetails `json:"root_cause,omitempty"`
  77. FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
  78. }
  79. // Error returns a string representation of the error.
  80. func (e *Error) Error() string {
  81. if e.Details != nil && e.Details.Reason != "" {
  82. return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
  83. }
  84. return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
  85. }
  86. // IsContextErr returns true if the error is from a context that was canceled or deadline exceeded
  87. func IsContextErr(err error) bool {
  88. if err == context.Canceled || err == context.DeadlineExceeded {
  89. return true
  90. }
  91. // This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
  92. if ue, ok := err.(*url.Error); ok {
  93. if ue.Temporary() {
  94. return true
  95. }
  96. // Use of an AWS Signing Transport can result in a wrapped url.Error
  97. return IsContextErr(ue.Err)
  98. }
  99. return false
  100. }
  101. // IsConnErr returns true if the error indicates that Elastic could not
  102. // find an Elasticsearch host to connect to.
  103. func IsConnErr(err error) bool {
  104. return err == ErrNoClient || errors.Cause(err) == ErrNoClient
  105. }
  106. // IsNotFound returns true if the given error indicates that Elasticsearch
  107. // returned HTTP status 404. The err parameter can be of type *elastic.Error,
  108. // elastic.Error, *http.Response or int (indicating the HTTP status code).
  109. func IsNotFound(err interface{}) bool {
  110. return IsStatusCode(err, http.StatusNotFound)
  111. }
  112. // IsTimeout returns true if the given error indicates that Elasticsearch
  113. // returned HTTP status 408. The err parameter can be of type *elastic.Error,
  114. // elastic.Error, *http.Response or int (indicating the HTTP status code).
  115. func IsTimeout(err interface{}) bool {
  116. return IsStatusCode(err, http.StatusRequestTimeout)
  117. }
  118. // IsConflict returns true if the given error indicates that the Elasticsearch
  119. // operation resulted in a version conflict. This can occur in operations like
  120. // `update` or `index` with `op_type=create`. The err parameter can be of
  121. // type *elastic.Error, elastic.Error, *http.Response or int (indicating the
  122. // HTTP status code).
  123. func IsConflict(err interface{}) bool {
  124. return IsStatusCode(err, http.StatusConflict)
  125. }
  126. // IsStatusCode returns true if the given error indicates that the Elasticsearch
  127. // operation returned the specified HTTP status code. The err parameter can be of
  128. // type *http.Response, *Error, Error, or int (indicating the HTTP status code).
  129. func IsStatusCode(err interface{}, code int) bool {
  130. switch e := err.(type) {
  131. case *http.Response:
  132. return e.StatusCode == code
  133. case *Error:
  134. return e.Status == code
  135. case Error:
  136. return e.Status == code
  137. case int:
  138. return e == code
  139. }
  140. return false
  141. }
  142. // -- General errors --
  143. // ShardsInfo represents information from a shard.
  144. type ShardsInfo struct {
  145. Total int `json:"total"`
  146. Successful int `json:"successful"`
  147. Failed int `json:"failed"`
  148. Failures []*ShardFailure `json:"failures,omitempty"`
  149. }
  150. // ShardFailure represents details about a failure.
  151. type ShardFailure struct {
  152. Index string `json:"_index,omitempty"`
  153. Shard int `json:"_shard,omitempty"`
  154. Node string `json:"_node,omitempty"`
  155. Reason map[string]interface{} `json:"reason,omitempty"`
  156. Status string `json:"status,omitempty"`
  157. Primary bool `json:"primary,omitempty"`
  158. }