forked from opensearch-project/opensearch-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror.go
More file actions
214 lines (182 loc) · 6.21 KB
/
Copy patherror.go
File metadata and controls
214 lines (182 loc) · 6.21 KB
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// 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 (
"bytes"
"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"`
CausedBy *CausedBy `json:"caused_by,omitempty"`
Index string `json:"index,omitempty"`
IndexUUID string `json:"index_uuid,omitempty"`
}
// CausedBy represents the optional caused_by of an API error response. Causes can be nested
type CausedBy struct {
Type string `json:"type"`
Reason string `json:"reason"`
CausedBy *CausedBy `json:"caused_by,omitempty"`
}
// String returns a string representation of CausedBy, handling nested structures
func (c *CausedBy) String() string {
if c.CausedBy == nil {
return fmt.Sprintf("{type: %s, reason: %s}", c.Type, c.Reason)
}
return fmt.Sprintf("{type: %s, reason: %s, caused_by: %s}", c.Type, c.Reason, c.CausedBy)
}
// 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 {
result := fmt.Sprintf("status: %d, type: %s, reason: %s, root_cause: %s", e.Status, e.Err.Type, e.Err.Reason, e.Err.RootCause)
if e.Err.CausedBy != nil {
result += fmt.Sprintf(", caused_by: %s", e.Err.CausedBy)
}
return result
}
// 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)
resp.Body.Close()
if err != nil {
// Restore Body so callers don't see a closed reader. The partial
// bytes are intentionally dropped because the underlying read
// already failed; what matters is keeping the Body field
// consistent with the success branch below (an in-memory
// NopCloser the caller can safely Read or Close).
resp.Body = io.NopCloser(bytes.NewReader(body))
return fmt.Errorf("%w: %w", ErrReadBody, err)
}
resp.Body = io.NopCloser(bytes.NewReader(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, status: %d, body: %q", ErrJSONUnmarshalBody, resp.StatusCode, string(body))
}
// 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
}