withcode.go (6495B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package gtserror 19 20 import ( 21 "errors" 22 "net/http" 23 "strings" 24 ) 25 26 // Custom http response codes + text that 27 // aren't included in the net/http package. 28 const ( 29 StatusClientClosedRequest = 499 30 StatusTextClientClosedRequest = "Client Closed Request" 31 ) 32 33 // WithCode wraps an internal error with an http code, and a 'safe' version of 34 // the error that can be served to clients without revealing internal business logic. 35 // 36 // A typical use of this error would be to first log the Original error, then return 37 // the Safe error and the StatusCode to an API caller. 38 type WithCode interface { 39 // Unwrap returns the original error. 40 // This should *NEVER* be returned to a client as it may contain sensitive information. 41 Unwrap() error 42 // Error serializes the original internal error for debugging within the GoToSocial logs. 43 // This should *NEVER* be returned to a client as it may contain sensitive information. 44 Error() string 45 // Safe returns the API-safe version of the error for serialization towards a client. 46 // There's not much point logging this internally because it won't contain much helpful information. 47 Safe() string 48 // Code returns the status code for serving to a client. 49 Code() int 50 } 51 52 type withCode struct { 53 original error 54 safe error 55 code int 56 } 57 58 func (e withCode) Unwrap() error { 59 return e.original 60 } 61 62 func (e withCode) Error() string { 63 return e.original.Error() 64 } 65 66 func (e withCode) Safe() string { 67 return e.safe.Error() 68 } 69 70 func (e withCode) Code() int { 71 return e.code 72 } 73 74 // NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text. 75 func NewErrorBadRequest(original error, helpText ...string) WithCode { 76 safe := http.StatusText(http.StatusBadRequest) 77 if helpText != nil { 78 safe = safe + ": " + strings.Join(helpText, ": ") 79 } 80 return withCode{ 81 original: original, 82 safe: errors.New(safe), 83 code: http.StatusBadRequest, 84 } 85 } 86 87 // NewErrorUnauthorized returns an ErrorWithCode 401 with the given original error and optional help text. 88 func NewErrorUnauthorized(original error, helpText ...string) WithCode { 89 safe := http.StatusText(http.StatusUnauthorized) 90 if helpText != nil { 91 safe = safe + ": " + strings.Join(helpText, ": ") 92 } 93 return withCode{ 94 original: original, 95 safe: errors.New(safe), 96 code: http.StatusUnauthorized, 97 } 98 } 99 100 // NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text. 101 func NewErrorForbidden(original error, helpText ...string) WithCode { 102 safe := http.StatusText(http.StatusForbidden) 103 if helpText != nil { 104 safe = safe + ": " + strings.Join(helpText, ": ") 105 } 106 return withCode{ 107 original: original, 108 safe: errors.New(safe), 109 code: http.StatusForbidden, 110 } 111 } 112 113 // NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text. 114 func NewErrorNotFound(original error, helpText ...string) WithCode { 115 safe := http.StatusText(http.StatusNotFound) 116 if helpText != nil { 117 safe = safe + ": " + strings.Join(helpText, ": ") 118 } 119 return withCode{ 120 original: original, 121 safe: errors.New(safe), 122 code: http.StatusNotFound, 123 } 124 } 125 126 // NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text. 127 func NewErrorInternalError(original error, helpText ...string) WithCode { 128 safe := http.StatusText(http.StatusInternalServerError) 129 if helpText != nil { 130 safe = safe + ": " + strings.Join(helpText, ": ") 131 } 132 return withCode{ 133 original: original, 134 safe: errors.New(safe), 135 code: http.StatusInternalServerError, 136 } 137 } 138 139 // NewErrorConflict returns an ErrorWithCode 409 with the given original error and optional help text. 140 func NewErrorConflict(original error, helpText ...string) WithCode { 141 safe := http.StatusText(http.StatusConflict) 142 if helpText != nil { 143 safe = safe + ": " + strings.Join(helpText, ": ") 144 } 145 return withCode{ 146 original: original, 147 safe: errors.New(safe), 148 code: http.StatusConflict, 149 } 150 } 151 152 // NewErrorNotAcceptable returns an ErrorWithCode 406 with the given original error and optional help text. 153 func NewErrorNotAcceptable(original error, helpText ...string) WithCode { 154 safe := http.StatusText(http.StatusNotAcceptable) 155 if helpText != nil { 156 safe = safe + ": " + strings.Join(helpText, ": ") 157 } 158 return withCode{ 159 original: original, 160 safe: errors.New(safe), 161 code: http.StatusNotAcceptable, 162 } 163 } 164 165 // NewErrorUnprocessableEntity returns an ErrorWithCode 422 with the given original error and optional help text. 166 func NewErrorUnprocessableEntity(original error, helpText ...string) WithCode { 167 safe := http.StatusText(http.StatusUnprocessableEntity) 168 if helpText != nil { 169 safe = safe + ": " + strings.Join(helpText, ": ") 170 } 171 return withCode{ 172 original: original, 173 safe: errors.New(safe), 174 code: http.StatusUnprocessableEntity, 175 } 176 } 177 178 // NewErrorGone returns an ErrorWithCode 410 with the given original error and optional help text. 179 func NewErrorGone(original error, helpText ...string) WithCode { 180 safe := http.StatusText(http.StatusGone) 181 if helpText != nil { 182 safe = safe + ": " + strings.Join(helpText, ": ") 183 } 184 return withCode{ 185 original: original, 186 safe: errors.New(safe), 187 code: http.StatusGone, 188 } 189 } 190 191 // NewErrorClientClosedRequest returns an ErrorWithCode 499 with the given original error. 192 // This error type should only be used when an http caller has already hung up their request. 193 // See: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#nginx 194 func NewErrorClientClosedRequest(original error) WithCode { 195 return withCode{ 196 original: original, 197 safe: errors.New(StatusTextClientClosedRequest), 198 code: StatusClientClosedRequest, 199 } 200 }