github.com/go-kivik/kivik/v4@v4.3.2/int/errors/errors.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 // Package errors provides some internal error types and utilities. 14 package errors 15 16 import ( 17 "errors" 18 "fmt" 19 "net/http" 20 "regexp" 21 "strconv" 22 "strings" 23 ) 24 25 // CompositeError represents an HTTP status, encoded in the first byte as the 26 // status - 400, plus the error message. 27 type CompositeError string 28 29 func (c CompositeError) Error() string { 30 return "kivik: " + string(c[4:]) 31 } 32 33 // HTTPStatus returns c's HTTP status code. 34 func (c CompositeError) HTTPStatus() int { 35 i, _ := strconv.Atoi(string(c[:3])) 36 return i 37 } 38 39 // Error represents an error returned by Kivik. 40 // 41 // This type definition is not guaranteed to remain stable, or even exported. 42 // When examining errors programmatically, you should rely instead on the 43 // HTTPStatus() function in this package, rather than on directly observing 44 // the fields of this type. 45 type Error struct { 46 // Status is the HTTP status code associated with this error. Normally 47 // this is the actual HTTP status returned by the server, but in some cases 48 // it may be generated by Kivik directly. 49 Status int 50 51 // Message is the error message. 52 Message string 53 54 // Err is the originating error, if any. 55 Err error 56 } 57 58 var ( 59 _ error = &Error{} 60 _ statusCoder = &Error{} 61 ) 62 63 func (e *Error) Error() string { 64 if e.Err == nil { 65 return e.msg() 66 } 67 if e.Message == "" { 68 return e.Err.Error() 69 } 70 return e.Message + ": " + e.Err.Error() 71 } 72 73 // HTTPStatus returns the HTTP status code associated with the error, or 500 74 // (internal server error), if none. 75 func (e *Error) HTTPStatus() int { 76 if e.Status == 0 { 77 return http.StatusInternalServerError 78 } 79 return e.Status 80 } 81 82 // Unwrap satisfies the errors wrapper interface. 83 func (e *Error) Unwrap() error { 84 return e.Err 85 } 86 87 // Format implements [fmt.Formatter]. 88 func (e *Error) Format(f fmt.State, c rune) { 89 const partsLen = 3 90 parts := make([]string, 0, partsLen) 91 if e.Message != "" { 92 parts = append(parts, e.Message) 93 } 94 if c == 'v' { 95 if f.Flag('+') { 96 parts = append(parts, fmt.Sprintf("%d / %s", e.Status, http.StatusText(e.Status))) 97 } 98 } 99 if e.Err != nil { 100 parts = append(parts, e.Err.Error()) 101 } 102 _, _ = fmt.Fprint(f, strings.Join(parts, ": ")) 103 } 104 105 func (e *Error) msg() string { 106 switch e.Message { 107 case "": 108 return http.StatusText(e.HTTPStatus()) 109 default: 110 return e.Message 111 } 112 } 113 114 type statusCoder interface { 115 HTTPStatus() int 116 } 117 118 // HTTPStatus returns the HTTP status code embedded in the error, or 500 119 // (internal server error), if there was no specified status code. If err is 120 // nil, HTTPStatus returns 0. 121 func HTTPStatus(err error) int { 122 if err == nil { 123 return 0 124 } 125 var coder statusCoder 126 for { 127 if errors.As(err, &coder) { 128 return coder.HTTPStatus() 129 } 130 if uw := errors.Unwrap(err); uw != nil { 131 err = uw 132 continue 133 } 134 return http.StatusInternalServerError 135 } 136 } 137 138 // StatusErrorDiff returns the empty string if the expected error string and 139 // status match err. Otherwise, it returns a description of the mismatch. 140 func StatusErrorDiff(wantErr string, wantStatus int, err error) string { 141 var ( 142 msg string 143 status int 144 ) 145 if err != nil { 146 status = HTTPStatus(err) 147 msg = err.Error() 148 } 149 if msg != wantErr || status != wantStatus { 150 return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])", 151 err, status, wantErr, wantStatus) 152 } 153 return "" 154 } 155 156 // StatusErrorDiffRE returns the empty string if the expected error RE and 157 // status match err. Otherwise, it returns a description of the mismatch. 158 func StatusErrorDiffRE(wantErrRE string, wantStatus int, err error) string { 159 re := regexp.MustCompile(wantErrRE) 160 var ( 161 msg string 162 status int 163 ) 164 if err != nil { 165 status = HTTPStatus(err) 166 msg = err.Error() 167 } 168 if !re.MatchString(msg) || status != wantStatus { 169 return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])", 170 err, status, re, wantStatus) 171 } 172 return "" 173 }