internal/pkg/es/error.go (142 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package es
import (
"encoding/json"
"errors"
"strconv"
"strings"
)
const (
unknownErrorType = "unknown_error"
timeoutErrorType = "timeout_exception"
indexNotFoundErrorType = "index_not_found_exception"
versionConflictErrorType = "version_conflict_engine_exception"
)
// TODO: Why do we have both ErrElastic and ErrorT? Very strange.
type ErrElastic struct {
Status int
Type string
Reason string
Cause struct {
Type string
Reason string
}
}
func (e *ErrElastic) Unwrap() error {
if e.Type == indexNotFoundErrorType {
return ErrIndexNotFound
} else if e.Type == timeoutErrorType {
return ErrTimeout
}
return nil
}
func (e ErrElastic) Error() string {
// Improved error string to account on missing empty e.Type and e.Reason
// Otherwise were getting: "elastic fail 404::"
msg := "elastic fail "
var b strings.Builder
b.Grow(len(msg) + 11 + len(e.Type) + len(e.Reason) + len(e.Cause.Type) + len(e.Cause.Reason))
b.WriteString(msg)
b.WriteString(strconv.Itoa(e.Status))
if e.Type != "" {
b.WriteString(": ")
b.WriteString(e.Type)
}
if e.Reason != "" {
b.WriteString(": ")
b.WriteString(e.Reason)
}
if e.Cause.Type != "" {
b.WriteString(": ")
b.WriteString(e.Cause.Type)
}
if e.Cause.Reason != "" {
b.WriteString(": ")
b.WriteString(e.Cause.Reason)
}
return b.String()
}
var (
ErrElasticVersionConflict = errors.New("elastic version conflict")
ErrElasticNotFound = errors.New("elastic not found")
ErrInvalidBody = errors.New("invalid body")
ErrIndexNotFound = errors.New("index not found")
ErrTimeout = errors.New("timeout")
ErrNotFound = errors.New("not found")
knownErrorTypes = [3]string{
timeoutErrorType,
indexNotFoundErrorType,
versionConflictErrorType,
}
// helps with native translation of native java exceptions
// list possible exceptions is much broader, these we recognize.
// all listed here: https://github.com/elastic/elasticsearch/blob/f8d1d2afa67afd1b9769751fde35f86c5ec885d9/server/src/main/java/org/elasticsearch/ElasticsearchException.java#L730
errorTranslationMap = map[string]string{
ErrIndexNotFound.Error(): indexNotFoundErrorType,
"IndexNotFoundException": indexNotFoundErrorType,
ErrTimeout.Error(): timeoutErrorType,
"ElasticsearchTimeoutException": timeoutErrorType,
"ProcessClusterEventTimeoutException": timeoutErrorType,
"ReceiveTimeoutTransportException": timeoutErrorType,
ErrElasticVersionConflict.Error(): versionConflictErrorType,
"VersionConflictEngineException": versionConflictErrorType,
}
)
func TranslateError(status int, rawError json.RawMessage) error {
if status == 200 || status == 201 {
return nil
}
if len(rawError) == 0 {
// error was omitted
return &ErrElastic{
Status: status,
}
}
// try decoding detailed error by default
detailedError := &ErrorT{}
if err := json.Unmarshal(rawError, &detailedError); err == nil {
return translateDetailedError(status, detailedError)
}
reason := string(rawError)
eType := errType(reason)
switch eType {
case versionConflictErrorType:
return ErrElasticVersionConflict
default:
return &ErrElastic{
Status: status,
Type: eType,
Reason: reason,
}
}
}
func errType(errBody string) string {
for _, errCheck := range knownErrorTypes {
if strings.Contains(errBody, errCheck) {
return errCheck
}
}
for errCheck, errType := range errorTranslationMap {
if strings.Contains(errBody, errCheck) {
return errType
}
}
return unknownErrorType
}
func translateDetailedError(status int, e *ErrorT) error {
if e == nil {
return &ErrElastic{
Status: status,
}
}
var err error
switch e.Type {
case versionConflictErrorType:
err = ErrElasticVersionConflict
default:
err = &ErrElastic{
Status: status,
Type: e.Type,
Reason: e.Reason,
Cause: struct {
Type string
Reason string
}{
Type: e.Cause.Type,
Reason: e.Cause.Reason,
},
}
}
return err
}