├── .github └── workflows │ └── gotest.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── checkers_test.go ├── doc.go ├── error.go ├── error_test.go ├── errortypes.go ├── errortypes_test.go ├── example_test.go ├── functions.go ├── functions_test.go ├── go.mod ├── go.sum └── package_test.go /.github/workflows/gotest.yml: -------------------------------------------------------------------------------- 1 | name: Run `go test` 2 | on: [push, pull_request, workflow_dispatch] 3 | jobs: 4 | run-go-test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout repo 8 | uses: actions/checkout@v3 9 | - name: Find required go version 10 | id: go-version 11 | run: | 12 | set -euxo pipefail 13 | echo "::set-output name=version::$(grep '^go ' go.mod | awk '{print $2}')" 14 | - name: Install Golang 15 | uses: actions/setup-go@v2 16 | with: 17 | # Gets go version from the previous step 18 | go-version: ${{ steps.go-version.outputs.version }} 19 | - name: Run test suite 20 | run: go test -v 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All files in this repository are licensed as follows. If you contribute 2 | to this repository, it is assumed that you license your contribution 3 | under the same license unless you state otherwise. 4 | 5 | All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. 6 | 7 | This software is licensed under the LGPLv3, included below. 8 | 9 | As a special exception to the GNU Lesser General Public License version 3 10 | ("LGPL3"), the copyright holders of this Library give you permission to 11 | convey to a third party a Combined Work that links statically or dynamically 12 | to this Library without providing any Minimal Corresponding Source or 13 | Minimal Application Code as set out in 4d or providing the installation 14 | information set out in section 4e, provided that you comply with the other 15 | provisions of LGPL3 and provided that you meet, for the Application the 16 | terms and conditions of the license(s) which apply to the Application. 17 | 18 | Except as stated in this special exception, the provisions of LGPL3 will 19 | continue to comply in full to this Library. If you modify this Library, you 20 | may apply this exception to your version of this Library, but you are not 21 | obliged to do so. If you do not wish to do so, delete this exception 22 | statement from your version. This exception does not (and cannot) modify any 23 | license terms which apply to the Application, with which you must still 24 | comply. 25 | 26 | 27 | GNU LESSER GENERAL PUBLIC LICENSE 28 | Version 3, 29 June 2007 29 | 30 | Copyright (C) 2007 Free Software Foundation, Inc. 31 | Everyone is permitted to copy and distribute verbatim copies 32 | of this license document, but changing it is not allowed. 33 | 34 | 35 | This version of the GNU Lesser General Public License incorporates 36 | the terms and conditions of version 3 of the GNU General Public 37 | License, supplemented by the additional permissions listed below. 38 | 39 | 0. Additional Definitions. 40 | 41 | As used herein, "this License" refers to version 3 of the GNU Lesser 42 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 43 | General Public License. 44 | 45 | "The Library" refers to a covered work governed by this License, 46 | other than an Application or a Combined Work as defined below. 47 | 48 | An "Application" is any work that makes use of an interface provided 49 | by the Library, but which is not otherwise based on the Library. 50 | Defining a subclass of a class defined by the Library is deemed a mode 51 | of using an interface provided by the Library. 52 | 53 | A "Combined Work" is a work produced by combining or linking an 54 | Application with the Library. The particular version of the Library 55 | with which the Combined Work was made is also called the "Linked 56 | Version". 57 | 58 | The "Minimal Corresponding Source" for a Combined Work means the 59 | Corresponding Source for the Combined Work, excluding any source code 60 | for portions of the Combined Work that, considered in isolation, are 61 | based on the Application, and not on the Linked Version. 62 | 63 | The "Corresponding Application Code" for a Combined Work means the 64 | object code and/or source code for the Application, including any data 65 | and utility programs needed for reproducing the Combined Work from the 66 | Application, but excluding the System Libraries of the Combined Work. 67 | 68 | 1. Exception to Section 3 of the GNU GPL. 69 | 70 | You may convey a covered work under sections 3 and 4 of this License 71 | without being bound by section 3 of the GNU GPL. 72 | 73 | 2. Conveying Modified Versions. 74 | 75 | If you modify a copy of the Library, and, in your modifications, a 76 | facility refers to a function or data to be supplied by an Application 77 | that uses the facility (other than as an argument passed when the 78 | facility is invoked), then you may convey a copy of the modified 79 | version: 80 | 81 | a) under this License, provided that you make a good faith effort to 82 | ensure that, in the event an Application does not supply the 83 | function or data, the facility still operates, and performs 84 | whatever part of its purpose remains meaningful, or 85 | 86 | b) under the GNU GPL, with none of the additional permissions of 87 | this License applicable to that copy. 88 | 89 | 3. Object Code Incorporating Material from Library Header Files. 90 | 91 | The object code form of an Application may incorporate material from 92 | a header file that is part of the Library. You may convey such object 93 | code under terms of your choice, provided that, if the incorporated 94 | material is not limited to numerical parameters, data structure 95 | layouts and accessors, or small macros, inline functions and templates 96 | (ten or fewer lines in length), you do both of the following: 97 | 98 | a) Give prominent notice with each copy of the object code that the 99 | Library is used in it and that the Library and its use are 100 | covered by this License. 101 | 102 | b) Accompany the object code with a copy of the GNU GPL and this license 103 | document. 104 | 105 | 4. Combined Works. 106 | 107 | You may convey a Combined Work under terms of your choice that, 108 | taken together, effectively do not restrict modification of the 109 | portions of the Library contained in the Combined Work and reverse 110 | engineering for debugging such modifications, if you also do each of 111 | the following: 112 | 113 | a) Give prominent notice with each copy of the Combined Work that 114 | the Library is used in it and that the Library and its use are 115 | covered by this License. 116 | 117 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 118 | document. 119 | 120 | c) For a Combined Work that displays copyright notices during 121 | execution, include the copyright notice for the Library among 122 | these notices, as well as a reference directing the user to the 123 | copies of the GNU GPL and this license document. 124 | 125 | d) Do one of the following: 126 | 127 | 0) Convey the Minimal Corresponding Source under the terms of this 128 | License, and the Corresponding Application Code in a form 129 | suitable for, and under terms that permit, the user to 130 | recombine or relink the Application with a modified version of 131 | the Linked Version to produce a modified Combined Work, in the 132 | manner specified by section 6 of the GNU GPL for conveying 133 | Corresponding Source. 134 | 135 | 1) Use a suitable shared library mechanism for linking with the 136 | Library. A suitable mechanism is one that (a) uses at run time 137 | a copy of the Library already present on the user's computer 138 | system, and (b) will operate properly with a modified version 139 | of the Library that is interface-compatible with the Linked 140 | Version. 141 | 142 | e) Provide Installation Information, but only if you would otherwise 143 | be required to provide such information under section 6 of the 144 | GNU GPL, and only to the extent that such information is 145 | necessary to install and execute a modified version of the 146 | Combined Work produced by recombining or relinking the 147 | Application with a modified version of the Linked Version. (If 148 | you use option 4d0, the Installation Information must accompany 149 | the Minimal Corresponding Source and Corresponding Application 150 | Code. If you use option 4d1, you must provide the Installation 151 | Information in the manner specified by section 6 of the GNU GPL 152 | for conveying Corresponding Source.) 153 | 154 | 5. Combined Libraries. 155 | 156 | You may place library facilities that are a work based on the 157 | Library side by side in a single library together with other library 158 | facilities that are not Applications and are not covered by this 159 | License, and convey such a combined library under terms of your 160 | choice, if you do both of the following: 161 | 162 | a) Accompany the combined library with a copy of the same work based 163 | on the Library, uncombined with any other library facilities, 164 | conveyed under the terms of this License. 165 | 166 | b) Give prominent notice with the combined library that part of it 167 | is a work based on the Library, and explaining where to find the 168 | accompanying uncombined form of the same work. 169 | 170 | 6. Revised Versions of the GNU Lesser General Public License. 171 | 172 | The Free Software Foundation may publish revised and/or new versions 173 | of the GNU Lesser General Public License from time to time. Such new 174 | versions will be similar in spirit to the present version, but may 175 | differ in detail to address new problems or concerns. 176 | 177 | Each version is given a distinguishing version number. If the 178 | Library as you received it specifies that a certain numbered version 179 | of the GNU Lesser General Public License "or any later version" 180 | applies to it, you have the option of following the terms and 181 | conditions either of that published version or of any later version 182 | published by the Free Software Foundation. If the Library as you 183 | received it does not specify a version number of the GNU Lesser 184 | General Public License, you may choose any version of the GNU Lesser 185 | General Public License ever published by the Free Software Foundation. 186 | 187 | If the Library as you received it specifies that a proxy can decide 188 | whether future versions of the GNU Lesser General Public License shall 189 | apply, that proxy's public statement of acceptance of any version is 190 | permanent authorization for you to choose that version for the 191 | Library. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := github.com/juju/errors 2 | 3 | .PHONY: check-licence check-go check docs 4 | 5 | check: check-licence check-go 6 | go test $(PROJECT)/... 7 | 8 | check-licence: 9 | @(fgrep -rl "Licensed under the LGPLv3" --exclude *.s .;\ 10 | fgrep -rl "MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT" --exclude *.s .;\ 11 | find . -name "*.go") | sed -e 's,\./,,' | sort | uniq -u | \ 12 | xargs -I {} echo FAIL: licence missed: {} 13 | 14 | check-go: 15 | $(eval GOFMT := $(strip $(shell gofmt -l .| sed -e "s/^/ /g"))) 16 | @(if [ x$(GOFMT) != x"" ]; then \ 17 | echo go fmt is sad: $(GOFMT); \ 18 | exit 1; \ 19 | fi ) 20 | @(go vet -all -composites=false -copylocks=false .) 21 | 22 | docs: 23 | godoc2md github.com/juju/errors > README.md 24 | sed -i '5i[\[GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)' README.md 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # errors 3 | import "github.com/juju/errors" 4 | 5 | [![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors) 6 | 7 | The juju/errors provides an easy way to annotate errors without losing the 8 | original error context. 9 | 10 | The exported `New` and `Errorf` functions are designed to replace the 11 | `errors.New` and `fmt.Errorf` functions respectively. The same underlying 12 | error is there, but the package also records the location at which the error 13 | was created. 14 | 15 | A primary use case for this library is to add extra context any time an 16 | error is returned from a function. 17 | 18 | 19 | if err := SomeFunc(); err != nil { 20 | return err 21 | } 22 | 23 | This instead becomes: 24 | 25 | 26 | if err := SomeFunc(); err != nil { 27 | return errors.Trace(err) 28 | } 29 | 30 | which just records the file and line number of the Trace call, or 31 | 32 | 33 | if err := SomeFunc(); err != nil { 34 | return errors.Annotate(err, "more context") 35 | } 36 | 37 | which also adds an annotation to the error. 38 | 39 | When you want to check to see if an error is of a particular type, a helper 40 | function is normally exported by the package that returned the error, like the 41 | `os` package does. The underlying cause of the error is available using the 42 | `Cause` function. 43 | 44 | 45 | os.IsNotExist(errors.Cause(err)) 46 | 47 | The result of the `Error()` call on an annotated error is the annotations joined 48 | with colons, then the result of the `Error()` method for the underlying error 49 | that was the cause. 50 | 51 | 52 | err := errors.Errorf("original") 53 | err = errors.Annotatef(err, "context") 54 | err = errors.Annotatef(err, "more context") 55 | err.Error() -> "more context: context: original" 56 | 57 | Obviously recording the file, line and functions is not very useful if you 58 | cannot get them back out again. 59 | 60 | 61 | errors.ErrorStack(err) 62 | 63 | will return something like: 64 | 65 | 66 | first error 67 | github.com/juju/errors/annotation_test.go:193: 68 | github.com/juju/errors/annotation_test.go:194: annotation 69 | github.com/juju/errors/annotation_test.go:195: 70 | github.com/juju/errors/annotation_test.go:196: more context 71 | github.com/juju/errors/annotation_test.go:197: 72 | 73 | The first error was generated by an external system, so there was no location 74 | associated. The second, fourth, and last lines were generated with Trace calls, 75 | and the other two through Annotate. 76 | 77 | Sometimes when responding to an error you want to return a more specific error 78 | for the situation. 79 | 80 | 81 | if err := FindField(field); err != nil { 82 | return errors.Wrap(err, errors.NotFoundf(field)) 83 | } 84 | 85 | This returns an error where the complete error stack is still available, and 86 | `errors.Cause()` will return the `NotFound` error. 87 | 88 | 89 | 90 | 91 | 92 | 93 | ## func AlreadyExistsf 94 | ``` go 95 | func AlreadyExistsf(format string, args ...interface{}) error 96 | ``` 97 | AlreadyExistsf returns an error which satisfies IsAlreadyExists(). 98 | 99 | 100 | ## func Annotate 101 | ``` go 102 | func Annotate(other error, message string) error 103 | ``` 104 | Annotate is used to add extra context to an existing error. The location of 105 | the Annotate call is recorded with the annotations. The file, line and 106 | function are also recorded. 107 | 108 | For example: 109 | 110 | 111 | if err := SomeFunc(); err != nil { 112 | return errors.Annotate(err, "failed to frombulate") 113 | } 114 | 115 | 116 | ## func Annotatef 117 | ``` go 118 | func Annotatef(other error, format string, args ...interface{}) error 119 | ``` 120 | Annotatef is used to add extra context to an existing error. The location of 121 | the Annotate call is recorded with the annotations. The file, line and 122 | function are also recorded. 123 | 124 | For example: 125 | 126 | 127 | if err := SomeFunc(); err != nil { 128 | return errors.Annotatef(err, "failed to frombulate the %s", arg) 129 | } 130 | 131 | 132 | ## func BadRequestf 133 | ``` go 134 | func BadRequestf(format string, args ...interface{}) error 135 | ``` 136 | BadRequestf returns an error which satisfies IsBadRequest(). 137 | 138 | 139 | ## func Cause 140 | ``` go 141 | func Cause(err error) error 142 | ``` 143 | Cause returns the cause of the given error. This will be either the 144 | original error, or the result of a Wrap or Mask call. 145 | 146 | Cause is the usual way to diagnose errors that may have been wrapped by 147 | the other errors functions. 148 | 149 | 150 | ## func DeferredAnnotatef 151 | ``` go 152 | func DeferredAnnotatef(err *error, format string, args ...interface{}) 153 | ``` 154 | DeferredAnnotatef annotates the given error (when it is not nil) with the given 155 | format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef 156 | does nothing. This method is used in a defer statement in order to annotate any 157 | resulting error with the same message. 158 | 159 | For example: 160 | 161 | 162 | defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) 163 | 164 | 165 | ## func Details 166 | ``` go 167 | func Details(err error) string 168 | ``` 169 | Details returns information about the stack of errors wrapped by err, in 170 | the format: 171 | 172 | 173 | [{filename:99: error one} {otherfile:55: cause of error one}] 174 | 175 | This is a terse alternative to ErrorStack as it returns a single line. 176 | 177 | 178 | ## func ErrorStack 179 | ``` go 180 | func ErrorStack(err error) string 181 | ``` 182 | ErrorStack returns a string representation of the annotated error. If the 183 | error passed as the parameter is not an annotated error, the result is 184 | simply the result of the Error() method on that error. 185 | 186 | If the error is an annotated error, a multi-line string is returned where 187 | each line represents one entry in the annotation stack. The full filename 188 | from the call stack is used in the output. 189 | 190 | 191 | first error 192 | github.com/juju/errors/annotation_test.go:193: 193 | github.com/juju/errors/annotation_test.go:194: annotation 194 | github.com/juju/errors/annotation_test.go:195: 195 | github.com/juju/errors/annotation_test.go:196: more context 196 | github.com/juju/errors/annotation_test.go:197: 197 | 198 | 199 | ## func Errorf 200 | ``` go 201 | func Errorf(format string, args ...interface{}) error 202 | ``` 203 | Errorf creates a new annotated error and records the location that the 204 | error is created. This should be a drop in replacement for fmt.Errorf. 205 | 206 | For example: 207 | 208 | 209 | return errors.Errorf("validation failed: %s", message) 210 | 211 | 212 | ## func Forbiddenf 213 | ``` go 214 | func Forbiddenf(format string, args ...interface{}) error 215 | ``` 216 | Forbiddenf returns an error which satistifes IsForbidden() 217 | 218 | 219 | ## func IsAlreadyExists 220 | ``` go 221 | func IsAlreadyExists(err error) bool 222 | ``` 223 | IsAlreadyExists reports whether the error was created with 224 | AlreadyExistsf() or NewAlreadyExists(). 225 | 226 | 227 | ## func IsBadRequest 228 | ``` go 229 | func IsBadRequest(err error) bool 230 | ``` 231 | IsBadRequest reports whether err was created with BadRequestf() or 232 | NewBadRequest(). 233 | 234 | 235 | ## func IsForbidden 236 | ``` go 237 | func IsForbidden(err error) bool 238 | ``` 239 | IsForbidden reports whether err was created with Forbiddenf() or 240 | NewForbidden(). 241 | 242 | 243 | ## func IsMethodNotAllowed 244 | ``` go 245 | func IsMethodNotAllowed(err error) bool 246 | ``` 247 | IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or 248 | NewMethodNotAllowed(). 249 | 250 | 251 | ## func IsNotAssigned 252 | ``` go 253 | func IsNotAssigned(err error) bool 254 | ``` 255 | IsNotAssigned reports whether err was created with NotAssignedf() or 256 | NewNotAssigned(). 257 | 258 | 259 | ## func IsNotFound 260 | ``` go 261 | func IsNotFound(err error) bool 262 | ``` 263 | IsNotFound reports whether err was created with NotFoundf() or 264 | NewNotFound(). 265 | 266 | 267 | ## func IsNotImplemented 268 | ``` go 269 | func IsNotImplemented(err error) bool 270 | ``` 271 | IsNotImplemented reports whether err was created with 272 | NotImplementedf() or NewNotImplemented(). 273 | 274 | 275 | ## func IsNotProvisioned 276 | ``` go 277 | func IsNotProvisioned(err error) bool 278 | ``` 279 | IsNotProvisioned reports whether err was created with NotProvisionedf() or 280 | NewNotProvisioned(). 281 | 282 | 283 | ## func IsNotSupported 284 | ``` go 285 | func IsNotSupported(err error) bool 286 | ``` 287 | IsNotSupported reports whether the error was created with 288 | NotSupportedf() or NewNotSupported(). 289 | 290 | 291 | ## func IsNotValid 292 | ``` go 293 | func IsNotValid(err error) bool 294 | ``` 295 | IsNotValid reports whether the error was created with NotValidf() or 296 | NewNotValid(). 297 | 298 | 299 | ## func IsUnauthorized 300 | ``` go 301 | func IsUnauthorized(err error) bool 302 | ``` 303 | IsUnauthorized reports whether err was created with Unauthorizedf() or 304 | NewUnauthorized(). 305 | 306 | 307 | ## func IsUserNotFound 308 | ``` go 309 | func IsUserNotFound(err error) bool 310 | ``` 311 | IsUserNotFound reports whether err was created with UserNotFoundf() or 312 | NewUserNotFound(). 313 | 314 | 315 | ## func Mask 316 | ``` go 317 | func Mask(other error) error 318 | ``` 319 | Mask hides the underlying error type, and records the location of the masking. 320 | 321 | 322 | ## func Maskf 323 | ``` go 324 | func Maskf(other error, format string, args ...interface{}) error 325 | ``` 326 | Mask masks the given error with the given format string and arguments (like 327 | fmt.Sprintf), returning a new error that maintains the error stack, but 328 | hides the underlying error type. The error string still contains the full 329 | annotations. If you want to hide the annotations, call Wrap. 330 | 331 | 332 | ## func MethodNotAllowedf 333 | ``` go 334 | func MethodNotAllowedf(format string, args ...interface{}) error 335 | ``` 336 | MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed(). 337 | 338 | 339 | ## func New 340 | ``` go 341 | func New(message string) error 342 | ``` 343 | New is a drop in replacement for the standard library errors module that records 344 | the location that the error is created. 345 | 346 | For example: 347 | 348 | 349 | return errors.New("validation failed") 350 | 351 | 352 | ## func NewAlreadyExists 353 | ``` go 354 | func NewAlreadyExists(err error, msg string) error 355 | ``` 356 | NewAlreadyExists returns an error which wraps err and satisfies 357 | IsAlreadyExists(). 358 | 359 | 360 | ## func NewBadRequest 361 | ``` go 362 | func NewBadRequest(err error, msg string) error 363 | ``` 364 | NewBadRequest returns an error which wraps err that satisfies 365 | IsBadRequest(). 366 | 367 | 368 | ## func NewForbidden 369 | ``` go 370 | func NewForbidden(err error, msg string) error 371 | ``` 372 | NewForbidden returns an error which wraps err that satisfies 373 | IsForbidden(). 374 | 375 | 376 | ## func NewMethodNotAllowed 377 | ``` go 378 | func NewMethodNotAllowed(err error, msg string) error 379 | ``` 380 | NewMethodNotAllowed returns an error which wraps err that satisfies 381 | IsMethodNotAllowed(). 382 | 383 | 384 | ## func NewNotAssigned 385 | ``` go 386 | func NewNotAssigned(err error, msg string) error 387 | ``` 388 | NewNotAssigned returns an error which wraps err that satisfies 389 | IsNotAssigned(). 390 | 391 | 392 | ## func NewNotFound 393 | ``` go 394 | func NewNotFound(err error, msg string) error 395 | ``` 396 | NewNotFound returns an error which wraps err that satisfies 397 | IsNotFound(). 398 | 399 | 400 | ## func NewNotImplemented 401 | ``` go 402 | func NewNotImplemented(err error, msg string) error 403 | ``` 404 | NewNotImplemented returns an error which wraps err and satisfies 405 | IsNotImplemented(). 406 | 407 | 408 | ## func NewNotProvisioned 409 | ``` go 410 | func NewNotProvisioned(err error, msg string) error 411 | ``` 412 | NewNotProvisioned returns an error which wraps err that satisfies 413 | IsNotProvisioned(). 414 | 415 | 416 | ## func NewNotSupported 417 | ``` go 418 | func NewNotSupported(err error, msg string) error 419 | ``` 420 | NewNotSupported returns an error which wraps err and satisfies 421 | IsNotSupported(). 422 | 423 | 424 | ## func NewNotValid 425 | ``` go 426 | func NewNotValid(err error, msg string) error 427 | ``` 428 | NewNotValid returns an error which wraps err and satisfies IsNotValid(). 429 | 430 | 431 | ## func NewUnauthorized 432 | ``` go 433 | func NewUnauthorized(err error, msg string) error 434 | ``` 435 | NewUnauthorized returns an error which wraps err and satisfies 436 | IsUnauthorized(). 437 | 438 | 439 | ## func NewUserNotFound 440 | ``` go 441 | func NewUserNotFound(err error, msg string) error 442 | ``` 443 | NewUserNotFound returns an error which wraps err and satisfies 444 | IsUserNotFound(). 445 | 446 | 447 | ## func NotAssignedf 448 | ``` go 449 | func NotAssignedf(format string, args ...interface{}) error 450 | ``` 451 | NotAssignedf returns an error which satisfies IsNotAssigned(). 452 | 453 | 454 | ## func NotFoundf 455 | ``` go 456 | func NotFoundf(format string, args ...interface{}) error 457 | ``` 458 | NotFoundf returns an error which satisfies IsNotFound(). 459 | 460 | 461 | ## func NotImplementedf 462 | ``` go 463 | func NotImplementedf(format string, args ...interface{}) error 464 | ``` 465 | NotImplementedf returns an error which satisfies IsNotImplemented(). 466 | 467 | 468 | ## func NotProvisionedf 469 | ``` go 470 | func NotProvisionedf(format string, args ...interface{}) error 471 | ``` 472 | NotProvisionedf returns an error which satisfies IsNotProvisioned(). 473 | 474 | 475 | ## func NotSupportedf 476 | ``` go 477 | func NotSupportedf(format string, args ...interface{}) error 478 | ``` 479 | NotSupportedf returns an error which satisfies IsNotSupported(). 480 | 481 | 482 | ## func NotValidf 483 | ``` go 484 | func NotValidf(format string, args ...interface{}) error 485 | ``` 486 | NotValidf returns an error which satisfies IsNotValid(). 487 | 488 | 489 | ## func Trace 490 | ``` go 491 | func Trace(other error) error 492 | ``` 493 | Trace adds the location of the Trace call to the stack. The Cause of the 494 | resulting error is the same as the error parameter. If the other error is 495 | nil, the result will be nil. 496 | 497 | For example: 498 | 499 | 500 | if err := SomeFunc(); err != nil { 501 | return errors.Trace(err) 502 | } 503 | 504 | 505 | ## func Unauthorizedf 506 | ``` go 507 | func Unauthorizedf(format string, args ...interface{}) error 508 | ``` 509 | Unauthorizedf returns an error which satisfies IsUnauthorized(). 510 | 511 | 512 | ## func UserNotFoundf 513 | ``` go 514 | func UserNotFoundf(format string, args ...interface{}) error 515 | ``` 516 | UserNotFoundf returns an error which satisfies IsUserNotFound(). 517 | 518 | 519 | ## func Wrap 520 | ``` go 521 | func Wrap(other, newDescriptive error) error 522 | ``` 523 | Wrap changes the Cause of the error. The location of the Wrap call is also 524 | stored in the error stack. 525 | 526 | For example: 527 | 528 | 529 | if err := SomeFunc(); err != nil { 530 | newErr := &packageError{"more context", private_value} 531 | return errors.Wrap(err, newErr) 532 | } 533 | 534 | 535 | ## func Wrapf 536 | ``` go 537 | func Wrapf(other, newDescriptive error, format string, args ...interface{}) error 538 | ``` 539 | Wrapf changes the Cause of the error, and adds an annotation. The location 540 | of the Wrap call is also stored in the error stack. 541 | 542 | For example: 543 | 544 | 545 | if err := SomeFunc(); err != nil { 546 | return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) 547 | } 548 | 549 | 550 | 551 | ## type Err 552 | ``` go 553 | type Err struct { 554 | // contains filtered or unexported fields 555 | } 556 | ``` 557 | Err holds a description of an error along with information about 558 | where the error was created. 559 | 560 | It may be embedded in custom error types to add extra information that 561 | this errors package can understand. 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | ### func NewErr 572 | ``` go 573 | func NewErr(format string, args ...interface{}) Err 574 | ``` 575 | NewErr is used to return an Err for the purpose of embedding in other 576 | structures. The location is not specified, and needs to be set with a call 577 | to SetLocation. 578 | 579 | For example: 580 | 581 | 582 | type FooError struct { 583 | errors.Err 584 | code int 585 | } 586 | 587 | func NewFooError(code int) error { 588 | err := &FooError{errors.NewErr("foo"), code} 589 | err.SetLocation(1) 590 | return err 591 | } 592 | 593 | 594 | ### func NewErrWithCause 595 | ``` go 596 | func NewErrWithCause(other error, format string, args ...interface{}) Err 597 | ``` 598 | NewErrWithCause is used to return an Err with cause by other error for the purpose of embedding in other 599 | structures. The location is not specified, and needs to be set with a call 600 | to SetLocation. 601 | 602 | For example: 603 | 604 | 605 | type FooError struct { 606 | errors.Err 607 | code int 608 | } 609 | 610 | func (e *FooError) Annotate(format string, args ...interface{}) error { 611 | err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code} 612 | err.SetLocation(1) 613 | return err 614 | }) 615 | 616 | 617 | 618 | 619 | ### func (\*Err) Cause 620 | ``` go 621 | func (e *Err) Cause() error 622 | ``` 623 | The Cause of an error is the most recent error in the error stack that 624 | meets one of these criteria: the original error that was raised; the new 625 | error that was passed into the Wrap function; the most recently masked 626 | error; or nil if the error itself is considered the Cause. Normally this 627 | method is not invoked directly, but instead through the Cause stand alone 628 | function. 629 | 630 | 631 | 632 | ### func (\*Err) Error 633 | ``` go 634 | func (e *Err) Error() string 635 | ``` 636 | Error implements error.Error. 637 | 638 | 639 | 640 | ### func (\*Err) Format 641 | ``` go 642 | func (e *Err) Format(s fmt.State, verb rune) 643 | ``` 644 | Format implements fmt.Formatter 645 | When printing errors with %+v it also prints the stack trace. 646 | %#v unsurprisingly will print the real underlying type. 647 | 648 | 649 | 650 | ### func (\*Err) Location 651 | ``` go 652 | func (e *Err) Location() (filename string, line int) 653 | ``` 654 | Location is the file and line of where the error was most recently 655 | created or annotated. 656 | 657 | 658 | 659 | ### func (\*Err) Message 660 | ``` go 661 | func (e *Err) Message() string 662 | ``` 663 | Message returns the message stored with the most recent location. This is 664 | the empty string if the most recent call was Trace, or the message stored 665 | with Annotate or Mask. 666 | 667 | 668 | 669 | ### func (\*Err) SetLocation 670 | ``` go 671 | func (e *Err) SetLocation(callDepth int) 672 | ``` 673 | SetLocation records the source location of the error at callDepth stack 674 | frames above the call. 675 | 676 | 677 | 678 | ### func (\*Err) StackTrace 679 | ``` go 680 | func (e *Err) StackTrace() []string 681 | ``` 682 | StackTrace returns one string for each location recorded in the stack of 683 | errors. The first value is the originating error, with a line for each 684 | other annotation or tracing of the error. 685 | 686 | 687 | 688 | ### func (\*Err) Underlying 689 | ``` go 690 | func (e *Err) Underlying() error 691 | ``` 692 | Underlying returns the previous error in the error stack, if any. A client 693 | should not ever really call this method. It is used to build the error 694 | stack and should not be introspected by client calls. Or more 695 | specifically, clients should not depend on anything but the `Cause` of an 696 | error. 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | - - - 707 | Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) 708 | -------------------------------------------------------------------------------- /checkers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | "strings" 10 | 11 | gc "gopkg.in/check.v1" 12 | ) 13 | 14 | // containsChecker is a copy of the containsChecker from juju/testing 15 | type containsChecker struct { 16 | *gc.CheckerInfo 17 | } 18 | 19 | // satisfiesChecker is a copy of the satisfiesChecker from juju/testing 20 | type satisfiesChecker struct { 21 | *gc.CheckerInfo 22 | } 23 | 24 | // Contains is a copy of the Contains checker from juju/testing 25 | var Contains gc.Checker = &containsChecker{ 26 | &gc.CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}}, 27 | } 28 | 29 | // Satisfies is a copy of the Satisfies checker from juju/testing 30 | var Satisfies gc.Checker = &satisfiesChecker{ 31 | &gc.CheckerInfo{ 32 | Name: "Satisfies", 33 | Params: []string{"obtained", "func(T) bool"}, 34 | }, 35 | } 36 | 37 | // canBeNil is copied from juju/testing 38 | func canBeNil(t reflect.Type) bool { 39 | switch t.Kind() { 40 | case reflect.Chan, 41 | reflect.Func, 42 | reflect.Interface, 43 | reflect.Map, 44 | reflect.Ptr, 45 | reflect.Slice: 46 | return true 47 | } 48 | return false 49 | } 50 | 51 | // Check is copied from juju/testing containsChecker 52 | func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, error string) { 53 | expected, ok := params[1].(string) 54 | if !ok { 55 | return false, "expected must be a string" 56 | } 57 | 58 | obtained, isString := stringOrStringer(params[0]) 59 | if isString { 60 | return strings.Contains(obtained, expected), "" 61 | } 62 | 63 | return false, "Obtained value is not a string and has no .String()" 64 | } 65 | 66 | // Check is copied from juju/testing satisfiesChecker 67 | func (checker *satisfiesChecker) Check(params []interface{}, names []string) (result bool, error string) { 68 | f := reflect.ValueOf(params[1]) 69 | ft := f.Type() 70 | if ft.Kind() != reflect.Func || 71 | ft.NumIn() != 1 || 72 | ft.NumOut() != 1 || 73 | ft.Out(0) != reflect.TypeOf(true) { 74 | return false, fmt.Sprintf("expected func(T) bool, got %s", ft) 75 | } 76 | v := reflect.ValueOf(params[0]) 77 | if !v.IsValid() { 78 | if !canBeNil(ft.In(0)) { 79 | return false, fmt.Sprintf("cannot assign nil to argument %T", ft.In(0)) 80 | } 81 | v = reflect.Zero(ft.In(0)) 82 | } 83 | if !v.Type().AssignableTo(ft.In(0)) { 84 | return false, fmt.Sprintf("wrong argument type %s for %s", v.Type(), ft) 85 | } 86 | return f.Call([]reflect.Value{v})[0].Interface().(bool), "" 87 | } 88 | 89 | // stringOrStringer is copied from juju/testing 90 | func stringOrStringer(value interface{}) (string, bool) { 91 | result, isString := value.(string) 92 | if !isString { 93 | if stringer, isStringer := value.(fmt.Stringer); isStringer { 94 | result, isString = stringer.String(), true 95 | } 96 | } 97 | return result, isString 98 | } 99 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | /* 5 | Package errors provides an easy way to annotate errors without losing the 6 | original error context. 7 | 8 | The exported `New` and `Errorf` functions are designed to replace the 9 | `errors.New` and `fmt.Errorf` functions respectively. The same underlying 10 | error is there, but the package also records the location at which the error 11 | was created. 12 | 13 | A primary use case for this library is to add extra context any time an 14 | error is returned from a function. 15 | 16 | if err := SomeFunc(); err != nil { 17 | return err 18 | } 19 | 20 | This instead becomes: 21 | 22 | if err := SomeFunc(); err != nil { 23 | return errors.Trace(err) 24 | } 25 | 26 | which just records the file and line number of the Trace call, or 27 | 28 | if err := SomeFunc(); err != nil { 29 | return errors.Annotate(err, "more context") 30 | } 31 | 32 | which also adds an annotation to the error. 33 | 34 | When you want to check to see if an error is of a particular type, a helper 35 | function is normally exported by the package that returned the error, like the 36 | `os` package does. The underlying cause of the error is available using the 37 | `Cause` function. 38 | 39 | os.IsNotExist(errors.Cause(err)) 40 | 41 | The result of the `Error()` call on an annotated error is the annotations joined 42 | with colons, then the result of the `Error()` method for the underlying error 43 | that was the cause. 44 | 45 | err := errors.Errorf("original") 46 | err = errors.Annotatef(err, "context") 47 | err = errors.Annotatef(err, "more context") 48 | err.Error() -> "more context: context: original" 49 | 50 | Obviously recording the file, line and functions is not very useful if you 51 | cannot get them back out again. 52 | 53 | errors.ErrorStack(err) 54 | 55 | will return something like: 56 | 57 | first error 58 | github.com/juju/errors/annotation_test.go:193: 59 | github.com/juju/errors/annotation_test.go:194: annotation 60 | github.com/juju/errors/annotation_test.go:195: 61 | github.com/juju/errors/annotation_test.go:196: more context 62 | github.com/juju/errors/annotation_test.go:197: 63 | 64 | The first error was generated by an external system, so there was no location 65 | associated. The second, fourth, and last lines were generated with Trace calls, 66 | and the other two through Annotate. 67 | 68 | Sometimes when responding to an error you want to return a more specific error 69 | for the situation. 70 | 71 | if err := FindField(field); err != nil { 72 | return errors.Wrap(err, errors.NotFoundf(field)) 73 | } 74 | 75 | This returns an error where the complete error stack is still available, and 76 | `errors.Cause()` will return the `NotFound` error. 77 | 78 | */ 79 | package errors 80 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | ) 10 | 11 | // Err holds a description of an error along with information about 12 | // where the error was created. 13 | // 14 | // It may be embedded in custom error types to add extra information that 15 | // this errors package can understand. 16 | type Err struct { 17 | // message holds an annotation of the error. 18 | message string 19 | 20 | // cause holds the cause of the error as returned 21 | // by the Cause method. 22 | cause error 23 | 24 | // previous holds the previous error in the error stack, if any. 25 | previous error 26 | 27 | // function is the package path-qualified function name where the 28 | // error was created. 29 | function string 30 | 31 | // line is the line number the error was created on inside of function 32 | line int 33 | } 34 | 35 | // Locationer is an interface that represents a certain class of errors that 36 | // contain the location information from where they were raised. 37 | type Locationer interface { 38 | // Location returns the path-qualified function name where the error was 39 | // created and the line number 40 | Location() (function string, line int) 41 | } 42 | 43 | // locationError is the internal implementation of the Locationer interface. 44 | type locationError struct { 45 | error 46 | 47 | // function is the package path-qualified function name where the 48 | // error was created. 49 | function string 50 | 51 | // line is the line number the error was created on inside of function 52 | line int 53 | } 54 | 55 | // newLocationError constructs a new Locationer error from the supplied error 56 | // with the location set to callDepth in the stack. If a nill error is provided 57 | // to this function then a new empty error is constructed. 58 | func newLocationError(err error, callDepth int) *locationError { 59 | le := &locationError{error: err} 60 | le.function, le.line = getLocation(callDepth + 1) 61 | return le 62 | } 63 | 64 | // Error implementes the error interface. 65 | func (l *locationError) Error() string { 66 | if l.error == nil { 67 | return "" 68 | } 69 | return l.error.Error() 70 | } 71 | 72 | // *locationError implements Locationer.Location interface 73 | func (l *locationError) Location() (string, int) { 74 | return l.function, l.line 75 | } 76 | 77 | func (l *locationError) Unwrap() error { 78 | return l.error 79 | } 80 | 81 | // NewErr is used to return an Err for the purpose of embedding in other 82 | // structures. The location is not specified, and needs to be set with a call 83 | // to SetLocation. 84 | // 85 | // For example: 86 | // type FooError struct { 87 | // errors.Err 88 | // code int 89 | // } 90 | // 91 | // func NewFooError(code int) error { 92 | // err := &FooError{errors.NewErr("foo"), code} 93 | // err.SetLocation(1) 94 | // return err 95 | // } 96 | func NewErr(format string, args ...interface{}) Err { 97 | return Err{ 98 | message: fmt.Sprintf(format, args...), 99 | } 100 | } 101 | 102 | // NewErrWithCause is used to return an Err with cause by other error for the purpose of embedding in other 103 | // structures. The location is not specified, and needs to be set with a call 104 | // to SetLocation. 105 | // 106 | // For example: 107 | // type FooError struct { 108 | // errors.Err 109 | // code int 110 | // } 111 | // 112 | // func (e *FooError) Annotate(format string, args ...interface{}) error { 113 | // err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code} 114 | // err.SetLocation(1) 115 | // return err 116 | // }) 117 | func NewErrWithCause(other error, format string, args ...interface{}) Err { 118 | return Err{ 119 | message: fmt.Sprintf(format, args...), 120 | cause: Cause(other), 121 | previous: other, 122 | } 123 | } 124 | 125 | // Location returns the package path-qualified function name and line of where 126 | // the error was most recently created or annotated. 127 | func (e *Err) Location() (function string, line int) { 128 | return e.function, e.line 129 | } 130 | 131 | // Underlying returns the previous error in the error stack, if any. A client 132 | // should not ever really call this method. It is used to build the error 133 | // stack and should not be introspected by client calls. Or more 134 | // specifically, clients should not depend on anything but the `Cause` of an 135 | // error. 136 | func (e *Err) Underlying() error { 137 | return e.previous 138 | } 139 | 140 | // Cause returns the most recent error in the error stack that 141 | // meets one of these criteria: the original error that was raised; the new 142 | // error that was passed into the Wrap function; the most recently masked 143 | // error; or nil if the error itself is considered the Cause. Normally this 144 | // method is not invoked directly, but instead through the Cause stand alone 145 | // function. 146 | func (e *Err) Cause() error { 147 | return e.cause 148 | } 149 | 150 | // Message returns the message stored with the most recent location. This is 151 | // the empty string if the most recent call was Trace, or the message stored 152 | // with Annotate or Mask. 153 | func (e *Err) Message() string { 154 | return e.message 155 | } 156 | 157 | // Error implements error.Error. 158 | func (e *Err) Error() string { 159 | // We want to walk up the stack of errors showing the annotations 160 | // as long as the cause is the same. 161 | err := e.previous 162 | if !sameError(Cause(err), e.cause) && e.cause != nil { 163 | err = e.cause 164 | } 165 | switch { 166 | case err == nil: 167 | return e.message 168 | case e.message == "": 169 | return err.Error() 170 | } 171 | return fmt.Sprintf("%s: %v", e.message, err) 172 | } 173 | 174 | // Format implements fmt.Formatter 175 | // When printing errors with %+v it also prints the stack trace. 176 | // %#v unsurprisingly will print the real underlying type. 177 | func (e *Err) Format(s fmt.State, verb rune) { 178 | switch verb { 179 | case 'v': 180 | switch { 181 | case s.Flag('+'): 182 | fmt.Fprintf(s, "%s", ErrorStack(e)) 183 | return 184 | case s.Flag('#'): 185 | // avoid infinite recursion by wrapping e into a type 186 | // that doesn't implement Formatter. 187 | fmt.Fprintf(s, "%#v", (*unformatter)(e)) 188 | return 189 | } 190 | fallthrough 191 | case 's': 192 | fmt.Fprintf(s, "%s", e.Error()) 193 | case 'q': 194 | fmt.Fprintf(s, "%q", e.Error()) 195 | default: 196 | fmt.Fprintf(s, "%%!%c(%T=%s)", verb, e, e.Error()) 197 | } 198 | } 199 | 200 | // helper for Format 201 | type unformatter Err 202 | 203 | func (unformatter) Format() { /* break the fmt.Formatter interface */ } 204 | 205 | // SetLocation records the package path-qualified function name of the error at 206 | // callDepth stack frames above the call. 207 | func (e *Err) SetLocation(callDepth int) { 208 | e.function, e.line = getLocation(callDepth + 1) 209 | } 210 | 211 | // StackTrace returns one string for each location recorded in the stack of 212 | // errors. The first value is the originating error, with a line for each 213 | // other annotation or tracing of the error. 214 | func (e *Err) StackTrace() []string { 215 | return errorStack(e) 216 | } 217 | 218 | // Ideally we'd have a way to check identity, but deep equals will do. 219 | func sameError(e1, e2 error) bool { 220 | return reflect.DeepEqual(e1, e2) 221 | } 222 | 223 | // Unwrap is a synonym for Underlying, which allows Err to be used with the 224 | // Unwrap, Is and As functions in Go's standard `errors` library. 225 | func (e *Err) Unwrap() error { 226 | return e.previous 227 | } 228 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | 10 | gc "gopkg.in/check.v1" 11 | 12 | "github.com/juju/errors" 13 | ) 14 | 15 | type errorsSuite struct{} 16 | 17 | var _ = gc.Suite(&errorsSuite{}) 18 | 19 | var someErr = errors.New("some error") //err varSomeErr 20 | 21 | func (*errorsSuite) TestErrorString(c *gc.C) { 22 | for i, test := range []struct { 23 | message string 24 | generator func() error 25 | expected string 26 | }{ 27 | { 28 | message: "uncomparable errors", 29 | generator: func() error { 30 | err := errors.Annotatef(newNonComparableError("uncomparable"), "annotation") 31 | return errors.Annotatef(err, "another") 32 | }, 33 | expected: "another: annotation: uncomparable", 34 | }, { 35 | message: "Errorf", 36 | generator: func() error { 37 | return errors.Errorf("first error") 38 | }, 39 | expected: "first error", 40 | }, { 41 | message: "annotated error", 42 | generator: func() error { 43 | err := errors.Errorf("first error") 44 | return errors.Annotatef(err, "annotation") 45 | }, 46 | expected: "annotation: first error", 47 | }, { 48 | message: "test annotation format", 49 | generator: func() error { 50 | err := errors.Errorf("first %s", "error") 51 | return errors.Annotatef(err, "%s", "annotation") 52 | }, 53 | expected: "annotation: first error", 54 | }, { 55 | message: "wrapped error", 56 | generator: func() error { 57 | err := newError("first error") 58 | return errors.Wrap(err, newError("detailed error")) 59 | }, 60 | expected: "detailed error", 61 | }, { 62 | message: "wrapped annotated error", 63 | generator: func() error { 64 | err := errors.Errorf("first error") 65 | err = errors.Annotatef(err, "annotated") 66 | return errors.Wrap(err, fmt.Errorf("detailed error")) 67 | }, 68 | expected: "detailed error", 69 | }, { 70 | message: "annotated wrapped error", 71 | generator: func() error { 72 | err := errors.Errorf("first error") 73 | err = errors.Wrap(err, fmt.Errorf("detailed error")) 74 | return errors.Annotatef(err, "annotated") 75 | }, 76 | expected: "annotated: detailed error", 77 | }, { 78 | message: "traced, and annotated", 79 | generator: func() error { 80 | err := errors.New("first error") 81 | err = errors.Trace(err) 82 | err = errors.Annotate(err, "some context") 83 | err = errors.Trace(err) 84 | err = errors.Annotate(err, "more context") 85 | return errors.Trace(err) 86 | }, 87 | expected: "more context: some context: first error", 88 | }, { 89 | message: "traced, and annotated, masked and annotated", 90 | generator: func() error { 91 | err := errors.New("first error") 92 | err = errors.Trace(err) 93 | err = errors.Annotate(err, "some context") 94 | err = errors.Maskf(err, "masked") 95 | err = errors.Annotate(err, "more context") 96 | return errors.Trace(err) 97 | }, 98 | expected: "more context: masked: some context: first error", 99 | }, { 100 | message: "error traced then unwrapped", 101 | generator: func() error { 102 | err := errors.New("inner error") 103 | err = errors.Trace(err) 104 | return errors.Unwrap(err) 105 | }, 106 | expected: "inner error", 107 | }, { 108 | message: "error annotated then unwrapped", 109 | generator: func() error { 110 | err := errors.New("inner error") 111 | err = errors.Annotate(err, "annotation") 112 | return errors.Unwrap(err) 113 | }, 114 | expected: "inner error", 115 | }, { 116 | message: "error wrapped then unwrapped", 117 | generator: func() error { 118 | err := errors.New("inner error") 119 | err = errors.Wrap(err, errors.New("cause")) 120 | return errors.Unwrap(err) 121 | }, 122 | expected: "inner error", 123 | }, { 124 | message: "error masked then unwrapped", 125 | generator: func() error { 126 | err := errors.New("inner error") 127 | err = errors.Mask(err) 128 | return errors.Unwrap(err) 129 | }, 130 | expected: "inner error", 131 | }, 132 | } { 133 | c.Logf("%v: %s", i, test.message) 134 | err := test.generator() 135 | ok := c.Check(err.Error(), gc.Equals, test.expected) 136 | if !ok { 137 | c.Logf("%#v", test.generator()) 138 | } 139 | } 140 | } 141 | 142 | func (*errorsSuite) TestNewErr(c *gc.C) { 143 | if runtime.Compiler == "gccgo" { 144 | c.Skip("gccgo can't determine the location") 145 | } 146 | 147 | err := errors.NewErr("testing %d", 42) 148 | err.SetLocation(0) 149 | locLine := errorLocationValue(c) 150 | 151 | c.Assert(err.Error(), gc.Equals, "testing 42") 152 | c.Assert(errors.Cause(&err), gc.Equals, &err) 153 | c.Assert(errors.Details(&err), Contains, locLine) 154 | } 155 | 156 | func (*errorsSuite) TestNewErrWithCause(c *gc.C) { 157 | if runtime.Compiler == "gccgo" { 158 | c.Skip("gccgo can't determine the location") 159 | } 160 | causeErr := fmt.Errorf("external error") 161 | err := errors.NewErrWithCause(causeErr, "testing %d", 43) 162 | err.SetLocation(0) 163 | locLine := errorLocationValue(c) 164 | 165 | c.Assert(err.Error(), gc.Equals, "testing 43: external error") 166 | c.Assert(errors.Cause(&err), gc.Equals, causeErr) 167 | c.Assert(errors.Details(&err), Contains, locLine) 168 | } 169 | 170 | func (*errorsSuite) TestUnwrapNewErrGivesNil(c *gc.C) { 171 | err := errors.New("test error") 172 | c.Assert(errors.Unwrap(err), gc.IsNil) 173 | } 174 | 175 | // This is an uncomparable error type, as it is a struct that supports the 176 | // error interface (as opposed to a pointer type). 177 | type error_ struct { 178 | info string 179 | slice []string 180 | } 181 | 182 | // Create a non-comparable error 183 | func newNonComparableError(message string) error { 184 | return error_{info: message} 185 | } 186 | 187 | func (e error_) Error() string { 188 | return e.info 189 | } 190 | 191 | func newError(message string) error { 192 | return testError{message} 193 | } 194 | 195 | // The testError is a value type error for ease of seeing results 196 | // when the test fails. 197 | type testError struct { 198 | message string 199 | } 200 | 201 | func (e testError) Error() string { 202 | return e.message 203 | } 204 | -------------------------------------------------------------------------------- /errortypes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors 5 | 6 | import ( 7 | "errors" 8 | stderror "errors" 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // a ConstError is a prototype for a certain type of error 14 | type ConstError string 15 | 16 | // ConstError implements error 17 | func (e ConstError) Error() string { 18 | return string(e) 19 | } 20 | 21 | // Different types of errors 22 | const ( 23 | // Timeout represents an error on timeout. 24 | Timeout = ConstError("timeout") 25 | // NotFound represents an error when something has not been found. 26 | NotFound = ConstError("not found") 27 | // UserNotFound represents an error when a non-existent user is looked up. 28 | UserNotFound = ConstError("user not found") 29 | // Unauthorized represents an error when an operation is unauthorized. 30 | Unauthorized = ConstError("unauthorized") 31 | // NotImplemented represents an error when something is not 32 | // implemented. 33 | NotImplemented = ConstError("not implemented") 34 | // AlreadyExists represents and error when something already exists. 35 | AlreadyExists = ConstError("already exists") 36 | // NotSupported represents an error when something is not supported. 37 | NotSupported = ConstError("not supported") 38 | // NotValid represents an error when something is not valid. 39 | NotValid = ConstError("not valid") 40 | // NotProvisioned represents an error when something is not yet provisioned. 41 | NotProvisioned = ConstError("not provisioned") 42 | // NotAssigned represents an error when something is not yet assigned to 43 | // something else. 44 | NotAssigned = ConstError("not assigned") 45 | // BadRequest represents an error when a request has bad parameters. 46 | BadRequest = ConstError("bad request") 47 | // MethodNotAllowed represents an error when an HTTP request 48 | // is made with an inappropriate method. 49 | MethodNotAllowed = ConstError("method not allowed") 50 | // Forbidden represents an error when a request cannot be completed because of 51 | // missing privileges. 52 | Forbidden = ConstError("forbidden") 53 | // QuotaLimitExceeded is emitted when an action failed due to a quota limit check. 54 | QuotaLimitExceeded = ConstError("quota limit exceeded") 55 | // NotYetAvailable is the error returned when a resource is not yet available 56 | // but it might be in the future. 57 | NotYetAvailable = ConstError("not yet available") 58 | ) 59 | 60 | // errWithType is an Err bundled with its error type (a ConstError) 61 | type errWithType struct { 62 | error 63 | errType ConstError 64 | } 65 | 66 | // Is compares `target` with e's error type 67 | func (e *errWithType) Is(target error) bool { 68 | if &e.errType == nil { 69 | return false 70 | } 71 | return target == e.errType 72 | } 73 | 74 | // Unwrap an errWithType gives the underlying Err 75 | func (e *errWithType) Unwrap() error { 76 | return e.error 77 | } 78 | 79 | func wrapErrorWithMsg(err error, msg string) error { 80 | if err == nil { 81 | return stderror.New(msg) 82 | } 83 | if msg == "" { 84 | return err 85 | } 86 | return fmt.Errorf("%s: %w", msg, err) 87 | } 88 | 89 | func makeWrappedConstError(err error, format string, args ...interface{}) error { 90 | separator := " " 91 | if err.Error() == "" || errors.Is(err, &fmtNoop{}) { 92 | separator = "" 93 | } 94 | return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...) 95 | } 96 | 97 | // WithType is responsible for annotating an already existing error so that it 98 | // also satisfies that of a ConstError. The resultant error returned should 99 | // satisfy Is(err, errType). If err is nil then a nil error will also be returned. 100 | // 101 | // Now with Go's Is, As and Unwrap support it no longer makes sense to Wrap() 102 | // 2 errors as both of those errors could be chains of errors in their own right. 103 | // WithType aims to solve some of the usefulness of Wrap with the ability to 104 | // make a pre-existing error also satisfy a ConstError type. 105 | func WithType(err error, errType ConstError) error { 106 | if err == nil { 107 | return nil 108 | } 109 | return &errWithType{ 110 | error: err, 111 | errType: errType, 112 | } 113 | } 114 | 115 | // Timeoutf returns an error which satisfies Is(err, Timeout) and the Locationer 116 | // interface. 117 | func Timeoutf(format string, args ...interface{}) error { 118 | return newLocationError( 119 | makeWrappedConstError(Timeout, format, args...), 120 | 1, 121 | ) 122 | } 123 | 124 | // NewTimeout returns an error which wraps err and satisfies Is(err, Timeout) 125 | // and the Locationer interface. 126 | func NewTimeout(err error, msg string) error { 127 | return &errWithType{ 128 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 129 | errType: Timeout, 130 | } 131 | } 132 | 133 | // Deprecated: IsTimeout reports whether err is a Timeout error. Use 134 | // Is(err, Timeout). 135 | func IsTimeout(err error) bool { 136 | return Is(err, Timeout) 137 | } 138 | 139 | // NotFoundf returns an error which satisfies Is(err, NotFound) and the 140 | // Locationer interface. 141 | func NotFoundf(format string, args ...interface{}) error { 142 | return newLocationError( 143 | makeWrappedConstError(NotFound, format, args...), 144 | 1, 145 | ) 146 | } 147 | 148 | // NewNotFound returns an error which wraps err and satisfies Is(err, NotFound) 149 | // and the Locationer interface. 150 | func NewNotFound(err error, msg string) error { 151 | return &errWithType{ 152 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 153 | errType: NotFound, 154 | } 155 | } 156 | 157 | // Deprecated: IsNotFound reports whether err is a NotFound error. Use 158 | // Is(err, NotFound). 159 | func IsNotFound(err error) bool { 160 | return Is(err, NotFound) 161 | } 162 | 163 | // UserNotFoundf returns an error which satisfies Is(err, UserNotFound) and the 164 | // Locationer interface. 165 | func UserNotFoundf(format string, args ...interface{}) error { 166 | return newLocationError( 167 | makeWrappedConstError(UserNotFound, format, args...), 168 | 1, 169 | ) 170 | } 171 | 172 | // NewUserNotFound returns an error which wraps err and satisfies 173 | // Is(err, UserNotFound) and the Locationer interface. 174 | func NewUserNotFound(err error, msg string) error { 175 | return &errWithType{ 176 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 177 | errType: UserNotFound, 178 | } 179 | } 180 | 181 | // Deprecated: IsUserNotFound reports whether err is a UserNotFound error. Use 182 | // Is(err, UserNotFound). 183 | func IsUserNotFound(err error) bool { 184 | return Is(err, UserNotFound) 185 | } 186 | 187 | // Unauthorizedf returns an error that satisfies Is(err, Unauthorized) and 188 | // the Locationer interface. 189 | func Unauthorizedf(format string, args ...interface{}) error { 190 | return newLocationError( 191 | makeWrappedConstError(Hide(Unauthorized), format, args...), 192 | 1, 193 | ) 194 | } 195 | 196 | // NewUnauthorized returns an error which wraps err and satisfies 197 | // Is(err, Unathorized) and the Locationer interface. 198 | func NewUnauthorized(err error, msg string) error { 199 | return &errWithType{ 200 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 201 | errType: Unauthorized, 202 | } 203 | } 204 | 205 | // Deprecated: IsUnauthorized reports whether err is a Unauthorized error. Use 206 | // Is(err, Unauthorized). 207 | func IsUnauthorized(err error) bool { 208 | return Is(err, Unauthorized) 209 | } 210 | 211 | // NotImplementedf returns an error which satisfies Is(err, NotImplemented) and 212 | // the Locationer interface. 213 | func NotImplementedf(format string, args ...interface{}) error { 214 | return newLocationError( 215 | makeWrappedConstError(NotImplemented, format, args...), 216 | 1, 217 | ) 218 | } 219 | 220 | // NewNotImplemented returns an error which wraps err and satisfies 221 | // Is(err, NotImplemented) and the Locationer interface. 222 | func NewNotImplemented(err error, msg string) error { 223 | return &errWithType{ 224 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 225 | errType: NotImplemented, 226 | } 227 | } 228 | 229 | // Deprecated: IsNotImplemented reports whether err is a NotImplemented error. 230 | // Use Is(err, NotImplemented). 231 | func IsNotImplemented(err error) bool { 232 | return Is(err, NotImplemented) 233 | } 234 | 235 | // AlreadyExistsf returns an error which satisfies Is(err, AlreadyExists) and 236 | // the Locationer interface. 237 | func AlreadyExistsf(format string, args ...interface{}) error { 238 | return newLocationError( 239 | makeWrappedConstError(AlreadyExists, format, args...), 240 | 1, 241 | ) 242 | } 243 | 244 | // NewAlreadyExists returns an error which wraps err and satisfies 245 | // Is(err, AlreadyExists) and the Locationer interface. 246 | func NewAlreadyExists(err error, msg string) error { 247 | return &errWithType{ 248 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 249 | errType: AlreadyExists, 250 | } 251 | } 252 | 253 | // Deprecated: IsAlreadyExists reports whether the err is a AlreadyExists 254 | // error. Use Is(err, AlreadyExists). 255 | func IsAlreadyExists(err error) bool { 256 | return Is(err, AlreadyExists) 257 | } 258 | 259 | // NotSupportedf returns an error which satisfies Is(err, NotSupported) and the 260 | // Locationer interface. 261 | func NotSupportedf(format string, args ...interface{}) error { 262 | return newLocationError( 263 | makeWrappedConstError(NotSupported, format, args...), 264 | 1, 265 | ) 266 | } 267 | 268 | // NewNotSupported returns an error which satisfies Is(err, NotSupported) and 269 | // the Locationer interface. 270 | func NewNotSupported(err error, msg string) error { 271 | return &errWithType{ 272 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 273 | errType: NotSupported, 274 | } 275 | } 276 | 277 | // Deprecated: IsNotSupported reports whether err is a NotSupported error. Use 278 | // Is(err, NotSupported). 279 | func IsNotSupported(err error) bool { 280 | return Is(err, NotSupported) 281 | } 282 | 283 | // NotValidf returns an error which satisfies Is(err, NotValid) and the 284 | // Locationer interface. 285 | func NotValidf(format string, args ...interface{}) error { 286 | return newLocationError( 287 | makeWrappedConstError(NotValid, format, args...), 288 | 1, 289 | ) 290 | } 291 | 292 | // NewNotValid returns an error which wraps err and satisfies Is(err, NotValid) 293 | // and the Locationer interface. 294 | func NewNotValid(err error, msg string) error { 295 | return &errWithType{ 296 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 297 | errType: NotValid, 298 | } 299 | } 300 | 301 | // Deprecated: IsNotValid reports whether err is a NotValid error. Use 302 | // Is(err, NotValid). 303 | func IsNotValid(err error) bool { 304 | return Is(err, NotValid) 305 | } 306 | 307 | // NotProvisionedf returns an error which satisfies Is(err, NotProvisioned) and 308 | // the Locationer interface. 309 | func NotProvisionedf(format string, args ...interface{}) error { 310 | return newLocationError( 311 | makeWrappedConstError(NotProvisioned, format, args...), 312 | 1, 313 | ) 314 | } 315 | 316 | // NewNotProvisioned returns an error which wraps err and satisfies 317 | // Is(err, NotProvisioned) and the Locationer interface. 318 | func NewNotProvisioned(err error, msg string) error { 319 | return &errWithType{ 320 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 321 | errType: NotProvisioned, 322 | } 323 | } 324 | 325 | // Deprecated: IsNotProvisioned reports whether err is a NotProvisioned error. 326 | // Use Is(err, NotProvisioned). 327 | func IsNotProvisioned(err error) bool { 328 | return Is(err, NotProvisioned) 329 | } 330 | 331 | // NotAssignedf returns an error which satisfies Is(err, NotAssigned) and the 332 | // Locationer interface. 333 | func NotAssignedf(format string, args ...interface{}) error { 334 | return newLocationError( 335 | makeWrappedConstError(NotAssigned, format, args...), 336 | 1, 337 | ) 338 | } 339 | 340 | // NewNotAssigned returns an error which wraps err and satisfies 341 | // Is(err, NotAssigned) and the Locationer interface. 342 | func NewNotAssigned(err error, msg string) error { 343 | return &errWithType{ 344 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 345 | errType: NotAssigned, 346 | } 347 | } 348 | 349 | // Deprecated: IsNotAssigned reports whether err is a NotAssigned error. 350 | // Use Is(err, NotAssigned) 351 | func IsNotAssigned(err error) bool { 352 | return Is(err, NotAssigned) 353 | } 354 | 355 | // BadRequestf returns an error which satisfies Is(err, BadRequest) and the 356 | // Locationer interface. 357 | func BadRequestf(format string, args ...interface{}) error { 358 | return newLocationError( 359 | makeWrappedConstError(Hide(BadRequest), format, args...), 360 | 1, 361 | ) 362 | } 363 | 364 | // NewBadRequest returns an error which wraps err and satisfies 365 | // Is(err, BadRequest) and the Locationer interface. 366 | func NewBadRequest(err error, msg string) error { 367 | return &errWithType{ 368 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 369 | errType: BadRequest, 370 | } 371 | } 372 | 373 | // Deprecated: IsBadRequest reports whether err is a BadRequest error. 374 | // Use Is(err, BadRequest) 375 | func IsBadRequest(err error) bool { 376 | return Is(err, BadRequest) 377 | } 378 | 379 | // MethodNotAllowedf returns an error which satisfies Is(err, MethodNotAllowed) 380 | // and the Locationer interface. 381 | func MethodNotAllowedf(format string, args ...interface{}) error { 382 | return newLocationError( 383 | makeWrappedConstError(Hide(MethodNotAllowed), format, args...), 384 | 1, 385 | ) 386 | } 387 | 388 | // NewMethodNotAllowed returns an error which wraps err and satisfies 389 | // Is(err, MethodNotAllowed) and the Locationer interface. 390 | func NewMethodNotAllowed(err error, msg string) error { 391 | return &errWithType{ 392 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 393 | errType: MethodNotAllowed, 394 | } 395 | } 396 | 397 | // Deprecated: IsMethodNotAllowed reports whether err is a MethodNotAllowed 398 | // error. Use Is(err, MethodNotAllowed) 399 | func IsMethodNotAllowed(err error) bool { 400 | return Is(err, MethodNotAllowed) 401 | } 402 | 403 | // Forbiddenf returns an error which satistifes Is(err, Forbidden) and the 404 | // Locationer interface. 405 | func Forbiddenf(format string, args ...interface{}) error { 406 | return newLocationError( 407 | makeWrappedConstError(Hide(Forbidden), format, args...), 408 | 1, 409 | ) 410 | } 411 | 412 | // NewForbidden returns an error which wraps err and satisfies 413 | // Is(err, Forbidden) and the Locationer interface. 414 | func NewForbidden(err error, msg string) error { 415 | return &errWithType{ 416 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 417 | errType: Forbidden, 418 | } 419 | } 420 | 421 | // Deprecated: IsForbidden reports whether err is a Forbidden error. Use 422 | // Is(err, Forbidden). 423 | func IsForbidden(err error) bool { 424 | return Is(err, Forbidden) 425 | } 426 | 427 | // QuotaLimitExceededf returns an error which satisfies 428 | // Is(err, QuotaLimitExceeded) and the Locationer interface. 429 | func QuotaLimitExceededf(format string, args ...interface{}) error { 430 | return newLocationError( 431 | makeWrappedConstError(Hide(QuotaLimitExceeded), format, args...), 432 | 1, 433 | ) 434 | } 435 | 436 | // NewQuotaLimitExceeded returns an error which wraps err and satisfies 437 | // Is(err, QuotaLimitExceeded) and the Locationer interface. 438 | func NewQuotaLimitExceeded(err error, msg string) error { 439 | return &errWithType{ 440 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 441 | errType: QuotaLimitExceeded, 442 | } 443 | } 444 | 445 | // Deprecated: IsQuotaLimitExceeded reports whether err is a QuoteLimitExceeded 446 | // err. Use Is(err, QuotaLimitExceeded). 447 | func IsQuotaLimitExceeded(err error) bool { 448 | return Is(err, QuotaLimitExceeded) 449 | } 450 | 451 | // NotYetAvailablef returns an error which satisfies Is(err, NotYetAvailable) 452 | // and the Locationer interface. 453 | func NotYetAvailablef(format string, args ...interface{}) error { 454 | return newLocationError( 455 | makeWrappedConstError(Hide(NotYetAvailable), format, args...), 456 | 1, 457 | ) 458 | } 459 | 460 | // NewNotYetAvailable returns an error which wraps err and satisfies 461 | // Is(err, NotYetAvailable) and the Locationer interface. 462 | func NewNotYetAvailable(err error, msg string) error { 463 | return &errWithType{ 464 | error: newLocationError(wrapErrorWithMsg(err, msg), 1), 465 | errType: NotYetAvailable, 466 | } 467 | } 468 | 469 | // Deprecated: IsNotYetAvailable reports whether err is a NotYetAvailable err. 470 | // Use Is(err, NotYetAvailable) 471 | func IsNotYetAvailable(err error) bool { 472 | return Is(err, NotYetAvailable) 473 | } 474 | -------------------------------------------------------------------------------- /errortypes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | stderrors "errors" 8 | "fmt" 9 | 10 | "github.com/juju/errors" 11 | gc "gopkg.in/check.v1" 12 | ) 13 | 14 | // errorInfo holds information about a single error type: its type 15 | // and name, wrapping and variable arguments constructors and message 16 | // suffix. 17 | type errorInfo struct { 18 | errType errors.ConstError 19 | errName string 20 | argsConstructor func(string, ...interface{}) error 21 | wrapConstructor func(error, string) error 22 | suffix string 23 | } 24 | 25 | // allErrors holds information for all defined errors. When adding new 26 | // errors, add them here as well to include them in tests. 27 | var allErrors = []*errorInfo{ 28 | {errors.Timeout, "Timeout", errors.Timeoutf, errors.NewTimeout, " timeout"}, 29 | {errors.NotFound, "NotFound", errors.NotFoundf, errors.NewNotFound, " not found"}, 30 | {errors.UserNotFound, "UserNotFound", errors.UserNotFoundf, errors.NewUserNotFound, " user not found"}, 31 | {errors.Unauthorized, "Unauthorized", errors.Unauthorizedf, errors.NewUnauthorized, ""}, 32 | {errors.NotImplemented, "NotImplemented", errors.NotImplementedf, errors.NewNotImplemented, " not implemented"}, 33 | {errors.AlreadyExists, "AlreadyExists", errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"}, 34 | {errors.NotSupported, "NotSupported", errors.NotSupportedf, errors.NewNotSupported, " not supported"}, 35 | {errors.NotValid, "NotValid", errors.NotValidf, errors.NewNotValid, " not valid"}, 36 | {errors.NotProvisioned, "NotProvisioned", errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"}, 37 | {errors.NotAssigned, "NotAssigned", errors.NotAssignedf, errors.NewNotAssigned, " not assigned"}, 38 | {errors.MethodNotAllowed, "MethodNotAllowed", errors.MethodNotAllowedf, errors.NewMethodNotAllowed, ""}, 39 | {errors.BadRequest, "BadRequest", errors.BadRequestf, errors.NewBadRequest, ""}, 40 | {errors.Forbidden, "Forbidden", errors.Forbiddenf, errors.NewForbidden, ""}, 41 | {errors.QuotaLimitExceeded, "QuotaLimitExceeded", errors.QuotaLimitExceededf, errors.NewQuotaLimitExceeded, ""}, 42 | {errors.NotYetAvailable, "NotYetAvailable", errors.NotYetAvailablef, errors.NewNotYetAvailable, ""}, 43 | } 44 | 45 | type errorTypeSuite struct{} 46 | 47 | var _ = gc.Suite(&errorTypeSuite{}) 48 | 49 | func (t *errorInfo) equal(t0 *errorInfo) bool { 50 | if t0 == nil { 51 | return false 52 | } 53 | return t == t0 54 | } 55 | 56 | type errorTest struct { 57 | err error 58 | message string 59 | errInfo *errorInfo 60 | } 61 | 62 | func deferredAnnotatef(err error, format string, args ...interface{}) error { 63 | errors.DeferredAnnotatef(&err, format, args...) 64 | return err 65 | } 66 | 67 | func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) { 68 | if errInfo != nil { 69 | msg := fmt.Sprintf("Is(err, %s) should be TRUE when err := %#v", errInfo.errName, err) 70 | c.Check(errors.Is(err, errInfo.errType), gc.Equals, true, gc.Commentf(msg)) 71 | } 72 | } 73 | 74 | func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) { 75 | if errInfo != nil { 76 | msg := fmt.Sprintf("Is(err, %s) should be FALSE when err := %#v", errInfo.errName, err) 77 | c.Check(errors.Is(err, errInfo.errType), gc.Equals, false, gc.Commentf(msg)) 78 | } 79 | } 80 | 81 | func checkErrorMatches(c *gc.C, err error, message string, errInfo *errorInfo) { 82 | if message == "" { 83 | c.Check(err, gc.IsNil) 84 | c.Check(errInfo, gc.IsNil) 85 | } else { 86 | c.Check(err, gc.ErrorMatches, message) 87 | } 88 | } 89 | 90 | func runErrorTests(c *gc.C, errorTests []errorTest, checkMustSatisfy bool) { 91 | for i, t := range errorTests { 92 | c.Logf("test %d: %T: %v", i, t.err, t.err) 93 | checkErrorMatches(c, t.err, t.message, t.errInfo) 94 | if checkMustSatisfy { 95 | mustSatisfy(c, t.err, t.errInfo) 96 | } 97 | 98 | // Check all other satisfiers to make sure none match. 99 | for _, otherErrInfo := range allErrors { 100 | if checkMustSatisfy && otherErrInfo.equal(t.errInfo) { 101 | continue 102 | } 103 | mustNotSatisfy(c, t.err, otherErrInfo) 104 | } 105 | } 106 | } 107 | 108 | func (*errorTypeSuite) TestDeferredAnnotatef(c *gc.C) { 109 | // Ensure DeferredAnnotatef annotates the errors. 110 | errorTests := []errorTest{} 111 | for _, errInfo := range allErrors { 112 | errorTests = append(errorTests, []errorTest{{ 113 | deferredAnnotatef(nil, "comment"), 114 | "", 115 | nil, 116 | }, { 117 | deferredAnnotatef(stderrors.New("blast"), "comment"), 118 | "comment: blast", 119 | nil, 120 | }, { 121 | deferredAnnotatef(errInfo.argsConstructor("foo %d", 42), "comment %d", 69), 122 | "comment 69: foo 42" + errInfo.suffix, 123 | errInfo, 124 | }, { 125 | deferredAnnotatef(errInfo.argsConstructor(""), "comment"), 126 | "comment: " + errInfo.suffix, 127 | errInfo, 128 | }, { 129 | deferredAnnotatef(errInfo.wrapConstructor(stderrors.New("pow!"), "woo"), "comment"), 130 | "comment: woo: pow!", 131 | errInfo, 132 | }}...) 133 | } 134 | 135 | runErrorTests(c, errorTests, true) 136 | } 137 | 138 | func (*errorTypeSuite) TestAllErrors(c *gc.C) { 139 | errorTests := []errorTest{} 140 | for _, errInfo := range allErrors { 141 | errorTests = append(errorTests, []errorTest{{ 142 | nil, 143 | "", 144 | nil, 145 | }, { 146 | errInfo.argsConstructor("foo %d", 42), 147 | "foo 42" + errInfo.suffix, 148 | errInfo, 149 | }, { 150 | errInfo.argsConstructor(""), 151 | errInfo.suffix, 152 | errInfo, 153 | }, { 154 | errInfo.wrapConstructor(stderrors.New("pow!"), "prefix"), 155 | "prefix: pow!", 156 | errInfo, 157 | }, { 158 | errInfo.wrapConstructor(stderrors.New("pow!"), ""), 159 | "pow!", 160 | errInfo, 161 | }, { 162 | errInfo.wrapConstructor(nil, "prefix"), 163 | "prefix", 164 | errInfo, 165 | }}...) 166 | } 167 | 168 | runErrorTests(c, errorTests, true) 169 | } 170 | 171 | // TestThatYouAlwaysGetError is a regression test for checking that the wrap 172 | // constructor for our error types always returns a valid error object even if 173 | // don't feed the construct with an instantiated error or a non empty string. 174 | func (*errorTypeSuite) TestThatYouAlwaysGetError(c *gc.C) { 175 | for _, errType := range allErrors { 176 | err := errType.wrapConstructor(nil, "") 177 | c.Assert(err.Error(), gc.Equals, "") 178 | } 179 | } 180 | 181 | func (*errorTypeSuite) TestWithTypeNil(c *gc.C) { 182 | myErr := errors.ConstError("do you feel lucky?") 183 | c.Assert(errors.WithType(nil, myErr), gc.IsNil) 184 | } 185 | 186 | func (*errorTypeSuite) TestWithType(c *gc.C) { 187 | myErr := errors.ConstError("do you feel lucky?") 188 | myErr2 := errors.ConstError("i don't feel lucky") 189 | err := errors.New("yes") 190 | 191 | err = errors.WithType(err, myErr) 192 | c.Assert(errors.Is(err, myErr), gc.Equals, true) 193 | c.Assert(err.Error(), gc.Equals, "yes") 194 | c.Assert(errors.Is(err, myErr2), gc.Equals, false) 195 | } 196 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/juju/errors" 10 | ) 11 | 12 | func ExampleTrace() { 13 | var err1 error = fmt.Errorf("something wicked this way comes") 14 | var err2 error = nil 15 | 16 | // Tracing a non nil error will return an error 17 | fmt.Println(errors.Trace(err1)) 18 | // Tracing nil will return nil 19 | fmt.Println(errors.Trace(err2)) 20 | 21 | // Output: something wicked this way comes 22 | // 23 | } 24 | -------------------------------------------------------------------------------- /functions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors 5 | 6 | import ( 7 | stderrors "errors" 8 | "fmt" 9 | "runtime" 10 | "strings" 11 | ) 12 | 13 | // New is a drop in replacement for the standard library errors module that records 14 | // the location that the error is created. 15 | // 16 | // For example: 17 | // return errors.New("validation failed") 18 | // 19 | func New(message string) error { 20 | err := &Err{message: message} 21 | err.SetLocation(1) 22 | return err 23 | } 24 | 25 | // Errorf creates a new annotated error and records the location that the 26 | // error is created. This should be a drop in replacement for fmt.Errorf. 27 | // 28 | // For example: 29 | // return errors.Errorf("validation failed: %s", message) 30 | // 31 | func Errorf(format string, args ...interface{}) error { 32 | err := &Err{message: fmt.Sprintf(format, args...)} 33 | err.SetLocation(1) 34 | return err 35 | } 36 | 37 | // getLocation records the package path-qualified function name of the error at 38 | // callDepth stack frames above the call. 39 | func getLocation(callDepth int) (string, int) { 40 | rpc := make([]uintptr, 1) 41 | n := runtime.Callers(callDepth+2, rpc[:]) 42 | if n < 1 { 43 | return "", 0 44 | } 45 | frame, _ := runtime.CallersFrames(rpc).Next() 46 | return frame.Function, frame.Line 47 | } 48 | 49 | // Trace adds the location of the Trace call to the stack. The Cause of the 50 | // resulting error is the same as the error parameter. If the other error is 51 | // nil, the result will be nil. 52 | // 53 | // For example: 54 | // if err := SomeFunc(); err != nil { 55 | // return errors.Trace(err) 56 | // } 57 | // 58 | func Trace(other error) error { 59 | //return SetLocation(other, 2) 60 | if other == nil { 61 | return nil 62 | } 63 | err := &Err{previous: other, cause: Cause(other)} 64 | err.SetLocation(1) 65 | return err 66 | } 67 | 68 | // Annotate is used to add extra context to an existing error. The location of 69 | // the Annotate call is recorded with the annotations. The file, line and 70 | // function are also recorded. 71 | // 72 | // For example: 73 | // if err := SomeFunc(); err != nil { 74 | // return errors.Annotate(err, "failed to frombulate") 75 | // } 76 | // 77 | func Annotate(other error, message string) error { 78 | if other == nil { 79 | return nil 80 | } 81 | err := &Err{ 82 | previous: other, 83 | cause: Cause(other), 84 | message: message, 85 | } 86 | err.SetLocation(1) 87 | return err 88 | } 89 | 90 | // Annotatef is used to add extra context to an existing error. The location of 91 | // the Annotate call is recorded with the annotations. The file, line and 92 | // function are also recorded. 93 | // 94 | // For example: 95 | // if err := SomeFunc(); err != nil { 96 | // return errors.Annotatef(err, "failed to frombulate the %s", arg) 97 | // } 98 | // 99 | func Annotatef(other error, format string, args ...interface{}) error { 100 | if other == nil { 101 | return nil 102 | } 103 | err := &Err{ 104 | previous: other, 105 | cause: Cause(other), 106 | message: fmt.Sprintf(format, args...), 107 | } 108 | err.SetLocation(1) 109 | return err 110 | } 111 | 112 | // DeferredAnnotatef annotates the given error (when it is not nil) with the given 113 | // format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef 114 | // does nothing. This method is used in a defer statement in order to annotate any 115 | // resulting error with the same message. 116 | // 117 | // For example: 118 | // 119 | // defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) 120 | // 121 | func DeferredAnnotatef(err *error, format string, args ...interface{}) { 122 | if *err == nil { 123 | return 124 | } 125 | newErr := &Err{ 126 | message: fmt.Sprintf(format, args...), 127 | cause: Cause(*err), 128 | previous: *err, 129 | } 130 | newErr.SetLocation(1) 131 | *err = newErr 132 | } 133 | 134 | // Wrap changes the Cause of the error. The location of the Wrap call is also 135 | // stored in the error stack. 136 | // 137 | // For example: 138 | // if err := SomeFunc(); err != nil { 139 | // newErr := &packageError{"more context", private_value} 140 | // return errors.Wrap(err, newErr) 141 | // } 142 | // 143 | func Wrap(other, newDescriptive error) error { 144 | err := &Err{ 145 | previous: other, 146 | cause: newDescriptive, 147 | } 148 | err.SetLocation(1) 149 | return err 150 | } 151 | 152 | // Wrapf changes the Cause of the error, and adds an annotation. The location 153 | // of the Wrap call is also stored in the error stack. 154 | // 155 | // For example: 156 | // if err := SomeFunc(); err != nil { 157 | // return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) 158 | // } 159 | // 160 | func Wrapf(other, newDescriptive error, format string, args ...interface{}) error { 161 | err := &Err{ 162 | message: fmt.Sprintf(format, args...), 163 | previous: other, 164 | cause: newDescriptive, 165 | } 166 | err.SetLocation(1) 167 | return err 168 | } 169 | 170 | // Maskf masks the given error with the given format string and arguments (like 171 | // fmt.Sprintf), returning a new error that maintains the error stack, but 172 | // hides the underlying error type. The error string still contains the full 173 | // annotations. If you want to hide the annotations, call Wrap. 174 | func Maskf(other error, format string, args ...interface{}) error { 175 | if other == nil { 176 | return nil 177 | } 178 | err := &Err{ 179 | message: fmt.Sprintf(format, args...), 180 | previous: other, 181 | } 182 | err.SetLocation(1) 183 | return err 184 | } 185 | 186 | // Mask hides the underlying error type, and records the location of the masking. 187 | func Mask(other error) error { 188 | if other == nil { 189 | return nil 190 | } 191 | err := &Err{ 192 | previous: other, 193 | } 194 | err.SetLocation(1) 195 | return err 196 | } 197 | 198 | // Cause returns the cause of the given error. This will be either the 199 | // original error, or the result of a Wrap or Mask call. 200 | // 201 | // Cause is the usual way to diagnose errors that may have been wrapped by 202 | // the other errors functions. 203 | func Cause(err error) error { 204 | var diag error 205 | if err, ok := err.(causer); ok { 206 | diag = err.Cause() 207 | } 208 | if diag != nil { 209 | return diag 210 | } 211 | return err 212 | } 213 | 214 | type causer interface { 215 | Cause() error 216 | } 217 | 218 | type wrapper interface { 219 | // Message returns the top level error message, 220 | // not including the message from the Previous 221 | // error. 222 | Message() string 223 | 224 | // Underlying returns the Previous error, or nil 225 | // if there is none. 226 | Underlying() error 227 | } 228 | 229 | var ( 230 | _ wrapper = (*Err)(nil) 231 | _ Locationer = (*Err)(nil) 232 | _ causer = (*Err)(nil) 233 | ) 234 | 235 | // Details returns information about the stack of errors wrapped by err, in 236 | // the format: 237 | // 238 | // [{filename:99: error one} {otherfile:55: cause of error one}] 239 | // 240 | // This is a terse alternative to ErrorStack as it returns a single line. 241 | func Details(err error) string { 242 | if err == nil { 243 | return "[]" 244 | } 245 | var s []byte 246 | s = append(s, '[') 247 | for { 248 | s = append(s, '{') 249 | if err, ok := err.(Locationer); ok { 250 | file, line := err.Location() 251 | if file != "" { 252 | s = append(s, fmt.Sprintf("%s:%d", file, line)...) 253 | s = append(s, ": "...) 254 | } 255 | } 256 | if cerr, ok := err.(wrapper); ok { 257 | s = append(s, cerr.Message()...) 258 | err = cerr.Underlying() 259 | } else { 260 | s = append(s, err.Error()...) 261 | err = nil 262 | } 263 | s = append(s, '}') 264 | if err == nil { 265 | break 266 | } 267 | s = append(s, ' ') 268 | } 269 | s = append(s, ']') 270 | return string(s) 271 | } 272 | 273 | // ErrorStack returns a string representation of the annotated error. If the 274 | // error passed as the parameter is not an annotated error, the result is 275 | // simply the result of the Error() method on that error. 276 | // 277 | // If the error is an annotated error, a multi-line string is returned where 278 | // each line represents one entry in the annotation stack. The full filename 279 | // from the call stack is used in the output. 280 | // 281 | // first error 282 | // github.com/juju/errors/annotation_test.go:193: 283 | // github.com/juju/errors/annotation_test.go:194: annotation 284 | // github.com/juju/errors/annotation_test.go:195: 285 | // github.com/juju/errors/annotation_test.go:196: more context 286 | // github.com/juju/errors/annotation_test.go:197: 287 | func ErrorStack(err error) string { 288 | return strings.Join(errorStack(err), "\n") 289 | } 290 | 291 | func errorStack(err error) []string { 292 | if err == nil { 293 | return nil 294 | } 295 | 296 | // We want the first error first 297 | var lines []string 298 | for { 299 | var buff []byte 300 | if err, ok := err.(Locationer); ok { 301 | file, line := err.Location() 302 | // Strip off the leading GOPATH/src path elements. 303 | if file != "" { 304 | buff = append(buff, fmt.Sprintf("%s:%d", file, line)...) 305 | buff = append(buff, ": "...) 306 | } 307 | } 308 | if cerr, ok := err.(wrapper); ok { 309 | message := cerr.Message() 310 | buff = append(buff, message...) 311 | // If there is a cause for this error, and it is different to the cause 312 | // of the underlying error, then output the error string in the stack trace. 313 | var cause error 314 | if err1, ok := err.(causer); ok { 315 | cause = err1.Cause() 316 | } 317 | err = cerr.Underlying() 318 | if cause != nil && !sameError(Cause(err), cause) { 319 | if message != "" { 320 | buff = append(buff, ": "...) 321 | } 322 | buff = append(buff, cause.Error()...) 323 | } 324 | } else { 325 | buff = append(buff, err.Error()...) 326 | err = nil 327 | } 328 | lines = append(lines, string(buff)) 329 | if err == nil { 330 | break 331 | } 332 | } 333 | // reverse the lines to get the original error, which was at the end of 334 | // the list, back to the start. 335 | var result []string 336 | for i := len(lines); i > 0; i-- { 337 | result = append(result, lines[i-1]) 338 | } 339 | return result 340 | } 341 | 342 | // Unwrap is a proxy for the Unwrap function in Go's standard `errors` library 343 | // (pkg.go.dev/errors). 344 | func Unwrap(err error) error { 345 | return stderrors.Unwrap(err) 346 | } 347 | 348 | // Is is a proxy for the Is function in Go's standard `errors` library 349 | // (pkg.go.dev/errors). 350 | func Is(err, target error) bool { 351 | return stderrors.Is(err, target) 352 | } 353 | 354 | // HasType is a function wrapper around AsType dropping the where return value 355 | // from AsType() making a function that can be used like this: 356 | // 357 | // return HasType[*MyError](err) 358 | // 359 | // Or 360 | // 361 | // if HasType[*MyError](err) {} 362 | func HasType[T error](err error) bool { 363 | _, rval := AsType[T](err) 364 | return rval 365 | } 366 | 367 | // As is a proxy for the As function in Go's standard `errors` library 368 | // (pkg.go.dev/errors). 369 | func As(err error, target interface{}) bool { 370 | return stderrors.As(err, target) 371 | } 372 | 373 | // AsType is a convenience method for checking and getting an error from within 374 | // a chain that is of type T. If no error is found of type T in the chain the 375 | // zero value of T is returned with false. If an error in the chain implementes 376 | // As(any) bool then it's As method will be called if it's type is not of type T. 377 | 378 | // AsType finds the first error in err's chain that is assignable to type T, and 379 | // if a match is found, returns that error value and true. Otherwise, it returns 380 | // T's zero value and false. 381 | // 382 | // AsType is equivalent to errors.As, but uses a type parameter and returns 383 | // the target, to avoid having to define a variable before the call. For 384 | // example, callers can replace this: 385 | // 386 | // var pathError *fs.PathError 387 | // if errors.As(err, &pathError) { 388 | // fmt.Println("Failed at path:", pathError.Path) 389 | // } 390 | // 391 | // With: 392 | // 393 | // if pathError, ok := errors.AsType[*fs.PathError](err); ok { 394 | // fmt.Println("Failed at path:", pathError.Path) 395 | // } 396 | func AsType[T error](err error) (T, bool) { 397 | for err != nil { 398 | if e, is := err.(T); is { 399 | return e, true 400 | } 401 | var res T 402 | if x, ok := err.(interface{ As(any) bool }); ok && x.As(&res) { 403 | return res, true 404 | } 405 | err = stderrors.Unwrap(err) 406 | } 407 | var zero T 408 | return zero, false 409 | } 410 | 411 | // SetLocation takes a given error and records where in the stack SetLocation 412 | // was called from and returns the wrapped error with the location information 413 | // set. The returned error implements the Locationer interface. If err is nil 414 | // then a nil error is returned. 415 | func SetLocation(err error, callDepth int) error { 416 | if err == nil { 417 | return nil 418 | } 419 | 420 | return newLocationError(err, callDepth) 421 | } 422 | 423 | // fmtNoop provides an internal type for wrapping errors so they won't be 424 | // printed in fmt type commands. As this type is used by the Hide function it's 425 | // expected that error not be nil. 426 | type fmtNoop struct { 427 | error 428 | } 429 | 430 | // Format implements the fmt.Formatter interface so that the error wrapped by 431 | // fmtNoop will not be printed. 432 | func (*fmtNoop) Format(_ fmt.State, r rune) {} 433 | 434 | // Is implements errors.Is. It useful for us to be able to check if an error 435 | // chain has fmtNoop for formatting purposes. 436 | func (f *fmtNoop) Is(err error) bool { 437 | _, is := err.(*fmtNoop) 438 | return is 439 | } 440 | 441 | // Unwrap implements the errors.Unwrap method returning the error wrapped by 442 | // fmtNoop. 443 | func (f *fmtNoop) Unwrap() error { 444 | return f.error 445 | } 446 | 447 | // Hide takes an error and silences it's error string from appearing in fmt 448 | // like 449 | func Hide(err error) error { 450 | if err == nil { 451 | return nil 452 | } 453 | return &fmtNoop{err} 454 | } 455 | -------------------------------------------------------------------------------- /functions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | stderrors "errors" 8 | "fmt" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "strings" 14 | 15 | gc "gopkg.in/check.v1" 16 | 17 | "github.com/juju/errors" 18 | ) 19 | 20 | type functionSuite struct { 21 | } 22 | 23 | var _ = gc.Suite(&functionSuite{}) 24 | 25 | func (*functionSuite) TestNew(c *gc.C) { 26 | err := errors.New("testing") 27 | loc := errorLocationValue(c) 28 | 29 | c.Assert(err.Error(), gc.Equals, "testing") 30 | c.Assert(errors.Cause(err), gc.Equals, err) 31 | c.Assert(errors.Details(err), Contains, loc) 32 | } 33 | 34 | func (*functionSuite) TestErrorf(c *gc.C) { 35 | err := errors.Errorf("testing %d", 42) 36 | loc := errorLocationValue(c) 37 | 38 | c.Assert(err.Error(), gc.Equals, "testing 42") 39 | c.Assert(errors.Cause(err), gc.Equals, err) 40 | c.Assert(errors.Details(err), Contains, loc) 41 | } 42 | 43 | func (*functionSuite) TestTrace(c *gc.C) { 44 | first := errors.New("first") 45 | err := errors.Trace(first) 46 | loc := errorLocationValue(c) 47 | 48 | c.Assert(err.Error(), gc.Equals, "first") 49 | c.Assert(errors.Is(err, first), gc.Equals, true) 50 | c.Assert(errors.Details(err), Contains, loc) 51 | 52 | c.Assert(errors.Trace(nil), gc.IsNil) 53 | } 54 | 55 | func (*functionSuite) TestAnnotate(c *gc.C) { 56 | first := errors.New("first") 57 | err := errors.Annotate(first, "annotation") 58 | loc := errorLocationValue(c) 59 | 60 | c.Assert(err.Error(), gc.Equals, "annotation: first") 61 | c.Assert(errors.Cause(err), gc.Equals, first) 62 | c.Assert(errors.Details(err), Contains, loc) 63 | 64 | c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil) 65 | } 66 | 67 | func (*functionSuite) TestAnnotatef(c *gc.C) { 68 | first := errors.New("first") 69 | err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest 70 | loc := errorLocationValue(c) 71 | 72 | c.Assert(err.Error(), gc.Equals, "annotation 2: first") 73 | c.Assert(errors.Cause(err), gc.Equals, first) 74 | c.Assert(errors.Details(err), Contains, loc) 75 | 76 | c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil) 77 | } 78 | 79 | func (*functionSuite) TestDeferredAnnotatef(c *gc.C) { 80 | // NOTE: this test fails with gccgo 81 | if runtime.Compiler == "gccgo" { 82 | c.Skip("gccgo can't determine the location") 83 | } 84 | first := errors.New("first") 85 | test := func() (err error) { 86 | defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate") 87 | return first 88 | } 89 | err := test() 90 | c.Assert(err.Error(), gc.Equals, "deferred annotate: first") 91 | c.Assert(errors.Cause(err), gc.Equals, first) 92 | 93 | err = nil 94 | errors.DeferredAnnotatef(&err, "deferred %s", "annotate") 95 | c.Assert(err, gc.IsNil) 96 | } 97 | 98 | func (*functionSuite) TestWrap(c *gc.C) { 99 | first := errors.New("first") 100 | firstLoc := errorLocationValue(c) 101 | 102 | detailed := errors.New("detailed") 103 | err := errors.Wrap(first, detailed) 104 | secondLoc := errorLocationValue(c) 105 | 106 | c.Assert(err.Error(), gc.Equals, "detailed") 107 | c.Assert(errors.Cause(err), gc.Equals, detailed) 108 | c.Assert(errors.Details(err), Contains, firstLoc) 109 | c.Assert(errors.Details(err), Contains, secondLoc) 110 | } 111 | 112 | func (*functionSuite) TestWrapOfNil(c *gc.C) { 113 | detailed := errors.New("detailed") 114 | err := errors.Wrap(nil, detailed) 115 | loc := errorLocationValue(c) 116 | c.Assert(err.Error(), gc.Equals, "detailed") 117 | c.Assert(errors.Cause(err), gc.Equals, detailed) 118 | c.Assert(errors.Details(err), Contains, loc) 119 | } 120 | 121 | func (*functionSuite) TestWrapf(c *gc.C) { 122 | first := errors.New("first") 123 | firstLoc := errorLocationValue(c) 124 | detailed := errors.New("detailed") 125 | err := errors.Wrapf(first, detailed, "value %d", 42) 126 | secondLoc := errorLocationValue(c) 127 | c.Assert(err.Error(), gc.Equals, "value 42: detailed") 128 | c.Assert(errors.Cause(err), gc.Equals, detailed) 129 | c.Assert(errors.Details(err), Contains, firstLoc) 130 | c.Assert(errors.Details(err), Contains, secondLoc) 131 | } 132 | 133 | func (*functionSuite) TestWrapfOfNil(c *gc.C) { 134 | detailed := errors.New("detailed") 135 | err := errors.Wrapf(nil, detailed, "value %d", 42) 136 | loc := errorLocationValue(c) 137 | c.Assert(err.Error(), gc.Equals, "value 42: detailed") 138 | c.Assert(errors.Cause(err), gc.Equals, detailed) 139 | c.Assert(errors.Details(err), Contains, loc) 140 | } 141 | 142 | func (*functionSuite) TestMask(c *gc.C) { 143 | first := errors.New("first") 144 | err := errors.Mask(first) 145 | loc := errorLocationValue(c) 146 | c.Assert(err.Error(), gc.Equals, "first") 147 | c.Assert(errors.Cause(err), gc.Equals, err) 148 | c.Assert(errors.Details(err), Contains, loc) 149 | 150 | c.Assert(errors.Mask(nil), gc.IsNil) 151 | } 152 | 153 | func (*functionSuite) TestMaskf(c *gc.C) { 154 | first := errors.New("first") 155 | err := errors.Maskf(first, "masked %d", 42) 156 | loc := errorLocationValue(c) 157 | c.Assert(err.Error(), gc.Equals, "masked 42: first") 158 | c.Assert(errors.Cause(err), gc.Equals, err) 159 | c.Assert(errors.Details(err), Contains, loc) 160 | 161 | c.Assert(errors.Maskf(nil, "mask"), gc.IsNil) 162 | } 163 | 164 | func (*functionSuite) TestCause(c *gc.C) { 165 | c.Assert(errors.Cause(nil), gc.IsNil) 166 | c.Assert(errors.Cause(someErr), gc.Equals, someErr) 167 | 168 | fmtErr := fmt.Errorf("simple") 169 | c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr) 170 | 171 | err := errors.Wrap(someErr, fmtErr) 172 | c.Assert(errors.Cause(err), gc.Equals, fmtErr) 173 | 174 | err = errors.Annotate(err, "annotated") 175 | c.Assert(errors.Cause(err), gc.Equals, fmtErr) 176 | 177 | err = errors.Maskf(err, "masked") 178 | c.Assert(errors.Cause(err), gc.Equals, err) 179 | 180 | // Look for a file that we know isn't there. 181 | dir := c.MkDir() 182 | _, err = os.Stat(filepath.Join(dir, "not-there")) 183 | c.Assert(os.IsNotExist(err), gc.Equals, true) 184 | 185 | err = errors.Annotatef(err, "wrap it") 186 | // Now the error itself isn't a 'IsNotExist'. 187 | c.Assert(os.IsNotExist(err), gc.Equals, false) 188 | // However if we use the Check method, it is. 189 | c.Assert(os.IsNotExist(errors.Cause(err)), gc.Equals, true) 190 | } 191 | 192 | type tracer interface { 193 | StackTrace() []string 194 | } 195 | 196 | func (*functionSuite) TestErrorStack(c *gc.C) { 197 | for i, test := range []struct { 198 | message string 199 | generator func(*gc.C, io.Writer) error 200 | tracer bool 201 | }{{ 202 | message: "nil", 203 | generator: func(_ *gc.C, _ io.Writer) error { 204 | return nil 205 | }, 206 | }, { 207 | message: "raw error", 208 | generator: func(c *gc.C, expected io.Writer) error { 209 | fmt.Fprint(expected, "raw") 210 | return fmt.Errorf("raw") 211 | }, 212 | }, { 213 | message: "single error stack", 214 | generator: func(c *gc.C, expected io.Writer) error { 215 | err := errors.New("first error") 216 | fmt.Fprintf(expected, "%s: first error", errorLocationValue(c)) 217 | return err 218 | }, 219 | tracer: true, 220 | }, { 221 | message: "annotated error", 222 | generator: func(c *gc.C, expected io.Writer) error { 223 | err := errors.New("first error") 224 | fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) 225 | err = errors.Annotate(err, "annotation") 226 | fmt.Fprintf(expected, "%s: annotation", errorLocationValue(c)) 227 | return err 228 | }, 229 | tracer: true, 230 | }, { 231 | message: "wrapped error", 232 | generator: func(c *gc.C, expected io.Writer) error { 233 | err := errors.New("first error") 234 | fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) 235 | err = errors.Wrap(err, newError("detailed error")) 236 | fmt.Fprintf(expected, "%s: detailed error", errorLocationValue(c)) 237 | return err 238 | }, 239 | tracer: true, 240 | }, { 241 | message: "annotated wrapped error", 242 | generator: func(c *gc.C, expected io.Writer) error { 243 | err := errors.Errorf("first error") 244 | fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) 245 | err = errors.Wrap(err, fmt.Errorf("detailed error")) 246 | fmt.Fprintf(expected, "%s: detailed error\n", errorLocationValue(c)) 247 | err = errors.Annotatef(err, "annotated") 248 | fmt.Fprintf(expected, "%s: annotated", errorLocationValue(c)) 249 | return err 250 | }, 251 | tracer: true, 252 | }, { 253 | message: "traced, and annotated", 254 | generator: func(c *gc.C, expected io.Writer) error { 255 | err := errors.New("first error") 256 | fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) 257 | err = errors.Trace(err) 258 | fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) 259 | err = errors.Annotate(err, "some context") 260 | fmt.Fprintf(expected, "%s: some context\n", errorLocationValue(c)) 261 | err = errors.Trace(err) 262 | fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) 263 | err = errors.Annotate(err, "more context") 264 | fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c)) 265 | err = errors.Trace(err) 266 | fmt.Fprintf(expected, "%s: ", errorLocationValue(c)) 267 | return err 268 | }, 269 | tracer: true, 270 | }, { 271 | message: "uncomparable, wrapped with a value error", 272 | generator: func(c *gc.C, expected io.Writer) error { 273 | err := newNonComparableError("first error") 274 | fmt.Fprintln(expected, "first error") 275 | err = errors.Trace(err) 276 | fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) 277 | err = errors.Wrap(err, newError("value error")) 278 | fmt.Fprintf(expected, "%s: value error\n", errorLocationValue(c)) 279 | err = errors.Maskf(err, "masked") 280 | fmt.Fprintf(expected, "%s: masked\n", errorLocationValue(c)) 281 | err = errors.Annotate(err, "more context") 282 | fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c)) 283 | err = errors.Trace(err) 284 | fmt.Fprintf(expected, "%s: ", errorLocationValue(c)) 285 | return err 286 | }, 287 | tracer: true, 288 | }} { 289 | c.Logf("%v: %s", i, test.message) 290 | expected := strings.Builder{} 291 | err := test.generator(c, &expected) 292 | stack := errors.ErrorStack(err) 293 | ok := c.Check(stack, gc.Equals, expected.String()) 294 | if !ok { 295 | c.Logf("%#v", err) 296 | } 297 | tracer, ok := err.(tracer) 298 | c.Check(ok, gc.Equals, test.tracer) 299 | if ok { 300 | stackTrace := tracer.StackTrace() 301 | c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n")) 302 | } 303 | } 304 | } 305 | 306 | func (*functionSuite) TestFormat(c *gc.C) { 307 | formatErrorExpected := &strings.Builder{} 308 | err := errors.New("TestFormat") 309 | fmt.Fprintf(formatErrorExpected, "%s: TestFormat\n", errorLocationValue(c)) 310 | err = errors.Mask(err) 311 | fmt.Fprintf(formatErrorExpected, "%s: ", errorLocationValue(c)) 312 | 313 | for i, test := range []struct { 314 | format string 315 | expect string 316 | }{{ 317 | format: "%s", 318 | expect: "TestFormat", 319 | }, { 320 | format: "%v", 321 | expect: "TestFormat", 322 | }, { 323 | format: "%q", 324 | expect: `"TestFormat"`, 325 | }, { 326 | format: "%A", 327 | expect: `%!A(*errors.Err=TestFormat)`, 328 | }, { 329 | format: "%+v", 330 | expect: formatErrorExpected.String(), 331 | }} { 332 | c.Logf("test %d: %q", i, test.format) 333 | s := fmt.Sprintf(test.format, err) 334 | c.Check(s, gc.Equals, test.expect) 335 | } 336 | } 337 | 338 | type basicError struct { 339 | Reason string 340 | } 341 | 342 | func (b *basicError) Error() string { 343 | return b.Reason 344 | } 345 | 346 | func (*functionSuite) TestAs(c *gc.C) { 347 | baseError := &basicError{"I'm an error"} 348 | testErrors := []error{ 349 | errors.Trace(baseError), 350 | errors.Annotate(baseError, "annotation"), 351 | errors.Wrap(baseError, errors.New("wrapper")), 352 | errors.Mask(baseError), 353 | } 354 | 355 | for _, err := range testErrors { 356 | bError := &basicError{} 357 | val := errors.As(err, &bError) 358 | c.Check(val, gc.Equals, true) 359 | c.Check(bError.Reason, gc.Equals, "I'm an error") 360 | } 361 | } 362 | 363 | func (*functionSuite) TestIs(c *gc.C) { 364 | baseError := &basicError{"I'm an error"} 365 | testErrors := []error{ 366 | errors.Trace(baseError), 367 | errors.Annotate(baseError, "annotation"), 368 | errors.Wrap(baseError, errors.New("wrapper")), 369 | errors.Mask(baseError), 370 | } 371 | 372 | for _, err := range testErrors { 373 | val := errors.Is(err, baseError) 374 | c.Check(val, gc.Equals, true) 375 | } 376 | } 377 | 378 | func (*functionSuite) TestSetLocationWithNilError(c *gc.C) { 379 | c.Assert(errors.SetLocation(nil, 1), gc.IsNil) 380 | } 381 | 382 | func (*functionSuite) TestSetLocation(c *gc.C) { 383 | err := errors.New("test") 384 | err = errors.SetLocation(err, 1) 385 | stack := fmt.Sprintf("%s: test", errorLocationValue(c)) 386 | _, implements := err.(errors.Locationer) 387 | c.Assert(implements, gc.Equals, true) 388 | 389 | c.Check(errors.ErrorStack(err), gc.Equals, stack) 390 | } 391 | 392 | func (*functionSuite) TestHideErrorStillReturnsErrorString(c *gc.C) { 393 | err := stderrors.New("This is a simple error") 394 | err = errors.Hide(err) 395 | 396 | c.Assert(err.Error(), gc.Equals, "This is a simple error") 397 | } 398 | 399 | func (*functionSuite) TestQuietWrappedErrorStillSatisfied(c *gc.C) { 400 | simpleTestError := errors.ConstError("I am a teapot") 401 | err := fmt.Errorf("fill me up%w", errors.Hide(simpleTestError)) 402 | c.Assert(err.Error(), gc.Equals, "fill me up") 403 | c.Assert(errors.Is(err, simpleTestError), gc.Equals, true) 404 | } 405 | 406 | type ComplexErrorMessage interface { 407 | error 408 | ComplexMessage() string 409 | } 410 | 411 | type complexError struct { 412 | Message string 413 | } 414 | 415 | func (c *complexError) Error() string { 416 | return c.Message 417 | } 418 | 419 | func (c *complexError) ComplexMessage() string { 420 | return c.Message 421 | } 422 | 423 | type complexErrorOther struct { 424 | Message string 425 | } 426 | 427 | func (c *complexErrorOther) As(e any) bool { 428 | if ce, ok := e.(**complexError); ok { 429 | *ce = &complexError{ 430 | Message: c.Message, 431 | } 432 | return true 433 | } 434 | return false 435 | } 436 | 437 | func (c *complexErrorOther) Error() string { 438 | return c.Message 439 | } 440 | 441 | func (c *complexErrorOther) ComplexMessage() string { 442 | return c.Message 443 | } 444 | 445 | func (*functionSuite) TestHasType(c *gc.C) { 446 | complexErr := &complexError{Message: "complex error message"} 447 | wrapped1 := fmt.Errorf("wrapping1: %w", complexErr) 448 | wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1) 449 | 450 | c.Assert(errors.HasType[*complexError](complexErr), gc.Equals, true) 451 | c.Assert(errors.HasType[*complexError](wrapped1), gc.Equals, true) 452 | c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true) 453 | c.Assert(errors.HasType[ComplexErrorMessage](wrapped2), gc.Equals, true) 454 | c.Assert(errors.HasType[*complexErrorOther](wrapped2), gc.Equals, false) 455 | c.Assert(errors.HasType[*complexErrorOther](nil), gc.Equals, false) 456 | 457 | complexErrOther := &complexErrorOther{Message: "another complex error"} 458 | 459 | c.Assert(errors.HasType[*complexError](complexErrOther), gc.Equals, true) 460 | 461 | wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther) 462 | c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true) 463 | } 464 | 465 | func (*functionSuite) TestAsType(c *gc.C) { 466 | complexErr := &complexError{Message: "complex error message"} 467 | wrapped1 := fmt.Errorf("wrapping1: %w", complexErr) 468 | wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1) 469 | 470 | ce, ok := errors.AsType[*complexError](complexErr) 471 | c.Assert(ok, gc.Equals, true) 472 | c.Assert(ce.Message, gc.Equals, complexErr.Message) 473 | 474 | ce, ok = errors.AsType[*complexError](wrapped1) 475 | c.Assert(ok, gc.Equals, true) 476 | c.Assert(ce.Message, gc.Equals, complexErr.Message) 477 | 478 | ce, ok = errors.AsType[*complexError](wrapped2) 479 | c.Assert(ok, gc.Equals, true) 480 | c.Assert(ce.Message, gc.Equals, complexErr.Message) 481 | 482 | cem, ok := errors.AsType[ComplexErrorMessage](wrapped2) 483 | c.Assert(ok, gc.Equals, true) 484 | c.Assert(cem.ComplexMessage(), gc.Equals, complexErr.Message) 485 | 486 | ceo, ok := errors.AsType[*complexErrorOther](wrapped2) 487 | c.Assert(ok, gc.Equals, false) 488 | c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil)) 489 | 490 | ceo, ok = errors.AsType[*complexErrorOther](nil) 491 | c.Assert(ok, gc.Equals, false) 492 | c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil)) 493 | 494 | complexErrOther := &complexErrorOther{Message: "another complex error"} 495 | ce, ok = errors.AsType[*complexError](complexErrOther) 496 | c.Assert(ok, gc.Equals, true) 497 | c.Assert(ce.Message, gc.Equals, complexErrOther.Message) 498 | 499 | wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther) 500 | ce, ok = errors.AsType[*complexError](wrapped2) 501 | c.Assert(ok, gc.Equals, true) 502 | c.Assert(ce.Message, gc.Equals, complexErrOther.Message) 503 | } 504 | 505 | func ExampleHide() { 506 | myConstError := errors.ConstError("I don't want to be fmt printed") 507 | err := fmt.Errorf("don't show this error%w", errors.Hide(myConstError)) 508 | 509 | fmt.Println(err) 510 | fmt.Println(stderrors.Is(err, myConstError)) 511 | 512 | // Output: 513 | // don't show this error 514 | // true 515 | } 516 | 517 | type MyError struct { 518 | Message string 519 | } 520 | 521 | func (m *MyError) Error() string { 522 | return m.Message 523 | } 524 | 525 | func ExampleHasType() { 526 | myErr := &MyError{Message: "these are not the droids you're looking for"} 527 | err := fmt.Errorf("wrapped: %w", myErr) 528 | is := errors.HasType[*MyError](err) 529 | fmt.Println(is) 530 | 531 | // Output: 532 | // true 533 | } 534 | 535 | func ExampleAsType() { 536 | myErr := &MyError{Message: "these are not the droids you're looking for"} 537 | err := fmt.Errorf("wrapped: %w", myErr) 538 | myErr, is := errors.AsType[*MyError](err) 539 | fmt.Println(is) 540 | fmt.Println(myErr.Message) 541 | 542 | // Output: 543 | // true 544 | // these are not the droids you're looking for 545 | } 546 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/juju/errors 2 | 3 | go 1.18 4 | 5 | require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 6 | 7 | require ( 8 | github.com/kr/pretty v0.2.1 // indirect 9 | github.com/kr/text v0.2.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 3 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 4 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 7 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 8 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 9 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 10 | -------------------------------------------------------------------------------- /package_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | "testing" 10 | 11 | gc "gopkg.in/check.v1" 12 | ) 13 | 14 | func Test(t *testing.T) { 15 | gc.TestingT(t) 16 | } 17 | 18 | // errorLocationValue provides the function name and line number for where this 19 | // function was called from - 1 line. What this means is that the returned value 20 | // will be homed to the file line directly above where this function was called. 21 | // This is a utility for testing error details and that associated error calls 22 | // set the error location correctly. 23 | func errorLocationValue(c *gc.C) string { 24 | rpc := make([]uintptr, 1) 25 | n := runtime.Callers(2, rpc[:]) 26 | if n < 1 { 27 | return "" 28 | } 29 | frame, _ := runtime.CallersFrames(rpc).Next() 30 | return fmt.Sprintf("%s:%d", frame.Function, frame.Line-1) 31 | } 32 | --------------------------------------------------------------------------------