github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/couchdb/errors.go (about) 1 package couchdb 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 "strconv" 10 "strings" 11 ) 12 13 // This file contains error handling code for couchdb request 14 // Possible errors in connecting to couchdb 15 // 503 Service Unavailable when the stack cant connect to couchdb or when 16 // couchdb response is interrupted mid-stream 17 // 500 When the viper provided configuration does not allow us to properly 18 // call http.newRequest, ie. wrong couchdbURL config, 19 // or when CouchDB has a bug 20 21 // Possible native couchdb errors 22 // 400 Bad Request : Bad request structure. The error can indicate an error 23 // with the request URL, path or headers. Differences in the supplied MD5 24 // hash and content also trigger this error, as this may indicate message 25 // corruption. 26 // 401 Unauthorized : The item requested was not available using the supplied 27 // authorization, or authorization was not supplied. 28 // {"error":"unauthorized","reason":"You are not a server admin."} 29 // {"error":"unauthorized","reason":"Name or password is incorrect."} 30 // 403 Forbidden : The requested item or operation is forbidden. 31 // 404 Not Found : The requested content could not be found. The content will 32 // include further information, as a JSON object, if available. 33 // **The structure will contain two keys, error and reason.** 34 // {"error":"not_found","reason":"deleted"} 35 // {"error":"not_found","reason":"missing"} 36 // {"error":"not_found","reason":"no_db_file"} 37 // 405 Resource Not Allowed : A request was made using an invalid HTTP request 38 // type for the URL requested. For example, you have requested a PUT when a 39 // POST is required. Errors of this type can also triggered by invalid URL 40 // strings. 41 // 406 Not Acceptable : The requested content type is not supported by the 42 // server. 43 // 409 Conflict : Request resulted in an update conflict. 44 // {"error":"conflict","reason":"Document update conflict."} 45 // 412 Precondition Failed : The request headers from the client and the 46 // capabilities of the server do not match. 47 // 415 Bad Content Type : The content types supported, and the content type of 48 // the information being requested or submitted indicate that the content 49 // type is not supported. 50 // 416 Requested Range Not Satisfiable : The range specified in the request 51 // header cannot be satisfied by the server. 52 // 417 Expectation Failed : When sending documents in bulk, the bulk load 53 // operation failed. 54 // 500 Internal Server Error : The request was invalid, either because the 55 // supplied JSON was invalid, or invalid information was supplied as part 56 // of the request. 57 58 // Error represent an error from couchdb 59 type Error struct { 60 StatusCode int `json:"status_code"` 61 CouchdbJSON []byte `json:"-"` 62 Name string `json:"error"` 63 Reason string `json:"reason"` 64 Original error `json:"-"` 65 } 66 67 func (e *Error) Error() string { 68 msg := fmt.Sprintf("CouchDB(%s): %s", e.Name, e.Reason) 69 if e.Original != nil { 70 msg += " - " + e.Original.Error() 71 } 72 return msg 73 } 74 75 // JSON returns the json representation of this error 76 func (e *Error) JSON() map[string]interface{} { 77 jsonMap := map[string]interface{}{ 78 "ok": false, 79 "status": strconv.Itoa(e.StatusCode), 80 "error": e.Name, 81 "reason": e.Reason, 82 } 83 if e.Original != nil { 84 jsonMap["original"] = e.Original.Error() 85 } 86 return jsonMap 87 } 88 89 // IsCouchError returns whether or not one error from the error 90 // tree is of type couchdb.Error. 91 func IsCouchError(err error) (*Error, bool) { 92 var couchErr *Error 93 94 isCouchErr := errors.As(err, &couchErr) 95 96 return couchErr, isCouchErr 97 } 98 99 // IsInternalServerError checks if an error from the error tree 100 // contains a CouchDB error with a 5xx code 101 func IsInternalServerError(err error) bool { 102 var couchErr *Error 103 104 if ok := errors.As(err, &couchErr); !ok { 105 return false 106 } 107 108 return couchErr.StatusCode/100 == 5 109 } 110 111 // IsNoDatabaseError checks if an error from the error tree 112 // contains a CouchDB a "no_db_file" error. 113 func IsNoDatabaseError(err error) bool { 114 var couchErr *Error 115 116 if ok := errors.As(err, &couchErr); !ok { 117 return false 118 } 119 120 return couchErr.Reason == "no_db_file" || 121 couchErr.Reason == "Database does not exist." 122 } 123 124 // IsNotFoundError checks if an error from the error tree 125 // contains a CouchDB a "not_found" error. 126 func IsNotFoundError(err error) bool { 127 var couchErr *Error 128 129 if ok := errors.As(err, &couchErr); !ok { 130 return false 131 } 132 133 return (couchErr.Name == "not_found" || 134 couchErr.Reason == "no_db_file" || 135 couchErr.Reason == "Database does not exist.") 136 } 137 138 // IsDeletedError checks if an error from the error tree 139 // contains a CouchDB a "not_found" error with the "deleted" 140 // reason. It means that a document has existed but is 141 // now deleted. 142 func IsDeletedError(err error) bool { 143 var couchErr *Error 144 145 if ok := errors.As(err, &couchErr); !ok { 146 return false 147 } 148 149 return couchErr.Name == "not_found" && couchErr.Reason == "deleted" 150 } 151 152 // IsFileExists checks if an error from the error tree 153 // contains a CouchDB a "file_exists" error. 154 func IsFileExists(err error) bool { 155 var couchErr *Error 156 157 if ok := errors.As(err, &couchErr); !ok { 158 return false 159 } 160 161 return couchErr.Name == "file_exists" 162 } 163 164 // IsConflictError checks if an error from the error tree 165 // contains a CouchDB 409 (Conflict) status code. 166 func IsConflictError(err error) bool { 167 var couchErr *Error 168 169 if ok := errors.As(err, &couchErr); !ok { 170 return false 171 } 172 173 return couchErr.StatusCode == http.StatusConflict 174 } 175 176 // IsNoUsableIndexError checks if an error from the error tree 177 // contains a "no_usable_index" error. 178 func IsNoUsableIndexError(err error) bool { 179 var couchErr *Error 180 181 if ok := errors.As(err, &couchErr); !ok { 182 return false 183 } 184 185 return couchErr.Name == "no_usable_index" 186 } 187 188 func isIndexError(err error) bool { 189 var couchErr *Error 190 191 if ok := errors.As(err, &couchErr); !ok { 192 return false 193 } 194 195 return strings.Contains(couchErr.Reason, "mango_idx") 196 } 197 198 func isBadArgError(err error) bool { 199 var couchErr *Error 200 201 if ok := errors.As(err, &couchErr); !ok { 202 return false 203 } 204 205 return strings.Contains(couchErr.Reason, "badarg") 206 } 207 208 func newRequestError(originalError error) error { 209 return &Error{ 210 StatusCode: http.StatusServiceUnavailable, 211 Name: "no_couch", 212 Reason: "could not create a request to the server", 213 Original: cleanURLError(originalError), 214 } 215 } 216 217 func newConnectionError(originalError error) error { 218 return &Error{ 219 StatusCode: http.StatusServiceUnavailable, 220 Name: "no_couch", 221 Reason: "could not create connection with the server", 222 Original: cleanURLError(originalError), 223 } 224 } 225 226 func newIOReadError(originalError error) error { 227 return &Error{ 228 StatusCode: http.StatusServiceUnavailable, 229 Name: "no_couch", 230 Reason: "could not read data from the server", 231 Original: cleanURLError(originalError), 232 } 233 } 234 235 func newDefinedIDError() error { 236 return &Error{ 237 StatusCode: http.StatusBadRequest, 238 Name: "defined_id", 239 Reason: "document _id should be empty", 240 } 241 } 242 243 func newBadIDError(id string) error { 244 return &Error{ 245 StatusCode: http.StatusBadRequest, 246 Name: "bad_id", 247 Reason: fmt.Sprintf("Unsuported couchdb operation %s", id), 248 } 249 } 250 251 func unoptimalError() error { 252 return &Error{ 253 StatusCode: http.StatusBadRequest, 254 Name: "no_index", 255 Reason: "no matching index found, create an index", 256 } 257 } 258 259 func newCouchdbError(statusCode int, couchdbJSON []byte) error { 260 err := &Error{ 261 CouchdbJSON: couchdbJSON, 262 } 263 parseErr := json.Unmarshal(couchdbJSON, err) 264 if parseErr != nil { 265 err.Name = "wrong_json" 266 err.Reason = parseErr.Error() 267 } 268 err.StatusCode = statusCode 269 return err 270 } 271 272 func cleanURLError(e error) error { 273 if erru, ok := e.(*url.Error); ok { 274 u, err := url.Parse(erru.URL) 275 if err != nil { 276 return erru 277 } 278 if u.User == nil { 279 return erru 280 } 281 u.User = nil 282 return &url.Error{ 283 Op: erru.Op, 284 URL: u.String(), 285 Err: erru.Err, 286 } 287 } 288 return e 289 }