forked from opensearch-project/opensearch-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
error.go
186 lines (158 loc) · 5.11 KB
/
error.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
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
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: Apache-2.0
//
// The OpenSearch Contributors require contributions made to
// this file be licensed under the Apache-2.0 license or a
// compatible open source license.
package opensearch
import (
"encoding/json"
"errors"
"fmt"
"io"
)
// Error vars
var (
ErrUnexpectedEmptyBody = errors.New("body is unexpectedly empty")
ErrReadBody = errors.New("failed to read body")
ErrJSONUnmarshalBody = errors.New("failed to json unmarshal body")
ErrUnknownOpensearchError = errors.New("opensearch error response could not be parsed as error")
)
// Error represents an Opensearch error with only an error field
type Error struct {
Err string `json:"error"`
}
// Error returns a string
func (e Error) Error() string {
return fmt.Sprintf("error: %s", e.Err)
}
// StringError represnets an Opensearch error where error is a string
type StringError struct {
Err string `json:"error"`
Status int `json:"status"`
}
// Error returns a string
func (e StringError) Error() string {
return fmt.Sprintf("status: %d, error: %s", e.Status, e.Err)
}
// ReasonError represents an Opensearch error with a reason field
type ReasonError struct {
Reason string `json:"reason"`
Status string `json:"status"`
}
// Error returns a string
func (e ReasonError) Error() string {
return fmt.Sprintf("status: %s, reason: %s", e.Status, e.Reason)
}
// MessageError represents an Opensearch error with a message field
type MessageError struct {
Message string `json:"message"`
Status string `json:"status"`
}
// Error returns a string
func (e MessageError) Error() string {
return fmt.Sprintf("status: %s, message: %s", e.Status, e.Message)
}
// StructError represents an Opensearch error with a detailed error struct
type StructError struct {
Err Err `json:"error"`
Status int `json:"status"`
}
// Err represents the error of an API error response
type Err struct {
RootCause []RootCause `json:"root_cause"`
Type string `json:"type"`
Reason string `json:"reason"`
Index string `json:"index,omitempty"`
IndexUUID string `json:"index_uuid,omitempty"`
}
// RootCause represents the root_cause of an API error response
type RootCause struct {
Type string `json:"type"`
Reason string `json:"reason"`
Index string `json:"index,omitempty"`
IndexUUID string `json:"index_uuid,omitempty"`
}
// Error returns a string
func (e StructError) Error() string {
return fmt.Sprintf("status: %d, type: %s, reason: %s, root_cause: %s", e.Status, e.Err.Type, e.Err.Reason, e.Err.RootCause)
}
// UnmarshalJSON is a custom unmarshal function for StructError returning custom errors in special cases
func (e *StructError) UnmarshalJSON(b []byte) error {
var dummy struct {
Err json.RawMessage `json:"error"`
Status int `json:"status"`
}
if err := json.Unmarshal(b, &dummy); err != nil {
return err
}
var osErr Err
if err := json.Unmarshal(dummy.Err, &osErr); err != nil {
return &StringError{Status: dummy.Status, Err: string(dummy.Err)}
}
if dummy.Status == 0 || (osErr.Type == "" && osErr.Reason == "") {
return fmt.Errorf("%w: %s", ErrUnknownOpensearchError, b)
}
e.Err = osErr
e.Status = dummy.Status
return nil
}
// ParseError tries to parse the opensearch error into an custom error
func ParseError(resp *Response) error {
if resp.Body == nil {
return fmt.Errorf("%w, status: %s", ErrUnexpectedEmptyBody, resp.Status())
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%w: %w", ErrReadBody, err)
}
if !json.Valid(body) {
return fmt.Errorf("%s", body)
}
var testResp struct {
Status any `json:"status"`
Error any `json:"error"`
Message any `json:"message"`
Reason any `json:"reason"`
}
if err = json.Unmarshal(body, &testResp); err != nil {
return fmt.Errorf("%w: %w", ErrJSONUnmarshalBody, err)
}
// Check for errors where status is a number
if _, ok := testResp.Status.(float64); ok {
// Check for errors where error is a string
if _, ok := testResp.Error.(string); ok {
var apiError StringError
return parseError(body, &apiError)
}
// Check for errors where error is a struct
if _, ok := testResp.Error.(map[string]any); ok {
var apiError StructError
return parseError(body, &apiError)
}
}
// Check for errors where status is a string
if _, ok := testResp.Status.(string); ok {
// Check for erros where message is a string
if _, ok := testResp.Message.(string); ok {
var apiError MessageError
return parseError(body, &apiError)
}
// Check for errors where reason is a string
if _, ok := testResp.Reason.(string); ok {
var apiError ReasonError
return parseError(body, &apiError)
}
}
// Check for errors that only contain the error field
if testResp.Status == nil && testResp.Message == nil && testResp.Reason == nil && testResp.Error != nil {
var apiError Error
return parseError(body, &apiError)
}
return &StringError{Status: resp.StatusCode, Err: string(body)}
}
func parseError(body []byte, errStruct error) error {
if err := json.Unmarshal(body, &errStruct); err != nil {
return fmt.Errorf("%w: %w: %s", ErrJSONUnmarshalBody, err, string(body))
}
return errStruct
}