pax_global_header00006660000000000000000000000064142547110460014516gustar00rootroot0000000000000052 comment=54a94488269b924b361ff837cd38e75fb507e48c errors-1.0.0/000077500000000000000000000000001425471104600130305ustar00rootroot00000000000000errors-1.0.0/.github/000077500000000000000000000000001425471104600143705ustar00rootroot00000000000000errors-1.0.0/.github/workflows/000077500000000000000000000000001425471104600164255ustar00rootroot00000000000000errors-1.0.0/.github/workflows/gotest.yml000066400000000000000000000011531425471104600204550ustar00rootroot00000000000000name: Run `go test` on: [push, pull_request, workflow_dispatch] jobs: run-go-test: runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v3 - name: Find required go version id: go-version run: | set -euxo pipefail echo "::set-output name=version::$(grep '^go ' go.mod | awk '{print $2}')" - name: Install Golang uses: actions/setup-go@v2 with: # Gets go version from the previous step go-version: ${{ steps.go-version.outputs.version }} - name: Run test suite run: go test -v errors-1.0.0/.gitignore000066400000000000000000000004031425471104600150150ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test errors-1.0.0/LICENSE000066400000000000000000000215011425471104600140340ustar00rootroot00000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. errors-1.0.0/Makefile000066400000000000000000000014331425471104600144710ustar00rootroot00000000000000PROJECT := github.com/juju/errors .PHONY: check-licence check-go check docs check: check-licence check-go go test $(PROJECT)/... check-licence: @(fgrep -rl "Licensed under the LGPLv3" --exclude *.s .;\ fgrep -rl "MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT" --exclude *.s .;\ find . -name "*.go") | sed -e 's,\./,,' | sort | uniq -u | \ xargs -I {} echo FAIL: licence missed: {} check-go: $(eval GOFMT := $(strip $(shell gofmt -l .| sed -e "s/^/ /g"))) @(if [ x$(GOFMT) != x"" ]; then \ echo go fmt is sad: $(GOFMT); \ exit 1; \ fi ) @(go vet -all -composites=false -copylocks=false .) docs: godoc2md github.com/juju/errors > README.md sed -i '5i[\[GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)' README.md errors-1.0.0/README.md000066400000000000000000000400651425471104600143140ustar00rootroot00000000000000 # errors import "github.com/juju/errors" [![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors) The juju/errors provides an easy way to annotate errors without losing the original error context. The exported `New` and `Errorf` functions are designed to replace the `errors.New` and `fmt.Errorf` functions respectively. The same underlying error is there, but the package also records the location at which the error was created. A primary use case for this library is to add extra context any time an error is returned from a function. if err := SomeFunc(); err != nil { return err } This instead becomes: if err := SomeFunc(); err != nil { return errors.Trace(err) } which just records the file and line number of the Trace call, or if err := SomeFunc(); err != nil { return errors.Annotate(err, "more context") } which also adds an annotation to the error. When you want to check to see if an error is of a particular type, a helper function is normally exported by the package that returned the error, like the `os` package does. The underlying cause of the error is available using the `Cause` function. os.IsNotExist(errors.Cause(err)) The result of the `Error()` call on an annotated error is the annotations joined with colons, then the result of the `Error()` method for the underlying error that was the cause. err := errors.Errorf("original") err = errors.Annotatef(err, "context") err = errors.Annotatef(err, "more context") err.Error() -> "more context: context: original" Obviously recording the file, line and functions is not very useful if you cannot get them back out again. errors.ErrorStack(err) will return something like: first error github.com/juju/errors/annotation_test.go:193: github.com/juju/errors/annotation_test.go:194: annotation github.com/juju/errors/annotation_test.go:195: github.com/juju/errors/annotation_test.go:196: more context github.com/juju/errors/annotation_test.go:197: The first error was generated by an external system, so there was no location associated. The second, fourth, and last lines were generated with Trace calls, and the other two through Annotate. Sometimes when responding to an error you want to return a more specific error for the situation. if err := FindField(field); err != nil { return errors.Wrap(err, errors.NotFoundf(field)) } This returns an error where the complete error stack is still available, and `errors.Cause()` will return the `NotFound` error. ## func AlreadyExistsf ``` go func AlreadyExistsf(format string, args ...interface{}) error ``` AlreadyExistsf returns an error which satisfies IsAlreadyExists(). ## func Annotate ``` go func Annotate(other error, message string) error ``` Annotate is used to add extra context to an existing error. The location of the Annotate call is recorded with the annotations. The file, line and function are also recorded. For example: if err := SomeFunc(); err != nil { return errors.Annotate(err, "failed to frombulate") } ## func Annotatef ``` go func Annotatef(other error, format string, args ...interface{}) error ``` Annotatef is used to add extra context to an existing error. The location of the Annotate call is recorded with the annotations. The file, line and function are also recorded. For example: if err := SomeFunc(); err != nil { return errors.Annotatef(err, "failed to frombulate the %s", arg) } ## func BadRequestf ``` go func BadRequestf(format string, args ...interface{}) error ``` BadRequestf returns an error which satisfies IsBadRequest(). ## func Cause ``` go func Cause(err error) error ``` Cause returns the cause of the given error. This will be either the original error, or the result of a Wrap or Mask call. Cause is the usual way to diagnose errors that may have been wrapped by the other errors functions. ## func DeferredAnnotatef ``` go func DeferredAnnotatef(err *error, format string, args ...interface{}) ``` DeferredAnnotatef annotates the given error (when it is not nil) with the given format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef does nothing. This method is used in a defer statement in order to annotate any resulting error with the same message. For example: defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) ## func Details ``` go func Details(err error) string ``` Details returns information about the stack of errors wrapped by err, in the format: [{filename:99: error one} {otherfile:55: cause of error one}] This is a terse alternative to ErrorStack as it returns a single line. ## func ErrorStack ``` go func ErrorStack(err error) string ``` ErrorStack returns a string representation of the annotated error. If the error passed as the parameter is not an annotated error, the result is simply the result of the Error() method on that error. If the error is an annotated error, a multi-line string is returned where each line represents one entry in the annotation stack. The full filename from the call stack is used in the output. first error github.com/juju/errors/annotation_test.go:193: github.com/juju/errors/annotation_test.go:194: annotation github.com/juju/errors/annotation_test.go:195: github.com/juju/errors/annotation_test.go:196: more context github.com/juju/errors/annotation_test.go:197: ## func Errorf ``` go func Errorf(format string, args ...interface{}) error ``` Errorf creates a new annotated error and records the location that the error is created. This should be a drop in replacement for fmt.Errorf. For example: return errors.Errorf("validation failed: %s", message) ## func Forbiddenf ``` go func Forbiddenf(format string, args ...interface{}) error ``` Forbiddenf returns an error which satistifes IsForbidden() ## func IsAlreadyExists ``` go func IsAlreadyExists(err error) bool ``` IsAlreadyExists reports whether the error was created with AlreadyExistsf() or NewAlreadyExists(). ## func IsBadRequest ``` go func IsBadRequest(err error) bool ``` IsBadRequest reports whether err was created with BadRequestf() or NewBadRequest(). ## func IsForbidden ``` go func IsForbidden(err error) bool ``` IsForbidden reports whether err was created with Forbiddenf() or NewForbidden(). ## func IsMethodNotAllowed ``` go func IsMethodNotAllowed(err error) bool ``` IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or NewMethodNotAllowed(). ## func IsNotAssigned ``` go func IsNotAssigned(err error) bool ``` IsNotAssigned reports whether err was created with NotAssignedf() or NewNotAssigned(). ## func IsNotFound ``` go func IsNotFound(err error) bool ``` IsNotFound reports whether err was created with NotFoundf() or NewNotFound(). ## func IsNotImplemented ``` go func IsNotImplemented(err error) bool ``` IsNotImplemented reports whether err was created with NotImplementedf() or NewNotImplemented(). ## func IsNotProvisioned ``` go func IsNotProvisioned(err error) bool ``` IsNotProvisioned reports whether err was created with NotProvisionedf() or NewNotProvisioned(). ## func IsNotSupported ``` go func IsNotSupported(err error) bool ``` IsNotSupported reports whether the error was created with NotSupportedf() or NewNotSupported(). ## func IsNotValid ``` go func IsNotValid(err error) bool ``` IsNotValid reports whether the error was created with NotValidf() or NewNotValid(). ## func IsUnauthorized ``` go func IsUnauthorized(err error) bool ``` IsUnauthorized reports whether err was created with Unauthorizedf() or NewUnauthorized(). ## func IsUserNotFound ``` go func IsUserNotFound(err error) bool ``` IsUserNotFound reports whether err was created with UserNotFoundf() or NewUserNotFound(). ## func Mask ``` go func Mask(other error) error ``` Mask hides the underlying error type, and records the location of the masking. ## func Maskf ``` go func Maskf(other error, format string, args ...interface{}) error ``` Mask masks the given error with the given format string and arguments (like fmt.Sprintf), returning a new error that maintains the error stack, but hides the underlying error type. The error string still contains the full annotations. If you want to hide the annotations, call Wrap. ## func MethodNotAllowedf ``` go func MethodNotAllowedf(format string, args ...interface{}) error ``` MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed(). ## func New ``` go func New(message string) error ``` New is a drop in replacement for the standard library errors module that records the location that the error is created. For example: return errors.New("validation failed") ## func NewAlreadyExists ``` go func NewAlreadyExists(err error, msg string) error ``` NewAlreadyExists returns an error which wraps err and satisfies IsAlreadyExists(). ## func NewBadRequest ``` go func NewBadRequest(err error, msg string) error ``` NewBadRequest returns an error which wraps err that satisfies IsBadRequest(). ## func NewForbidden ``` go func NewForbidden(err error, msg string) error ``` NewForbidden returns an error which wraps err that satisfies IsForbidden(). ## func NewMethodNotAllowed ``` go func NewMethodNotAllowed(err error, msg string) error ``` NewMethodNotAllowed returns an error which wraps err that satisfies IsMethodNotAllowed(). ## func NewNotAssigned ``` go func NewNotAssigned(err error, msg string) error ``` NewNotAssigned returns an error which wraps err that satisfies IsNotAssigned(). ## func NewNotFound ``` go func NewNotFound(err error, msg string) error ``` NewNotFound returns an error which wraps err that satisfies IsNotFound(). ## func NewNotImplemented ``` go func NewNotImplemented(err error, msg string) error ``` NewNotImplemented returns an error which wraps err and satisfies IsNotImplemented(). ## func NewNotProvisioned ``` go func NewNotProvisioned(err error, msg string) error ``` NewNotProvisioned returns an error which wraps err that satisfies IsNotProvisioned(). ## func NewNotSupported ``` go func NewNotSupported(err error, msg string) error ``` NewNotSupported returns an error which wraps err and satisfies IsNotSupported(). ## func NewNotValid ``` go func NewNotValid(err error, msg string) error ``` NewNotValid returns an error which wraps err and satisfies IsNotValid(). ## func NewUnauthorized ``` go func NewUnauthorized(err error, msg string) error ``` NewUnauthorized returns an error which wraps err and satisfies IsUnauthorized(). ## func NewUserNotFound ``` go func NewUserNotFound(err error, msg string) error ``` NewUserNotFound returns an error which wraps err and satisfies IsUserNotFound(). ## func NotAssignedf ``` go func NotAssignedf(format string, args ...interface{}) error ``` NotAssignedf returns an error which satisfies IsNotAssigned(). ## func NotFoundf ``` go func NotFoundf(format string, args ...interface{}) error ``` NotFoundf returns an error which satisfies IsNotFound(). ## func NotImplementedf ``` go func NotImplementedf(format string, args ...interface{}) error ``` NotImplementedf returns an error which satisfies IsNotImplemented(). ## func NotProvisionedf ``` go func NotProvisionedf(format string, args ...interface{}) error ``` NotProvisionedf returns an error which satisfies IsNotProvisioned(). ## func NotSupportedf ``` go func NotSupportedf(format string, args ...interface{}) error ``` NotSupportedf returns an error which satisfies IsNotSupported(). ## func NotValidf ``` go func NotValidf(format string, args ...interface{}) error ``` NotValidf returns an error which satisfies IsNotValid(). ## func Trace ``` go func Trace(other error) error ``` Trace adds the location of the Trace call to the stack. The Cause of the resulting error is the same as the error parameter. If the other error is nil, the result will be nil. For example: if err := SomeFunc(); err != nil { return errors.Trace(err) } ## func Unauthorizedf ``` go func Unauthorizedf(format string, args ...interface{}) error ``` Unauthorizedf returns an error which satisfies IsUnauthorized(). ## func UserNotFoundf ``` go func UserNotFoundf(format string, args ...interface{}) error ``` UserNotFoundf returns an error which satisfies IsUserNotFound(). ## func Wrap ``` go func Wrap(other, newDescriptive error) error ``` Wrap changes the Cause of the error. The location of the Wrap call is also stored in the error stack. For example: if err := SomeFunc(); err != nil { newErr := &packageError{"more context", private_value} return errors.Wrap(err, newErr) } ## func Wrapf ``` go func Wrapf(other, newDescriptive error, format string, args ...interface{}) error ``` Wrapf changes the Cause of the error, and adds an annotation. The location of the Wrap call is also stored in the error stack. For example: if err := SomeFunc(); err != nil { return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) } ## type Err ``` go type Err struct { // contains filtered or unexported fields } ``` Err holds a description of an error along with information about where the error was created. It may be embedded in custom error types to add extra information that this errors package can understand. ### func NewErr ``` go func NewErr(format string, args ...interface{}) Err ``` NewErr is used to return an Err for the purpose of embedding in other structures. The location is not specified, and needs to be set with a call to SetLocation. For example: type FooError struct { errors.Err code int } func NewFooError(code int) error { err := &FooError{errors.NewErr("foo"), code} err.SetLocation(1) return err } ### func NewErrWithCause ``` go func NewErrWithCause(other error, format string, args ...interface{}) Err ``` NewErrWithCause is used to return an Err with cause by other error for the purpose of embedding in other structures. The location is not specified, and needs to be set with a call to SetLocation. For example: type FooError struct { errors.Err code int } func (e *FooError) Annotate(format string, args ...interface{}) error { err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code} err.SetLocation(1) return err }) ### func (\*Err) Cause ``` go func (e *Err) Cause() error ``` The Cause of an error is the most recent error in the error stack that meets one of these criteria: the original error that was raised; the new error that was passed into the Wrap function; the most recently masked error; or nil if the error itself is considered the Cause. Normally this method is not invoked directly, but instead through the Cause stand alone function. ### func (\*Err) Error ``` go func (e *Err) Error() string ``` Error implements error.Error. ### func (\*Err) Format ``` go func (e *Err) Format(s fmt.State, verb rune) ``` Format implements fmt.Formatter When printing errors with %+v it also prints the stack trace. %#v unsurprisingly will print the real underlying type. ### func (\*Err) Location ``` go func (e *Err) Location() (filename string, line int) ``` Location is the file and line of where the error was most recently created or annotated. ### func (\*Err) Message ``` go func (e *Err) Message() string ``` Message returns the message stored with the most recent location. This is the empty string if the most recent call was Trace, or the message stored with Annotate or Mask. ### func (\*Err) SetLocation ``` go func (e *Err) SetLocation(callDepth int) ``` SetLocation records the source location of the error at callDepth stack frames above the call. ### func (\*Err) StackTrace ``` go func (e *Err) StackTrace() []string ``` StackTrace returns one string for each location recorded in the stack of errors. The first value is the originating error, with a line for each other annotation or tracing of the error. ### func (\*Err) Underlying ``` go func (e *Err) Underlying() error ``` Underlying returns the previous error in the error stack, if any. A client should not ever really call this method. It is used to build the error stack and should not be introspected by client calls. Or more specifically, clients should not depend on anything but the `Cause` of an error. - - - Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) errors-1.0.0/checkers_test.go000066400000000000000000000050051425471104600162050ustar00rootroot00000000000000// Copyright 2022 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( "fmt" "reflect" "strings" gc "gopkg.in/check.v1" ) // containsChecker is a copy of the containsChecker from juju/testing type containsChecker struct { *gc.CheckerInfo } // satisfiesChecker is a copy of the satisfiesChecker from juju/testing type satisfiesChecker struct { *gc.CheckerInfo } // Contains is a copy of the Contains checker from juju/testing var Contains gc.Checker = &containsChecker{ &gc.CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}}, } // Satisfies is a copy of the Satisfies checker from juju/testing var Satisfies gc.Checker = &satisfiesChecker{ &gc.CheckerInfo{ Name: "Satisfies", Params: []string{"obtained", "func(T) bool"}, }, } // canBeNil is copied from juju/testing func canBeNil(t reflect.Type) bool { switch t.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: return true } return false } // Check is copied from juju/testing containsChecker func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, error string) { expected, ok := params[1].(string) if !ok { return false, "expected must be a string" } obtained, isString := stringOrStringer(params[0]) if isString { return strings.Contains(obtained, expected), "" } return false, "Obtained value is not a string and has no .String()" } // Check is copied from juju/testing satisfiesChecker func (checker *satisfiesChecker) Check(params []interface{}, names []string) (result bool, error string) { f := reflect.ValueOf(params[1]) ft := f.Type() if ft.Kind() != reflect.Func || ft.NumIn() != 1 || ft.NumOut() != 1 || ft.Out(0) != reflect.TypeOf(true) { return false, fmt.Sprintf("expected func(T) bool, got %s", ft) } v := reflect.ValueOf(params[0]) if !v.IsValid() { if !canBeNil(ft.In(0)) { return false, fmt.Sprintf("cannot assign nil to argument %T", ft.In(0)) } v = reflect.Zero(ft.In(0)) } if !v.Type().AssignableTo(ft.In(0)) { return false, fmt.Sprintf("wrong argument type %s for %s", v.Type(), ft) } return f.Call([]reflect.Value{v})[0].Interface().(bool), "" } // stringOrStringer is copied from juju/testing func stringOrStringer(value interface{}) (string, bool) { result, isString := value.(string) if !isString { if stringer, isStringer := value.(fmt.Stringer); isStringer { result, isString = stringer.String(), true } } return result, isString } errors-1.0.0/doc.go000066400000000000000000000047341425471104600141340ustar00rootroot00000000000000// Copyright 2013, 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. /* Package errors provides an easy way to annotate errors without losing the original error context. The exported `New` and `Errorf` functions are designed to replace the `errors.New` and `fmt.Errorf` functions respectively. The same underlying error is there, but the package also records the location at which the error was created. A primary use case for this library is to add extra context any time an error is returned from a function. if err := SomeFunc(); err != nil { return err } This instead becomes: if err := SomeFunc(); err != nil { return errors.Trace(err) } which just records the file and line number of the Trace call, or if err := SomeFunc(); err != nil { return errors.Annotate(err, "more context") } which also adds an annotation to the error. When you want to check to see if an error is of a particular type, a helper function is normally exported by the package that returned the error, like the `os` package does. The underlying cause of the error is available using the `Cause` function. os.IsNotExist(errors.Cause(err)) The result of the `Error()` call on an annotated error is the annotations joined with colons, then the result of the `Error()` method for the underlying error that was the cause. err := errors.Errorf("original") err = errors.Annotatef(err, "context") err = errors.Annotatef(err, "more context") err.Error() -> "more context: context: original" Obviously recording the file, line and functions is not very useful if you cannot get them back out again. errors.ErrorStack(err) will return something like: first error github.com/juju/errors/annotation_test.go:193: github.com/juju/errors/annotation_test.go:194: annotation github.com/juju/errors/annotation_test.go:195: github.com/juju/errors/annotation_test.go:196: more context github.com/juju/errors/annotation_test.go:197: The first error was generated by an external system, so there was no location associated. The second, fourth, and last lines were generated with Trace calls, and the other two through Annotate. Sometimes when responding to an error you want to return a more specific error for the situation. if err := FindField(field); err != nil { return errors.Wrap(err, errors.NotFoundf(field)) } This returns an error where the complete error stack is still available, and `errors.Cause()` will return the `NotFound` error. */ package errors errors-1.0.0/error.go000066400000000000000000000150001425471104600145040ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors import ( "fmt" "reflect" ) // Err holds a description of an error along with information about // where the error was created. // // It may be embedded in custom error types to add extra information that // this errors package can understand. type Err struct { // message holds an annotation of the error. message string // cause holds the cause of the error as returned // by the Cause method. cause error // previous holds the previous error in the error stack, if any. previous error // function is the package path-qualified function name where the // error was created. function string // line is the line number the error was created on inside of function line int } // Locationer is an interface that represents a certain class of errors that // contain the location information from where they were raised. type Locationer interface { // Location returns the path-qualified function name where the error was // created and the line number Location() (function string, line int) } // locationError is the internal implementation of the Locationer interface. type locationError struct { error // function is the package path-qualified function name where the // error was created. function string // line is the line number the error was created on inside of function line int } // newLocationError constructs a new Locationer error from the supplied error // with the location set to callDepth in the stack. If a nill error is provided // to this function then a new empty error is constructed. func newLocationError(err error, callDepth int) *locationError { le := &locationError{error: err} le.function, le.line = getLocation(callDepth + 1) return le } // Error implementes the error interface. func (l *locationError) Error() string { if l.error == nil { return "" } return l.error.Error() } // *locationError implements Locationer.Location interface func (l *locationError) Location() (string, int) { return l.function, l.line } func (l *locationError) Unwrap() error { return l.error } // NewErr is used to return an Err for the purpose of embedding in other // structures. The location is not specified, and needs to be set with a call // to SetLocation. // // For example: // type FooError struct { // errors.Err // code int // } // // func NewFooError(code int) error { // err := &FooError{errors.NewErr("foo"), code} // err.SetLocation(1) // return err // } func NewErr(format string, args ...interface{}) Err { return Err{ message: fmt.Sprintf(format, args...), } } // NewErrWithCause is used to return an Err with cause by other error for the purpose of embedding in other // structures. The location is not specified, and needs to be set with a call // to SetLocation. // // For example: // type FooError struct { // errors.Err // code int // } // // func (e *FooError) Annotate(format string, args ...interface{}) error { // err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code} // err.SetLocation(1) // return err // }) func NewErrWithCause(other error, format string, args ...interface{}) Err { return Err{ message: fmt.Sprintf(format, args...), cause: Cause(other), previous: other, } } // Location returns the package path-qualified function name and line of where // the error was most recently created or annotated. func (e *Err) Location() (function string, line int) { return e.function, e.line } // Underlying returns the previous error in the error stack, if any. A client // should not ever really call this method. It is used to build the error // stack and should not be introspected by client calls. Or more // specifically, clients should not depend on anything but the `Cause` of an // error. func (e *Err) Underlying() error { return e.previous } // Cause returns the most recent error in the error stack that // meets one of these criteria: the original error that was raised; the new // error that was passed into the Wrap function; the most recently masked // error; or nil if the error itself is considered the Cause. Normally this // method is not invoked directly, but instead through the Cause stand alone // function. func (e *Err) Cause() error { return e.cause } // Message returns the message stored with the most recent location. This is // the empty string if the most recent call was Trace, or the message stored // with Annotate or Mask. func (e *Err) Message() string { return e.message } // Error implements error.Error. func (e *Err) Error() string { // We want to walk up the stack of errors showing the annotations // as long as the cause is the same. err := e.previous if !sameError(Cause(err), e.cause) && e.cause != nil { err = e.cause } switch { case err == nil: return e.message case e.message == "": return err.Error() } return fmt.Sprintf("%s: %v", e.message, err) } // Format implements fmt.Formatter // When printing errors with %+v it also prints the stack trace. // %#v unsurprisingly will print the real underlying type. func (e *Err) Format(s fmt.State, verb rune) { switch verb { case 'v': switch { case s.Flag('+'): fmt.Fprintf(s, "%s", ErrorStack(e)) return case s.Flag('#'): // avoid infinite recursion by wrapping e into a type // that doesn't implement Formatter. fmt.Fprintf(s, "%#v", (*unformatter)(e)) return } fallthrough case 's': fmt.Fprintf(s, "%s", e.Error()) case 'q': fmt.Fprintf(s, "%q", e.Error()) default: fmt.Fprintf(s, "%%!%c(%T=%s)", verb, e, e.Error()) } } // helper for Format type unformatter Err func (unformatter) Format() { /* break the fmt.Formatter interface */ } // SetLocation records the package path-qualified function name of the error at // callDepth stack frames above the call. func (e *Err) SetLocation(callDepth int) { e.function, e.line = getLocation(callDepth + 1) } // StackTrace returns one string for each location recorded in the stack of // errors. The first value is the originating error, with a line for each // other annotation or tracing of the error. func (e *Err) StackTrace() []string { return errorStack(e) } // Ideally we'd have a way to check identity, but deep equals will do. func sameError(e1, e2 error) bool { return reflect.DeepEqual(e1, e2) } // Unwrap is a synonym for Underlying, which allows Err to be used with the // Unwrap, Is and As functions in Go's standard `errors` library. func (e *Err) Unwrap() error { return e.previous } errors-1.0.0/error_test.go000066400000000000000000000123351425471104600155530ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( "fmt" "runtime" gc "gopkg.in/check.v1" "github.com/juju/errors" ) type errorsSuite struct{} var _ = gc.Suite(&errorsSuite{}) var someErr = errors.New("some error") //err varSomeErr func (*errorsSuite) TestErrorString(c *gc.C) { for i, test := range []struct { message string generator func() error expected string }{ { message: "uncomparable errors", generator: func() error { err := errors.Annotatef(newNonComparableError("uncomparable"), "annotation") return errors.Annotatef(err, "another") }, expected: "another: annotation: uncomparable", }, { message: "Errorf", generator: func() error { return errors.Errorf("first error") }, expected: "first error", }, { message: "annotated error", generator: func() error { err := errors.Errorf("first error") return errors.Annotatef(err, "annotation") }, expected: "annotation: first error", }, { message: "test annotation format", generator: func() error { err := errors.Errorf("first %s", "error") return errors.Annotatef(err, "%s", "annotation") }, expected: "annotation: first error", }, { message: "wrapped error", generator: func() error { err := newError("first error") return errors.Wrap(err, newError("detailed error")) }, expected: "detailed error", }, { message: "wrapped annotated error", generator: func() error { err := errors.Errorf("first error") err = errors.Annotatef(err, "annotated") return errors.Wrap(err, fmt.Errorf("detailed error")) }, expected: "detailed error", }, { message: "annotated wrapped error", generator: func() error { err := errors.Errorf("first error") err = errors.Wrap(err, fmt.Errorf("detailed error")) return errors.Annotatef(err, "annotated") }, expected: "annotated: detailed error", }, { message: "traced, and annotated", generator: func() error { err := errors.New("first error") err = errors.Trace(err) err = errors.Annotate(err, "some context") err = errors.Trace(err) err = errors.Annotate(err, "more context") return errors.Trace(err) }, expected: "more context: some context: first error", }, { message: "traced, and annotated, masked and annotated", generator: func() error { err := errors.New("first error") err = errors.Trace(err) err = errors.Annotate(err, "some context") err = errors.Maskf(err, "masked") err = errors.Annotate(err, "more context") return errors.Trace(err) }, expected: "more context: masked: some context: first error", }, { message: "error traced then unwrapped", generator: func() error { err := errors.New("inner error") err = errors.Trace(err) return errors.Unwrap(err) }, expected: "inner error", }, { message: "error annotated then unwrapped", generator: func() error { err := errors.New("inner error") err = errors.Annotate(err, "annotation") return errors.Unwrap(err) }, expected: "inner error", }, { message: "error wrapped then unwrapped", generator: func() error { err := errors.New("inner error") err = errors.Wrap(err, errors.New("cause")) return errors.Unwrap(err) }, expected: "inner error", }, { message: "error masked then unwrapped", generator: func() error { err := errors.New("inner error") err = errors.Mask(err) return errors.Unwrap(err) }, expected: "inner error", }, } { c.Logf("%v: %s", i, test.message) err := test.generator() ok := c.Check(err.Error(), gc.Equals, test.expected) if !ok { c.Logf("%#v", test.generator()) } } } func (*errorsSuite) TestNewErr(c *gc.C) { if runtime.Compiler == "gccgo" { c.Skip("gccgo can't determine the location") } err := errors.NewErr("testing %d", 42) err.SetLocation(0) locLine := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "testing 42") c.Assert(errors.Cause(&err), gc.Equals, &err) c.Assert(errors.Details(&err), Contains, locLine) } func (*errorsSuite) TestNewErrWithCause(c *gc.C) { if runtime.Compiler == "gccgo" { c.Skip("gccgo can't determine the location") } causeErr := fmt.Errorf("external error") err := errors.NewErrWithCause(causeErr, "testing %d", 43) err.SetLocation(0) locLine := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "testing 43: external error") c.Assert(errors.Cause(&err), gc.Equals, causeErr) c.Assert(errors.Details(&err), Contains, locLine) } func (*errorsSuite) TestUnwrapNewErrGivesNil(c *gc.C) { err := errors.New("test error") c.Assert(errors.Unwrap(err), gc.IsNil) } // This is an uncomparable error type, as it is a struct that supports the // error interface (as opposed to a pointer type). type error_ struct { info string slice []string } // Create a non-comparable error func newNonComparableError(message string) error { return error_{info: message} } func (e error_) Error() string { return e.info } func newError(message string) error { return testError{message} } // The testError is a value type error for ease of seeing results // when the test fails. type testError struct { message string } func (e testError) Error() string { return e.message } errors-1.0.0/errortypes.go000066400000000000000000000344121425471104600156010ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors import ( "errors" stderror "errors" "fmt" "strings" ) // a ConstError is a prototype for a certain type of error type ConstError string // ConstError implements error func (e ConstError) Error() string { return string(e) } // Different types of errors const ( // Timeout represents an error on timeout. Timeout = ConstError("timeout") // NotFound represents an error when something has not been found. NotFound = ConstError("not found") // UserNotFound represents an error when a non-existent user is looked up. UserNotFound = ConstError("user not found") // Unauthorized represents an error when an operation is unauthorized. Unauthorized = ConstError("unauthorized") // NotImplemented represents an error when something is not // implemented. NotImplemented = ConstError("not implemented") // AlreadyExists represents and error when something already exists. AlreadyExists = ConstError("already exists") // NotSupported represents an error when something is not supported. NotSupported = ConstError("not supported") // NotValid represents an error when something is not valid. NotValid = ConstError("not valid") // NotProvisioned represents an error when something is not yet provisioned. NotProvisioned = ConstError("not provisioned") // NotAssigned represents an error when something is not yet assigned to // something else. NotAssigned = ConstError("not assigned") // BadRequest represents an error when a request has bad parameters. BadRequest = ConstError("bad request") // MethodNotAllowed represents an error when an HTTP request // is made with an inappropriate method. MethodNotAllowed = ConstError("method not allowed") // Forbidden represents an error when a request cannot be completed because of // missing privileges. Forbidden = ConstError("forbidden") // QuotaLimitExceeded is emitted when an action failed due to a quota limit check. QuotaLimitExceeded = ConstError("quota limit exceeded") // NotYetAvailable is the error returned when a resource is not yet available // but it might be in the future. NotYetAvailable = ConstError("not yet available") ) // errWithType is an Err bundled with its error type (a ConstError) type errWithType struct { error errType ConstError } // Is compares `target` with e's error type func (e *errWithType) Is(target error) bool { if &e.errType == nil { return false } return target == e.errType } // Unwrap an errWithType gives the underlying Err func (e *errWithType) Unwrap() error { return e.error } func wrapErrorWithMsg(err error, msg string) error { if err == nil { return stderror.New(msg) } if msg == "" { return err } return fmt.Errorf("%s: %w", msg, err) } func makeWrappedConstError(err error, format string, args ...interface{}) error { separator := " " if err.Error() == "" || errors.Is(err, &fmtNoop{}) { separator = "" } return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...) } // WithType is responsible for annotating an already existing error so that it // also satisfies that of a ConstError. The resultant error returned should // satisfy Is(err, errType). If err is nil then a nil error will also be returned. // // Now with Go's Is, As and Unwrap support it no longer makes sense to Wrap() // 2 errors as both of those errors could be chains of errors in their own right. // WithType aims to solve some of the usefulness of Wrap with the ability to // make a pre-existing error also satisfy a ConstError type. func WithType(err error, errType ConstError) error { if err == nil { return nil } return &errWithType{ error: err, errType: errType, } } // Timeoutf returns an error which satisfies Is(err, Timeout) and the Locationer // interface. func Timeoutf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Timeout, format, args...), 1, ) } // NewTimeout returns an error which wraps err and satisfies Is(err, Timeout) // and the Locationer interface. func NewTimeout(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: Timeout, } } // Deprecated: IsTimeout reports whether err is a Timeout error. Use // Is(err, Timeout). func IsTimeout(err error) bool { return Is(err, Timeout) } // NotFoundf returns an error which satisfies Is(err, NotFound) and the // Locationer interface. func NotFoundf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotFound, format, args...), 1, ) } // NewNotFound returns an error which wraps err and satisfies Is(err, NotFound) // and the Locationer interface. func NewNotFound(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotFound, } } // Deprecated: IsNotFound reports whether err is a NotFound error. Use // Is(err, NotFound). func IsNotFound(err error) bool { return Is(err, NotFound) } // UserNotFoundf returns an error which satisfies Is(err, UserNotFound) and the // Locationer interface. func UserNotFoundf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(UserNotFound, format, args...), 1, ) } // NewUserNotFound returns an error which wraps err and satisfies // Is(err, UserNotFound) and the Locationer interface. func NewUserNotFound(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: UserNotFound, } } // Deprecated: IsUserNotFound reports whether err is a UserNotFound error. Use // Is(err, UserNotFound). func IsUserNotFound(err error) bool { return Is(err, UserNotFound) } // Unauthorizedf returns an error that satisfies Is(err, Unauthorized) and // the Locationer interface. func Unauthorizedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(Unauthorized), format, args...), 1, ) } // NewUnauthorized returns an error which wraps err and satisfies // Is(err, Unathorized) and the Locationer interface. func NewUnauthorized(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: Unauthorized, } } // Deprecated: IsUnauthorized reports whether err is a Unauthorized error. Use // Is(err, Unauthorized). func IsUnauthorized(err error) bool { return Is(err, Unauthorized) } // NotImplementedf returns an error which satisfies Is(err, NotImplemented) and // the Locationer interface. func NotImplementedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotImplemented, format, args...), 1, ) } // NewNotImplemented returns an error which wraps err and satisfies // Is(err, NotImplemented) and the Locationer interface. func NewNotImplemented(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotImplemented, } } // Deprecated: IsNotImplemented reports whether err is a NotImplemented error. // Use Is(err, NotImplemented). func IsNotImplemented(err error) bool { return Is(err, NotImplemented) } // AlreadyExistsf returns an error which satisfies Is(err, AlreadyExists) and // the Locationer interface. func AlreadyExistsf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(AlreadyExists, format, args...), 1, ) } // NewAlreadyExists returns an error which wraps err and satisfies // Is(err, AlreadyExists) and the Locationer interface. func NewAlreadyExists(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: AlreadyExists, } } // Deprecated: IsAlreadyExists reports whether the err is a AlreadyExists // error. Use Is(err, AlreadyExists). func IsAlreadyExists(err error) bool { return Is(err, AlreadyExists) } // NotSupportedf returns an error which satisfies Is(err, NotSupported) and the // Locationer interface. func NotSupportedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotSupported, format, args...), 1, ) } // NewNotSupported returns an error which satisfies Is(err, NotSupported) and // the Locationer interface. func NewNotSupported(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotSupported, } } // Deprecated: IsNotSupported reports whether err is a NotSupported error. Use // Is(err, NotSupported). func IsNotSupported(err error) bool { return Is(err, NotSupported) } // NotValidf returns an error which satisfies Is(err, NotValid) and the // Locationer interface. func NotValidf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotValid, format, args...), 1, ) } // NewNotValid returns an error which wraps err and satisfies Is(err, NotValid) // and the Locationer interface. func NewNotValid(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotValid, } } // Deprecated: IsNotValid reports whether err is a NotValid error. Use // Is(err, NotValid). func IsNotValid(err error) bool { return Is(err, NotValid) } // NotProvisionedf returns an error which satisfies Is(err, NotProvisioned) and // the Locationer interface. func NotProvisionedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotProvisioned, format, args...), 1, ) } // NewNotProvisioned returns an error which wraps err and satisfies // Is(err, NotProvisioned) and the Locationer interface. func NewNotProvisioned(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotProvisioned, } } // Deprecated: IsNotProvisioned reports whether err is a NotProvisioned error. // Use Is(err, NotProvisioned). func IsNotProvisioned(err error) bool { return Is(err, NotProvisioned) } // NotAssignedf returns an error which satisfies Is(err, NotAssigned) and the // Locationer interface. func NotAssignedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(NotAssigned, format, args...), 1, ) } // NewNotAssigned returns an error which wraps err and satisfies // Is(err, NotAssigned) and the Locationer interface. func NewNotAssigned(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotAssigned, } } // Deprecated: IsNotAssigned reports whether err is a NotAssigned error. // Use Is(err, NotAssigned) func IsNotAssigned(err error) bool { return Is(err, NotAssigned) } // BadRequestf returns an error which satisfies Is(err, BadRequest) and the // Locationer interface. func BadRequestf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(BadRequest), format, args...), 1, ) } // NewBadRequest returns an error which wraps err and satisfies // Is(err, BadRequest) and the Locationer interface. func NewBadRequest(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: BadRequest, } } // Deprecated: IsBadRequest reports whether err is a BadRequest error. // Use Is(err, BadRequest) func IsBadRequest(err error) bool { return Is(err, BadRequest) } // MethodNotAllowedf returns an error which satisfies Is(err, MethodNotAllowed) // and the Locationer interface. func MethodNotAllowedf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(MethodNotAllowed), format, args...), 1, ) } // NewMethodNotAllowed returns an error which wraps err and satisfies // Is(err, MethodNotAllowed) and the Locationer interface. func NewMethodNotAllowed(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: MethodNotAllowed, } } // Deprecated: IsMethodNotAllowed reports whether err is a MethodNotAllowed // error. Use Is(err, MethodNotAllowed) func IsMethodNotAllowed(err error) bool { return Is(err, MethodNotAllowed) } // Forbiddenf returns an error which satistifes Is(err, Forbidden) and the // Locationer interface. func Forbiddenf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(Forbidden), format, args...), 1, ) } // NewForbidden returns an error which wraps err and satisfies // Is(err, Forbidden) and the Locationer interface. func NewForbidden(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: Forbidden, } } // Deprecated: IsForbidden reports whether err is a Forbidden error. Use // Is(err, Forbidden). func IsForbidden(err error) bool { return Is(err, Forbidden) } // QuotaLimitExceededf returns an error which satisfies // Is(err, QuotaLimitExceeded) and the Locationer interface. func QuotaLimitExceededf(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(QuotaLimitExceeded), format, args...), 1, ) } // NewQuotaLimitExceeded returns an error which wraps err and satisfies // Is(err, QuotaLimitExceeded) and the Locationer interface. func NewQuotaLimitExceeded(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: QuotaLimitExceeded, } } // Deprecated: IsQuotaLimitExceeded reports whether err is a QuoteLimitExceeded // err. Use Is(err, QuotaLimitExceeded). func IsQuotaLimitExceeded(err error) bool { return Is(err, QuotaLimitExceeded) } // NotYetAvailablef returns an error which satisfies Is(err, NotYetAvailable) // and the Locationer interface. func NotYetAvailablef(format string, args ...interface{}) error { return newLocationError( makeWrappedConstError(Hide(NotYetAvailable), format, args...), 1, ) } // NewNotYetAvailable returns an error which wraps err and satisfies // Is(err, NotYetAvailable) and the Locationer interface. func NewNotYetAvailable(err error, msg string) error { return &errWithType{ error: newLocationError(wrapErrorWithMsg(err, msg), 1), errType: NotYetAvailable, } } // Deprecated: IsNotYetAvailable reports whether err is a NotYetAvailable err. // Use Is(err, NotYetAvailable) func IsNotYetAvailable(err error) bool { return Is(err, NotYetAvailable) } errors-1.0.0/errortypes_test.go000066400000000000000000000140721425471104600166400ustar00rootroot00000000000000// Copyright 2013, 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( stderrors "errors" "fmt" "github.com/juju/errors" gc "gopkg.in/check.v1" ) // errorInfo holds information about a single error type: its type // and name, wrapping and variable arguments constructors and message // suffix. type errorInfo struct { errType errors.ConstError errName string argsConstructor func(string, ...interface{}) error wrapConstructor func(error, string) error suffix string } // allErrors holds information for all defined errors. When adding new // errors, add them here as well to include them in tests. var allErrors = []*errorInfo{ {errors.Timeout, "Timeout", errors.Timeoutf, errors.NewTimeout, " timeout"}, {errors.NotFound, "NotFound", errors.NotFoundf, errors.NewNotFound, " not found"}, {errors.UserNotFound, "UserNotFound", errors.UserNotFoundf, errors.NewUserNotFound, " user not found"}, {errors.Unauthorized, "Unauthorized", errors.Unauthorizedf, errors.NewUnauthorized, ""}, {errors.NotImplemented, "NotImplemented", errors.NotImplementedf, errors.NewNotImplemented, " not implemented"}, {errors.AlreadyExists, "AlreadyExists", errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"}, {errors.NotSupported, "NotSupported", errors.NotSupportedf, errors.NewNotSupported, " not supported"}, {errors.NotValid, "NotValid", errors.NotValidf, errors.NewNotValid, " not valid"}, {errors.NotProvisioned, "NotProvisioned", errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"}, {errors.NotAssigned, "NotAssigned", errors.NotAssignedf, errors.NewNotAssigned, " not assigned"}, {errors.MethodNotAllowed, "MethodNotAllowed", errors.MethodNotAllowedf, errors.NewMethodNotAllowed, ""}, {errors.BadRequest, "BadRequest", errors.BadRequestf, errors.NewBadRequest, ""}, {errors.Forbidden, "Forbidden", errors.Forbiddenf, errors.NewForbidden, ""}, {errors.QuotaLimitExceeded, "QuotaLimitExceeded", errors.QuotaLimitExceededf, errors.NewQuotaLimitExceeded, ""}, {errors.NotYetAvailable, "NotYetAvailable", errors.NotYetAvailablef, errors.NewNotYetAvailable, ""}, } type errorTypeSuite struct{} var _ = gc.Suite(&errorTypeSuite{}) func (t *errorInfo) equal(t0 *errorInfo) bool { if t0 == nil { return false } return t == t0 } type errorTest struct { err error message string errInfo *errorInfo } func deferredAnnotatef(err error, format string, args ...interface{}) error { errors.DeferredAnnotatef(&err, format, args...) return err } func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) { if errInfo != nil { msg := fmt.Sprintf("Is(err, %s) should be TRUE when err := %#v", errInfo.errName, err) c.Check(errors.Is(err, errInfo.errType), gc.Equals, true, gc.Commentf(msg)) } } func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) { if errInfo != nil { msg := fmt.Sprintf("Is(err, %s) should be FALSE when err := %#v", errInfo.errName, err) c.Check(errors.Is(err, errInfo.errType), gc.Equals, false, gc.Commentf(msg)) } } func checkErrorMatches(c *gc.C, err error, message string, errInfo *errorInfo) { if message == "" { c.Check(err, gc.IsNil) c.Check(errInfo, gc.IsNil) } else { c.Check(err, gc.ErrorMatches, message) } } func runErrorTests(c *gc.C, errorTests []errorTest, checkMustSatisfy bool) { for i, t := range errorTests { c.Logf("test %d: %T: %v", i, t.err, t.err) checkErrorMatches(c, t.err, t.message, t.errInfo) if checkMustSatisfy { mustSatisfy(c, t.err, t.errInfo) } // Check all other satisfiers to make sure none match. for _, otherErrInfo := range allErrors { if checkMustSatisfy && otherErrInfo.equal(t.errInfo) { continue } mustNotSatisfy(c, t.err, otherErrInfo) } } } func (*errorTypeSuite) TestDeferredAnnotatef(c *gc.C) { // Ensure DeferredAnnotatef annotates the errors. errorTests := []errorTest{} for _, errInfo := range allErrors { errorTests = append(errorTests, []errorTest{{ deferredAnnotatef(nil, "comment"), "", nil, }, { deferredAnnotatef(stderrors.New("blast"), "comment"), "comment: blast", nil, }, { deferredAnnotatef(errInfo.argsConstructor("foo %d", 42), "comment %d", 69), "comment 69: foo 42" + errInfo.suffix, errInfo, }, { deferredAnnotatef(errInfo.argsConstructor(""), "comment"), "comment: " + errInfo.suffix, errInfo, }, { deferredAnnotatef(errInfo.wrapConstructor(stderrors.New("pow!"), "woo"), "comment"), "comment: woo: pow!", errInfo, }}...) } runErrorTests(c, errorTests, true) } func (*errorTypeSuite) TestAllErrors(c *gc.C) { errorTests := []errorTest{} for _, errInfo := range allErrors { errorTests = append(errorTests, []errorTest{{ nil, "", nil, }, { errInfo.argsConstructor("foo %d", 42), "foo 42" + errInfo.suffix, errInfo, }, { errInfo.argsConstructor(""), errInfo.suffix, errInfo, }, { errInfo.wrapConstructor(stderrors.New("pow!"), "prefix"), "prefix: pow!", errInfo, }, { errInfo.wrapConstructor(stderrors.New("pow!"), ""), "pow!", errInfo, }, { errInfo.wrapConstructor(nil, "prefix"), "prefix", errInfo, }}...) } runErrorTests(c, errorTests, true) } // TestThatYouAlwaysGetError is a regression test for checking that the wrap // constructor for our error types always returns a valid error object even if // don't feed the construct with an instantiated error or a non empty string. func (*errorTypeSuite) TestThatYouAlwaysGetError(c *gc.C) { for _, errType := range allErrors { err := errType.wrapConstructor(nil, "") c.Assert(err.Error(), gc.Equals, "") } } func (*errorTypeSuite) TestWithTypeNil(c *gc.C) { myErr := errors.ConstError("do you feel lucky?") c.Assert(errors.WithType(nil, myErr), gc.IsNil) } func (*errorTypeSuite) TestWithType(c *gc.C) { myErr := errors.ConstError("do you feel lucky?") myErr2 := errors.ConstError("i don't feel lucky") err := errors.New("yes") err = errors.WithType(err, myErr) c.Assert(errors.Is(err, myErr), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "yes") c.Assert(errors.Is(err, myErr2), gc.Equals, false) } errors-1.0.0/example_test.go000066400000000000000000000007401425471104600160520ustar00rootroot00000000000000// Copyright 2013, 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( "fmt" "github.com/juju/errors" ) func ExampleTrace() { var err1 error = fmt.Errorf("something wicked this way comes") var err2 error = nil // Tracing a non nil error will return an error fmt.Println(errors.Trace(err1)) // Tracing nil will return nil fmt.Println(errors.Trace(err2)) // Output: something wicked this way comes // } errors-1.0.0/functions.go000066400000000000000000000275421425471104600154010ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors import ( stderrors "errors" "fmt" "runtime" "strings" ) // New is a drop in replacement for the standard library errors module that records // the location that the error is created. // // For example: // return errors.New("validation failed") // func New(message string) error { err := &Err{message: message} err.SetLocation(1) return err } // Errorf creates a new annotated error and records the location that the // error is created. This should be a drop in replacement for fmt.Errorf. // // For example: // return errors.Errorf("validation failed: %s", message) // func Errorf(format string, args ...interface{}) error { err := &Err{message: fmt.Sprintf(format, args...)} err.SetLocation(1) return err } // getLocation records the package path-qualified function name of the error at // callDepth stack frames above the call. func getLocation(callDepth int) (string, int) { rpc := make([]uintptr, 1) n := runtime.Callers(callDepth+2, rpc[:]) if n < 1 { return "", 0 } frame, _ := runtime.CallersFrames(rpc).Next() return frame.Function, frame.Line } // Trace adds the location of the Trace call to the stack. The Cause of the // resulting error is the same as the error parameter. If the other error is // nil, the result will be nil. // // For example: // if err := SomeFunc(); err != nil { // return errors.Trace(err) // } // func Trace(other error) error { //return SetLocation(other, 2) if other == nil { return nil } err := &Err{previous: other, cause: Cause(other)} err.SetLocation(1) return err } // Annotate is used to add extra context to an existing error. The location of // the Annotate call is recorded with the annotations. The file, line and // function are also recorded. // // For example: // if err := SomeFunc(); err != nil { // return errors.Annotate(err, "failed to frombulate") // } // func Annotate(other error, message string) error { if other == nil { return nil } err := &Err{ previous: other, cause: Cause(other), message: message, } err.SetLocation(1) return err } // Annotatef is used to add extra context to an existing error. The location of // the Annotate call is recorded with the annotations. The file, line and // function are also recorded. // // For example: // if err := SomeFunc(); err != nil { // return errors.Annotatef(err, "failed to frombulate the %s", arg) // } // func Annotatef(other error, format string, args ...interface{}) error { if other == nil { return nil } err := &Err{ previous: other, cause: Cause(other), message: fmt.Sprintf(format, args...), } err.SetLocation(1) return err } // DeferredAnnotatef annotates the given error (when it is not nil) with the given // format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef // does nothing. This method is used in a defer statement in order to annotate any // resulting error with the same message. // // For example: // // defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) // func DeferredAnnotatef(err *error, format string, args ...interface{}) { if *err == nil { return } newErr := &Err{ message: fmt.Sprintf(format, args...), cause: Cause(*err), previous: *err, } newErr.SetLocation(1) *err = newErr } // Wrap changes the Cause of the error. The location of the Wrap call is also // stored in the error stack. // // For example: // if err := SomeFunc(); err != nil { // newErr := &packageError{"more context", private_value} // return errors.Wrap(err, newErr) // } // func Wrap(other, newDescriptive error) error { err := &Err{ previous: other, cause: newDescriptive, } err.SetLocation(1) return err } // Wrapf changes the Cause of the error, and adds an annotation. The location // of the Wrap call is also stored in the error stack. // // For example: // if err := SomeFunc(); err != nil { // return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) // } // func Wrapf(other, newDescriptive error, format string, args ...interface{}) error { err := &Err{ message: fmt.Sprintf(format, args...), previous: other, cause: newDescriptive, } err.SetLocation(1) return err } // Maskf masks the given error with the given format string and arguments (like // fmt.Sprintf), returning a new error that maintains the error stack, but // hides the underlying error type. The error string still contains the full // annotations. If you want to hide the annotations, call Wrap. func Maskf(other error, format string, args ...interface{}) error { if other == nil { return nil } err := &Err{ message: fmt.Sprintf(format, args...), previous: other, } err.SetLocation(1) return err } // Mask hides the underlying error type, and records the location of the masking. func Mask(other error) error { if other == nil { return nil } err := &Err{ previous: other, } err.SetLocation(1) return err } // Cause returns the cause of the given error. This will be either the // original error, or the result of a Wrap or Mask call. // // Cause is the usual way to diagnose errors that may have been wrapped by // the other errors functions. func Cause(err error) error { var diag error if err, ok := err.(causer); ok { diag = err.Cause() } if diag != nil { return diag } return err } type causer interface { Cause() error } type wrapper interface { // Message returns the top level error message, // not including the message from the Previous // error. Message() string // Underlying returns the Previous error, or nil // if there is none. Underlying() error } var ( _ wrapper = (*Err)(nil) _ Locationer = (*Err)(nil) _ causer = (*Err)(nil) ) // Details returns information about the stack of errors wrapped by err, in // the format: // // [{filename:99: error one} {otherfile:55: cause of error one}] // // This is a terse alternative to ErrorStack as it returns a single line. func Details(err error) string { if err == nil { return "[]" } var s []byte s = append(s, '[') for { s = append(s, '{') if err, ok := err.(Locationer); ok { file, line := err.Location() if file != "" { s = append(s, fmt.Sprintf("%s:%d", file, line)...) s = append(s, ": "...) } } if cerr, ok := err.(wrapper); ok { s = append(s, cerr.Message()...) err = cerr.Underlying() } else { s = append(s, err.Error()...) err = nil } s = append(s, '}') if err == nil { break } s = append(s, ' ') } s = append(s, ']') return string(s) } // ErrorStack returns a string representation of the annotated error. If the // error passed as the parameter is not an annotated error, the result is // simply the result of the Error() method on that error. // // If the error is an annotated error, a multi-line string is returned where // each line represents one entry in the annotation stack. The full filename // from the call stack is used in the output. // // first error // github.com/juju/errors/annotation_test.go:193: // github.com/juju/errors/annotation_test.go:194: annotation // github.com/juju/errors/annotation_test.go:195: // github.com/juju/errors/annotation_test.go:196: more context // github.com/juju/errors/annotation_test.go:197: func ErrorStack(err error) string { return strings.Join(errorStack(err), "\n") } func errorStack(err error) []string { if err == nil { return nil } // We want the first error first var lines []string for { var buff []byte if err, ok := err.(Locationer); ok { file, line := err.Location() // Strip off the leading GOPATH/src path elements. if file != "" { buff = append(buff, fmt.Sprintf("%s:%d", file, line)...) buff = append(buff, ": "...) } } if cerr, ok := err.(wrapper); ok { message := cerr.Message() buff = append(buff, message...) // If there is a cause for this error, and it is different to the cause // of the underlying error, then output the error string in the stack trace. var cause error if err1, ok := err.(causer); ok { cause = err1.Cause() } err = cerr.Underlying() if cause != nil && !sameError(Cause(err), cause) { if message != "" { buff = append(buff, ": "...) } buff = append(buff, cause.Error()...) } } else { buff = append(buff, err.Error()...) err = nil } lines = append(lines, string(buff)) if err == nil { break } } // reverse the lines to get the original error, which was at the end of // the list, back to the start. var result []string for i := len(lines); i > 0; i-- { result = append(result, lines[i-1]) } return result } // Unwrap is a proxy for the Unwrap function in Go's standard `errors` library // (pkg.go.dev/errors). func Unwrap(err error) error { return stderrors.Unwrap(err) } // Is is a proxy for the Is function in Go's standard `errors` library // (pkg.go.dev/errors). func Is(err, target error) bool { return stderrors.Is(err, target) } // HasType is a function wrapper around AsType dropping the where return value // from AsType() making a function that can be used like this: // // return HasType[*MyError](err) // // Or // // if HasType[*MyError](err) {} func HasType[T error](err error) bool { _, rval := AsType[T](err) return rval } // As is a proxy for the As function in Go's standard `errors` library // (pkg.go.dev/errors). func As(err error, target interface{}) bool { return stderrors.As(err, target) } // AsType is a convenience method for checking and getting an error from within // a chain that is of type T. If no error is found of type T in the chain the // zero value of T is returned with false. If an error in the chain implementes // As(any) bool then it's As method will be called if it's type is not of type T. // AsType finds the first error in err's chain that is assignable to type T, and // if a match is found, returns that error value and true. Otherwise, it returns // T's zero value and false. // // AsType is equivalent to errors.As, but uses a type parameter and returns // the target, to avoid having to define a variable before the call. For // example, callers can replace this: // // var pathError *fs.PathError // if errors.As(err, &pathError) { // fmt.Println("Failed at path:", pathError.Path) // } // // With: // // if pathError, ok := errors.AsType[*fs.PathError](err); ok { // fmt.Println("Failed at path:", pathError.Path) // } func AsType[T error](err error) (T, bool) { for err != nil { if e, is := err.(T); is { return e, true } var res T if x, ok := err.(interface{ As(any) bool }); ok && x.As(&res) { return res, true } err = stderrors.Unwrap(err) } var zero T return zero, false } // SetLocation takes a given error and records where in the stack SetLocation // was called from and returns the wrapped error with the location information // set. The returned error implements the Locationer interface. If err is nil // then a nil error is returned. func SetLocation(err error, callDepth int) error { if err == nil { return nil } return newLocationError(err, callDepth) } // fmtNoop provides an internal type for wrapping errors so they won't be // printed in fmt type commands. As this type is used by the Hide function it's // expected that error not be nil. type fmtNoop struct { error } // Format implements the fmt.Formatter interface so that the error wrapped by // fmtNoop will not be printed. func (*fmtNoop) Format(_ fmt.State, r rune) {} // Is implements errors.Is. It useful for us to be able to check if an error // chain has fmtNoop for formatting purposes. func (f *fmtNoop) Is(err error) bool { _, is := err.(*fmtNoop) return is } // Unwrap implements the errors.Unwrap method returning the error wrapped by // fmtNoop. func (f *fmtNoop) Unwrap() error { return f.error } // Hide takes an error and silences it's error string from appearing in fmt // like func Hide(err error) error { if err == nil { return nil } return &fmtNoop{err} } errors-1.0.0/functions_test.go000066400000000000000000000364221425471104600164350ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( stderrors "errors" "fmt" "io" "os" "path/filepath" "runtime" "strings" gc "gopkg.in/check.v1" "github.com/juju/errors" ) type functionSuite struct { } var _ = gc.Suite(&functionSuite{}) func (*functionSuite) TestNew(c *gc.C) { err := errors.New("testing") loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "testing") c.Assert(errors.Cause(err), gc.Equals, err) c.Assert(errors.Details(err), Contains, loc) } func (*functionSuite) TestErrorf(c *gc.C) { err := errors.Errorf("testing %d", 42) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "testing 42") c.Assert(errors.Cause(err), gc.Equals, err) c.Assert(errors.Details(err), Contains, loc) } func (*functionSuite) TestTrace(c *gc.C) { first := errors.New("first") err := errors.Trace(first) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "first") c.Assert(errors.Is(err, first), gc.Equals, true) c.Assert(errors.Details(err), Contains, loc) c.Assert(errors.Trace(nil), gc.IsNil) } func (*functionSuite) TestAnnotate(c *gc.C) { first := errors.New("first") err := errors.Annotate(first, "annotation") loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "annotation: first") c.Assert(errors.Cause(err), gc.Equals, first) c.Assert(errors.Details(err), Contains, loc) c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil) } func (*functionSuite) TestAnnotatef(c *gc.C) { first := errors.New("first") err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "annotation 2: first") c.Assert(errors.Cause(err), gc.Equals, first) c.Assert(errors.Details(err), Contains, loc) c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil) } func (*functionSuite) TestDeferredAnnotatef(c *gc.C) { // NOTE: this test fails with gccgo if runtime.Compiler == "gccgo" { c.Skip("gccgo can't determine the location") } first := errors.New("first") test := func() (err error) { defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate") return first } err := test() c.Assert(err.Error(), gc.Equals, "deferred annotate: first") c.Assert(errors.Cause(err), gc.Equals, first) err = nil errors.DeferredAnnotatef(&err, "deferred %s", "annotate") c.Assert(err, gc.IsNil) } func (*functionSuite) TestWrap(c *gc.C) { first := errors.New("first") firstLoc := errorLocationValue(c) detailed := errors.New("detailed") err := errors.Wrap(first, detailed) secondLoc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "detailed") c.Assert(errors.Cause(err), gc.Equals, detailed) c.Assert(errors.Details(err), Contains, firstLoc) c.Assert(errors.Details(err), Contains, secondLoc) } func (*functionSuite) TestWrapOfNil(c *gc.C) { detailed := errors.New("detailed") err := errors.Wrap(nil, detailed) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "detailed") c.Assert(errors.Cause(err), gc.Equals, detailed) c.Assert(errors.Details(err), Contains, loc) } func (*functionSuite) TestWrapf(c *gc.C) { first := errors.New("first") firstLoc := errorLocationValue(c) detailed := errors.New("detailed") err := errors.Wrapf(first, detailed, "value %d", 42) secondLoc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "value 42: detailed") c.Assert(errors.Cause(err), gc.Equals, detailed) c.Assert(errors.Details(err), Contains, firstLoc) c.Assert(errors.Details(err), Contains, secondLoc) } func (*functionSuite) TestWrapfOfNil(c *gc.C) { detailed := errors.New("detailed") err := errors.Wrapf(nil, detailed, "value %d", 42) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "value 42: detailed") c.Assert(errors.Cause(err), gc.Equals, detailed) c.Assert(errors.Details(err), Contains, loc) } func (*functionSuite) TestMask(c *gc.C) { first := errors.New("first") err := errors.Mask(first) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "first") c.Assert(errors.Cause(err), gc.Equals, err) c.Assert(errors.Details(err), Contains, loc) c.Assert(errors.Mask(nil), gc.IsNil) } func (*functionSuite) TestMaskf(c *gc.C) { first := errors.New("first") err := errors.Maskf(first, "masked %d", 42) loc := errorLocationValue(c) c.Assert(err.Error(), gc.Equals, "masked 42: first") c.Assert(errors.Cause(err), gc.Equals, err) c.Assert(errors.Details(err), Contains, loc) c.Assert(errors.Maskf(nil, "mask"), gc.IsNil) } func (*functionSuite) TestCause(c *gc.C) { c.Assert(errors.Cause(nil), gc.IsNil) c.Assert(errors.Cause(someErr), gc.Equals, someErr) fmtErr := fmt.Errorf("simple") c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr) err := errors.Wrap(someErr, fmtErr) c.Assert(errors.Cause(err), gc.Equals, fmtErr) err = errors.Annotate(err, "annotated") c.Assert(errors.Cause(err), gc.Equals, fmtErr) err = errors.Maskf(err, "masked") c.Assert(errors.Cause(err), gc.Equals, err) // Look for a file that we know isn't there. dir := c.MkDir() _, err = os.Stat(filepath.Join(dir, "not-there")) c.Assert(os.IsNotExist(err), gc.Equals, true) err = errors.Annotatef(err, "wrap it") // Now the error itself isn't a 'IsNotExist'. c.Assert(os.IsNotExist(err), gc.Equals, false) // However if we use the Check method, it is. c.Assert(os.IsNotExist(errors.Cause(err)), gc.Equals, true) } type tracer interface { StackTrace() []string } func (*functionSuite) TestErrorStack(c *gc.C) { for i, test := range []struct { message string generator func(*gc.C, io.Writer) error tracer bool }{{ message: "nil", generator: func(_ *gc.C, _ io.Writer) error { return nil }, }, { message: "raw error", generator: func(c *gc.C, expected io.Writer) error { fmt.Fprint(expected, "raw") return fmt.Errorf("raw") }, }, { message: "single error stack", generator: func(c *gc.C, expected io.Writer) error { err := errors.New("first error") fmt.Fprintf(expected, "%s: first error", errorLocationValue(c)) return err }, tracer: true, }, { message: "annotated error", generator: func(c *gc.C, expected io.Writer) error { err := errors.New("first error") fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) err = errors.Annotate(err, "annotation") fmt.Fprintf(expected, "%s: annotation", errorLocationValue(c)) return err }, tracer: true, }, { message: "wrapped error", generator: func(c *gc.C, expected io.Writer) error { err := errors.New("first error") fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) err = errors.Wrap(err, newError("detailed error")) fmt.Fprintf(expected, "%s: detailed error", errorLocationValue(c)) return err }, tracer: true, }, { message: "annotated wrapped error", generator: func(c *gc.C, expected io.Writer) error { err := errors.Errorf("first error") fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) err = errors.Wrap(err, fmt.Errorf("detailed error")) fmt.Fprintf(expected, "%s: detailed error\n", errorLocationValue(c)) err = errors.Annotatef(err, "annotated") fmt.Fprintf(expected, "%s: annotated", errorLocationValue(c)) return err }, tracer: true, }, { message: "traced, and annotated", generator: func(c *gc.C, expected io.Writer) error { err := errors.New("first error") fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c)) err = errors.Trace(err) fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) err = errors.Annotate(err, "some context") fmt.Fprintf(expected, "%s: some context\n", errorLocationValue(c)) err = errors.Trace(err) fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) err = errors.Annotate(err, "more context") fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c)) err = errors.Trace(err) fmt.Fprintf(expected, "%s: ", errorLocationValue(c)) return err }, tracer: true, }, { message: "uncomparable, wrapped with a value error", generator: func(c *gc.C, expected io.Writer) error { err := newNonComparableError("first error") fmt.Fprintln(expected, "first error") err = errors.Trace(err) fmt.Fprintf(expected, "%s: \n", errorLocationValue(c)) err = errors.Wrap(err, newError("value error")) fmt.Fprintf(expected, "%s: value error\n", errorLocationValue(c)) err = errors.Maskf(err, "masked") fmt.Fprintf(expected, "%s: masked\n", errorLocationValue(c)) err = errors.Annotate(err, "more context") fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c)) err = errors.Trace(err) fmt.Fprintf(expected, "%s: ", errorLocationValue(c)) return err }, tracer: true, }} { c.Logf("%v: %s", i, test.message) expected := strings.Builder{} err := test.generator(c, &expected) stack := errors.ErrorStack(err) ok := c.Check(stack, gc.Equals, expected.String()) if !ok { c.Logf("%#v", err) } tracer, ok := err.(tracer) c.Check(ok, gc.Equals, test.tracer) if ok { stackTrace := tracer.StackTrace() c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n")) } } } func (*functionSuite) TestFormat(c *gc.C) { formatErrorExpected := &strings.Builder{} err := errors.New("TestFormat") fmt.Fprintf(formatErrorExpected, "%s: TestFormat\n", errorLocationValue(c)) err = errors.Mask(err) fmt.Fprintf(formatErrorExpected, "%s: ", errorLocationValue(c)) for i, test := range []struct { format string expect string }{{ format: "%s", expect: "TestFormat", }, { format: "%v", expect: "TestFormat", }, { format: "%q", expect: `"TestFormat"`, }, { format: "%A", expect: `%!A(*errors.Err=TestFormat)`, }, { format: "%+v", expect: formatErrorExpected.String(), }} { c.Logf("test %d: %q", i, test.format) s := fmt.Sprintf(test.format, err) c.Check(s, gc.Equals, test.expect) } } type basicError struct { Reason string } func (b *basicError) Error() string { return b.Reason } func (*functionSuite) TestAs(c *gc.C) { baseError := &basicError{"I'm an error"} testErrors := []error{ errors.Trace(baseError), errors.Annotate(baseError, "annotation"), errors.Wrap(baseError, errors.New("wrapper")), errors.Mask(baseError), } for _, err := range testErrors { bError := &basicError{} val := errors.As(err, &bError) c.Check(val, gc.Equals, true) c.Check(bError.Reason, gc.Equals, "I'm an error") } } func (*functionSuite) TestIs(c *gc.C) { baseError := &basicError{"I'm an error"} testErrors := []error{ errors.Trace(baseError), errors.Annotate(baseError, "annotation"), errors.Wrap(baseError, errors.New("wrapper")), errors.Mask(baseError), } for _, err := range testErrors { val := errors.Is(err, baseError) c.Check(val, gc.Equals, true) } } func (*functionSuite) TestSetLocationWithNilError(c *gc.C) { c.Assert(errors.SetLocation(nil, 1), gc.IsNil) } func (*functionSuite) TestSetLocation(c *gc.C) { err := errors.New("test") err = errors.SetLocation(err, 1) stack := fmt.Sprintf("%s: test", errorLocationValue(c)) _, implements := err.(errors.Locationer) c.Assert(implements, gc.Equals, true) c.Check(errors.ErrorStack(err), gc.Equals, stack) } func (*functionSuite) TestHideErrorStillReturnsErrorString(c *gc.C) { err := stderrors.New("This is a simple error") err = errors.Hide(err) c.Assert(err.Error(), gc.Equals, "This is a simple error") } func (*functionSuite) TestQuietWrappedErrorStillSatisfied(c *gc.C) { simpleTestError := errors.ConstError("I am a teapot") err := fmt.Errorf("fill me up%w", errors.Hide(simpleTestError)) c.Assert(err.Error(), gc.Equals, "fill me up") c.Assert(errors.Is(err, simpleTestError), gc.Equals, true) } type ComplexErrorMessage interface { error ComplexMessage() string } type complexError struct { Message string } func (c *complexError) Error() string { return c.Message } func (c *complexError) ComplexMessage() string { return c.Message } type complexErrorOther struct { Message string } func (c *complexErrorOther) As(e any) bool { if ce, ok := e.(**complexError); ok { *ce = &complexError{ Message: c.Message, } return true } return false } func (c *complexErrorOther) Error() string { return c.Message } func (c *complexErrorOther) ComplexMessage() string { return c.Message } func (*functionSuite) TestHasType(c *gc.C) { complexErr := &complexError{Message: "complex error message"} wrapped1 := fmt.Errorf("wrapping1: %w", complexErr) wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1) c.Assert(errors.HasType[*complexError](complexErr), gc.Equals, true) c.Assert(errors.HasType[*complexError](wrapped1), gc.Equals, true) c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true) c.Assert(errors.HasType[ComplexErrorMessage](wrapped2), gc.Equals, true) c.Assert(errors.HasType[*complexErrorOther](wrapped2), gc.Equals, false) c.Assert(errors.HasType[*complexErrorOther](nil), gc.Equals, false) complexErrOther := &complexErrorOther{Message: "another complex error"} c.Assert(errors.HasType[*complexError](complexErrOther), gc.Equals, true) wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther) c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true) } func (*functionSuite) TestAsType(c *gc.C) { complexErr := &complexError{Message: "complex error message"} wrapped1 := fmt.Errorf("wrapping1: %w", complexErr) wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1) ce, ok := errors.AsType[*complexError](complexErr) c.Assert(ok, gc.Equals, true) c.Assert(ce.Message, gc.Equals, complexErr.Message) ce, ok = errors.AsType[*complexError](wrapped1) c.Assert(ok, gc.Equals, true) c.Assert(ce.Message, gc.Equals, complexErr.Message) ce, ok = errors.AsType[*complexError](wrapped2) c.Assert(ok, gc.Equals, true) c.Assert(ce.Message, gc.Equals, complexErr.Message) cem, ok := errors.AsType[ComplexErrorMessage](wrapped2) c.Assert(ok, gc.Equals, true) c.Assert(cem.ComplexMessage(), gc.Equals, complexErr.Message) ceo, ok := errors.AsType[*complexErrorOther](wrapped2) c.Assert(ok, gc.Equals, false) c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil)) ceo, ok = errors.AsType[*complexErrorOther](nil) c.Assert(ok, gc.Equals, false) c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil)) complexErrOther := &complexErrorOther{Message: "another complex error"} ce, ok = errors.AsType[*complexError](complexErrOther) c.Assert(ok, gc.Equals, true) c.Assert(ce.Message, gc.Equals, complexErrOther.Message) wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther) ce, ok = errors.AsType[*complexError](wrapped2) c.Assert(ok, gc.Equals, true) c.Assert(ce.Message, gc.Equals, complexErrOther.Message) } func ExampleHide() { myConstError := errors.ConstError("I don't want to be fmt printed") err := fmt.Errorf("don't show this error%w", errors.Hide(myConstError)) fmt.Println(err) fmt.Println(stderrors.Is(err, myConstError)) // Output: // don't show this error // true } type MyError struct { Message string } func (m *MyError) Error() string { return m.Message } func ExampleHasType() { myErr := &MyError{Message: "these are not the droids you're looking for"} err := fmt.Errorf("wrapped: %w", myErr) is := errors.HasType[*MyError](err) fmt.Println(is) // Output: // true } func ExampleAsType() { myErr := &MyError{Message: "these are not the droids you're looking for"} err := fmt.Errorf("wrapped: %w", myErr) myErr, is := errors.AsType[*MyError](err) fmt.Println(is) fmt.Println(myErr.Message) // Output: // true // these are not the droids you're looking for } errors-1.0.0/go.mod000066400000000000000000000003021425471104600141310ustar00rootroot00000000000000module github.com/juju/errors go 1.18 require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c require ( github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect ) errors-1.0.0/go.sum000066400000000000000000000014001425471104600141560ustar00rootroot00000000000000github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= errors-1.0.0/package_test.go000066400000000000000000000014731425471104600160160ustar00rootroot00000000000000// Copyright 2013, 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package errors_test import ( "fmt" "runtime" "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } // errorLocationValue provides the function name and line number for where this // function was called from - 1 line. What this means is that the returned value // will be homed to the file line directly above where this function was called. // This is a utility for testing error details and that associated error calls // set the error location correctly. func errorLocationValue(c *gc.C) string { rpc := make([]uintptr, 1) n := runtime.Callers(2, rpc[:]) if n < 1 { return "" } frame, _ := runtime.CallersFrames(rpc).Next() return fmt.Sprintf("%s:%d", frame.Function, frame.Line-1) }