14
14
15
15
package ociregistry
16
16
17
+ import (
18
+ "bytes"
19
+ "encoding/json"
20
+ "errors"
21
+ "net/http"
22
+ "strconv"
23
+ "strings"
24
+ "unicode"
25
+ )
26
+
27
+ // WireErrors is the JSON format used for error responses in
28
+ // the OCI HTTP API. It should always contain at least one
29
+ // error.
30
+ type WireErrors struct {
31
+ Errors []WireError `json:"errors"`
32
+ }
33
+
34
+ // Unwrap allows [errors.Is] and [errors.As] to
35
+ // see the errors inside e.
36
+ func (e * WireErrors ) Unwrap () []error {
37
+ // TODO we could do this only once.
38
+ errs := make ([]error , len (e .Errors ))
39
+ for i := range e .Errors {
40
+ errs [i ] = & e .Errors [i ]
41
+ }
42
+ return errs
43
+ }
44
+
45
+ func (e * WireErrors ) Error () string {
46
+ var buf strings.Builder
47
+ buf .WriteString (e .Errors [0 ].Error ())
48
+ for i := range e .Errors [1 :] {
49
+ buf .WriteString ("; " )
50
+ buf .WriteString (e .Errors [i + 1 ].Error ())
51
+ }
52
+ return buf .String ()
53
+ }
54
+
55
+ // WireError holds a single error in an OCI HTTP response.
56
+ type WireError struct {
57
+ Code_ string `json:"code"`
58
+ Message string `json:"message,omitempty"`
59
+ Detail_ json.RawMessage `json:"detail,omitempty"`
60
+ }
61
+
62
+ // Is makes it possible for users to write `if errors.Is(err, ociregistry.ErrBlobUnknown)`
63
+ // even when the error hasn't exactly wrapped that error.
64
+ func (e * WireError ) Is (err error ) bool {
65
+ var rerr Error
66
+ return errors .As (err , & rerr ) && rerr .Code () == e .Code ()
67
+ }
68
+
69
+ // Error implements the [error] interface.
70
+ func (e * WireError ) Error () string {
71
+ var buf strings.Builder
72
+ for _ , r := range e .Code_ {
73
+ if r == '_' {
74
+ buf .WriteByte (' ' )
75
+ } else {
76
+ buf .WriteRune (unicode .ToLower (r ))
77
+ }
78
+ }
79
+ if buf .Len () == 0 {
80
+ buf .WriteString ("(no code)" )
81
+ }
82
+ if e .Message != "" {
83
+ buf .WriteString (": " )
84
+ buf .WriteString (e .Message )
85
+ }
86
+ if len (e .Detail_ ) != 0 && ! bytes .Equal (e .Detail_ , []byte ("null" )) {
87
+ buf .WriteString ("; detail: " )
88
+ buf .Write (e .Detail_ )
89
+ }
90
+ return buf .String ()
91
+ }
92
+
93
+ // Code implements [Error.Code].
94
+ func (e * WireError ) Code () string {
95
+ return e .Code_
96
+ }
97
+
98
+ // Detail implements [Error.Detail].
99
+ func (e * WireError ) Detail () any {
100
+ if len (e .Detail_ ) == 0 {
101
+ return nil
102
+ }
103
+ // TODO do this once only?
104
+ var d any
105
+ json .Unmarshal (e .Detail_ , & d )
106
+ return d
107
+ }
108
+
17
109
// NewError returns a new error with the given code, message and detail.
18
110
func NewError (msg string , code string , detail any ) Error {
19
111
return & registryError {
@@ -31,14 +123,110 @@ type Error interface {
31
123
// error.Error provides the error message.
32
124
error
33
125
34
- // Code returns the error code. See
126
+ // Code returns the error code.
35
127
Code () string
36
128
37
129
// Detail returns any detail to be associated with the error; it should
38
130
// be JSON-marshable.
39
131
Detail () any
40
132
}
41
133
134
+ // HTTPError is optionally implemented by an error when
135
+ // the error has originated from an HTTP request
136
+ // or might be returned from one.
137
+ type HTTPError interface {
138
+ error
139
+
140
+ // StatusCode returns the HTTP status code of the response.
141
+ StatusCode () int
142
+
143
+ // Response holds the HTTP response that caused the HTTPError to
144
+ // be created. It will return nil if the error was not created
145
+ // as a result of an HTTP response.
146
+ //
147
+ // The caller should not read the response body or otherwise
148
+ // change the response (mutation of errors is a Bad Thing).
149
+ //
150
+ // Use the ResponseBody method to obtain the body of the
151
+ // response if needed.
152
+ Response () * http.Response
153
+
154
+ // ResponseBody returns the contents of the response body. It
155
+ // will return nil if the error was not created as a result of
156
+ // an HTTP response.
157
+ //
158
+ // The caller should not change or append to the returned data.
159
+ ResponseBody () []byte
160
+ }
161
+
162
+ // NewHTTPError returns an error that wraps err to make an [HTTPError]
163
+ // that represents the given status code, response and response body.
164
+ // Both response and body may be nil.
165
+ //
166
+ // A shallow copy is made of the response.
167
+ func NewHTTPError (err error , statusCode int , response * http.Response , body []byte ) HTTPError {
168
+ herr := & httpError {
169
+ underlying : err ,
170
+ statusCode : statusCode ,
171
+ }
172
+ if response != nil {
173
+ herr .response = ref (* response )
174
+ herr .response .Body = nil
175
+ herr .body = body
176
+ }
177
+ return herr
178
+ }
179
+
180
+ type httpError struct {
181
+ underlying error
182
+ statusCode int
183
+ response * http.Response
184
+ body []byte
185
+ }
186
+
187
+ // Unwrap implements the [errors] Unwrap interface.
188
+ func (e * httpError ) Unwrap () error {
189
+ return e .underlying
190
+ }
191
+
192
+ // Is makes it possible for users to write `if errors.Is(err, ociregistry.ErrRangeInvalid)`
193
+ // even when the error hasn't exactly wrapped that error.
194
+ func (e * httpError ) Is (err error ) bool {
195
+ switch e .statusCode {
196
+ case http .StatusRequestedRangeNotSatisfiable :
197
+ return err == ErrRangeInvalid
198
+ }
199
+ return false
200
+ }
201
+
202
+ // Error implements [error.Error].
203
+ func (e * httpError ) Error () string {
204
+ var buf strings.Builder
205
+ buf .WriteString (strconv .Itoa (e .statusCode ))
206
+ buf .WriteString (" " )
207
+ buf .WriteString (http .StatusText (e .statusCode ))
208
+ if e .underlying != nil {
209
+ buf .WriteString (": " )
210
+ buf .WriteString (e .underlying .Error ())
211
+ }
212
+ return buf .String ()
213
+ }
214
+
215
+ // StatusCode implements [HTTPEror.StatusCode].
216
+ func (e * httpError ) StatusCode () int {
217
+ return e .statusCode
218
+ }
219
+
220
+ // Response implements [HTTPEror.Response].
221
+ func (e * httpError ) Response () * http.Response {
222
+ return e .response
223
+ }
224
+
225
+ // ResponseBody implements [HTTPEror.ResponseBody].
226
+ func (e * httpError ) ResponseBody () []byte {
227
+ return e .body
228
+ }
229
+
42
230
// The following values represent the known error codes.
43
231
var (
44
232
ErrBlobUnknown = NewError ("blob unknown to registry" , "BLOB_UNKNOWN" , nil )
@@ -83,3 +271,7 @@ func (e *registryError) Error() string {
83
271
func (e * registryError ) Detail () any {
84
272
return e .detail
85
273
}
274
+
275
+ func ref [T any ](x T ) * T {
276
+ return & x
277
+ }
0 commit comments