├── .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 | [](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 |
--------------------------------------------------------------------------------