Giter Site home page Giter Site logo

merry's Introduction

merry Build GoDoc Go Report Card

Add context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values.

The package is largely based on http://github.com/go-errors/errors, with additional inspiration from https://github.com/go-errgo/errgo and https://github.com/amattn/deeperror.

V2

github.com/ansel1/merry/v2 now replaces v1. v1 will continue to be supported. v1 has been re-implemented in terms of v2, and the two packages can be used together and interchangeably.

There are some small enhancements and changes to v1 with the introduction of v2:

  • err.Error() now always just prints out the basic error message. It no longer prints out details, user message, or cause. VerboseDefault() and SetVerboseDefault() no longer have any effect. To print more detailed error information, you must use fmt:

      // print err message and cause chain
      fmt.Printf("%v", err)    // %s works too
    
      // print details, same as Details(err)
      fmt.Printf("%v+", err) 
    
  • MaxStackDepth is no longer supported. Setting it has no effect. It has been replaced with GetMaxStackDepth() and SetMaxStackDepth(), which delegate to corresponding v2 functions.

  • New, Errorf, Wrap, and WrapSkipping now accept v2.Wrapper arguments, allowing a mixture of v1's fluent API style and v2's option-func API style.

  • Compatibility with other error wrapping libraries is improved. All functions which extract a value from an error will now search the entire chain of errors, even if errors created by other libraries are inserted in the middle of the chain, so long as those errors implement Unwrap().

Installation

go get github.com/ansel1/merry

Features

Merry errors work a lot like google's golang.org/x/net/context package. Merry errors wrap normal errors with a context of key/value pairs. Like contexts, merry errors are immutable: adding a key/value to an error always creates a new error which wraps the original.

merry comes with built-in support for adding information to errors:

  • stacktraces
  • overriding the error message
  • HTTP status codes
  • End user error messages

You can also add your own additional information.

The stack capturing feature can be turned off for better performance, though it's pretty fast. Benchmarks on an 2017 MacBook Pro, with go 1.10:

BenchmarkNew_withStackCapture-8      	 2000000	       749 ns/op
BenchmarkNew_withoutStackCapture-8   	20000000	        64.1 ns/op

Details

  • Support for go 2's errors.Is and errors.As functions

  • New errors have a stacktrace captured where they are created

  • Add a stacktrace to existing errors (captured where they are wrapped)

    err := lib.Read()
    return merry.Wrap(err)  // no-op if err is already merry
  • Add a stacktrace to a sentinel error

    var ParseError = merry.New("parse error")
    
    func Parse() error {
    	// ...
        return ParseError.Here() // captures a stacktrace here
    }
  • The golang idiom for testing errors against sentinel values or type checking them doesn't work with merry errors, since they are wrapped. Use Is() for sentinel value checks, or the new go 2 errors.As() function for testing error types.

    err := Parse()
    
    // sentinel value check
    if merry.Is(err, ParseError) {
       // ...
    }
    
    // type check
    if serr, ok := merry.Unwrap(err).(*SyntaxError); ok {
      // ...
    }
    
    // these only work in go1.13
    
    // sentinel value check
    if errors.Is(err, ParseError) {}
    
    // type check
    var serr *SyntaxError
    if errors.As(err, &serr) {}
  • Add to the message on an error.

    err := merry.Prepend(ParseError, "reading config").Append("bad input")
    fmt.Println(err.Error()) // reading config: parse error: bad input
  • Hierarchies of errors

    var ParseError = merry.New("Parse error")
    var InvalidCharSet = merry.WithMessage(ParseError, "Invalid char set")
    var InvalidSyntax = merry.WithMessage(ParseError, "Invalid syntax")
    
    func Parse(s string) error {
        // use chainable methods to add context
        return InvalidCharSet.Here().WithMessagef("Invalid char set: %s", "UTF-8")
        // or functions
        // return merry.WithMessagef(merry.Here(InvalidCharSet), "Invalid char set: %s", "UTF-8")
    }
    
    func Check() {
        err := Parse("fields")
        merry.Is(err, ParseError) // yup
        merry.Is(err, InvalidCharSet) // yup
        merry.Is(err, InvalidSyntax) // nope
    }
  • Add an HTTP status code

    merry.HTTPCode(errors.New("regular error")) // 500
    merry.HTTPCode(merry.New("merry error").WithHTTPCode(404)) // 404
  • Set an alternate error message for end users

    e := merry.New("crash").WithUserMessage("nothing to see here")
    merry.UserMessage(e)  // returns "nothing to see here"
  • Functions for printing error details

    err := merry.New("boom")
    m := merry.Stacktrace(err) // just the stacktrace
    m = merry.Details(err) // error message and stacktrace
    fmt.Sprintf("%+v", err) == merry.Details(err) // errors implement fmt.Formatter
  • Add your own context info

    err := merry.New("boom").WithValue("explosive", "black powder")

Basic Usage

The package contains functions for creating new errors with stacks, or adding a stack to error instances. Functions with add context (e.g. WithValue()) work on any error, and will automatically convert them to merry errors (with a stack) if necessary.

Capturing the stack can be globally disabled with SetStackCaptureEnabled(false)

Functions which get context values from errors also accept error, and will return default values if the error is not merry, or doesn't have that key attached.

All the functions which create or attach context return concrete instances of *Error. *Error implements methods to add context to the error (they mirror the functions and do the same thing). They allow for a chainable syntax for adding context.

Example:

package main

import (
    "github.com/ansel1/merry"
    "errors"
)

var InvalidInputs = errors.New("Input is invalid")

func main() {
    // create a new error, with a stacktrace attached
    err := merry.New("bad stuff happened")
    
    // create a new error with format string, like fmt.Errorf
    err = merry.Errorf("bad input: %v", os.Args)
    
    // capture a fresh stacktrace from this callsite
    err = merry.Here(InvalidInputs)
    
    // Make err merry if it wasn't already.  The stacktrace will be captured here if the
    // error didn't already have one.  Also useful to cast to *Error 
    err = merry.Wrap(err, 0)

    // override the original error's message
    err.WithMessagef("Input is invalid: %v", os.Args)
    
    // Use Is to compare errors against values, which is a common golang idiom
    merry.Is(err, InvalidInputs) // will be true
    
    // associated an http code
    err.WithHTTPCode(400)
    
    perr := parser.Parse("blah")
    err = Wrap(perr, 0)
    // Get the original error back
    merry.Unwrap(err) == perr  // will be true
    
    // Print the error to a string, with the stacktrace, if it has one
    s := merry.Details(err)
    
    // Just print the stacktrace (empty string if err is not a RichError)
    s := merry.Stacktrace(err)

    // Get the location of the error (the first line in the stacktrace)
    file, line := merry.Location(err)
    
    // Get an HTTP status code for an error.  Defaults to 500 for non-nil errors, and 200 if err is nil.
    code := merry.HTTPCode(err)
    
}

See inline docs for more details.

Plugs

License

This package is licensed under the MIT license, see LICENSE.MIT for details.

merry's People

Contributors

ansel1 avatar dependabot[bot] avatar emanuelsteen avatar stroborobo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

merry's Issues

Tests fail (nil value handling)

In 99536c2 you changed the nil value handling and added a test for this (TestNilValues). It contains the following line:

if Wrap(nil).WithHTTPCode(400).WithMessage("hey") { //...

Maybe I misunderstand your commit, but you can't call methods on "true" nil interfaces, can you?

http://play.golang.org/p/mmJoXJR9J1

Did you mean

if (WithMessage(WithHTTPCode(Wrap(nil), 400), "hey") != nil) { //...

(This will still crash since WithValue doesn't handle the nil from the Wrap call it does.)

Support a way to set both the user message and the internal message to the same thing when creating a new error

Example: in a web app's controller, you want to raise an error with a user message. The internal error message should be same. Today, you need to make two calls with the same message argument to achieve this. Would be nice to express "use this message for the internal message and the user message".

There are a few approaches:

  1. A set of methods which set both the internal and user message
  2. A function which fetches the user message first, and falls back on the internal message if no user message is set
  3. A function which fetches the internal error, and falls back on the user message if not set

I'm leaning towards three. Two seems wrong because the whole purpose of the "user message" property is for frameworks to ensure that error messages presented to end users have been explicitly approved for end user consumption.

Support callbacks to add context to a merry error when it is first captured

When a merry error is first created, or first wrapped around an existing error, it would be cool to optionally invoke configurable a callback. This would allow users of merry to:

  1. capture a different kind of stacktrace (e.g. sentry stacktraces)
  2. attach other context information to the error.
  3. send the error to a log or error capture system

Callbacks would be registered globally. They would be invoked whenever a new merry error is created, or when an error was wrapped with a merry error. They would be passed the current merry error, and a stackframe offset from where the merry error is being created, and would return a merry error.

Add flag with causes e.Error() to return the same as merry.Details(e)

Sometimes, errors are passed from merry-aware code to merry-unaware code. If it's the unaware code which is responsible for printing the error, it won't print the details. Might be helpful to have a flag on the merry error which changes the behavior of Error() to return the same as merry.Details(e).

This came up in unit tests. I use an assertions library (testify) which checks for errors. If there's an error, it prints it. I end up having to wrap the assertion library in order to print out the full merry details of the error.

merry.Errorf returns a stdlib error in some cases which breaks %+v

Actual: fmt.Printf("%+v", merry.Errorf("%w", merry.New("boom"))) does not print a stacktrace
Expected: It should print a stacktrace. Maybe the stacktrace of the wrapped error.

This example reproduces the issue - https://go.dev/play/p/15d5C3TAbDh .

As far as I understand the code, merry.Wrap called by merry.Errorf does not wrap the argument if it finds an existing stack trace in its wrap chain. Since merry.New("boom") is in the chain, merry.Errorf just returns the result of fmt.Errorf . However, in that case, fmt.Printf("%+v") will not use the Formatter interface implementation in merry.

Workaround: add any value. merry.Errorf("%w", merry.New("boom"), merry.WithValue("foo", "bar")) works fine.

Should Location() return empty string?

Hi,

I feel like Location() should return an empty string if it's unknown where the error happened.

  • It's the null value of strings
  • A file with that name could actually exist (although unlikely)
  • Naming a file "" is probably impossible everywhere
  • Other functions like Stacktrace() return an empty string as well, not "no stacktrace" :)

What do you think?

merry v1.6.0 fails go get

$ go get github.com/ansel1/merry
go get: github.com/ansel1/[email protected] updating to
        github.com/ansel1/[email protected] requires
        github.com/ansel1/merry/[email protected]: invalid version: unknown revision 000000000000

I also note that there is no v2 release tag yet (only beta). I'm guessing making a v2.0.0 release tag and updating the v1 go.mod to point to that release tag would fix it.

I'm not sure if replace tags work as expected when a module is being imported as a library.

Value extraction

Hi! Could you please add a method extracting all the values from an error? Like map[string]interface{} or some iterator over them.

Add a way to use predefined merry errors to wrap

We generally define constants for often-used errors:

var (
  ErrInvalidParameter = merry.
    New("Invalid parameter").
    WithUserMessage("Invalid parameter").
    WithHTTPCode(http.StatusBadRequest)
)

Then in the controller I'd love to be able to write something like:

result, err := strconv.ParseBool(param)
if err != nil {
  // this should save the current stack and the err object, but maybe override the http code
  // and user/error message if present.
  return ErrInvalidParameter.Wrap(err)
}

Add a Cause property

Add a new native merry error property: "cause". This would be another error. Useful when a library emits errors of fixed types, and wants to wrap errors from lower level libraries in its own error types without losing information.

Details() would be enhanced to print the stack of causes, perhaps labelling the innermost cause the "root cause".

Cause(err) would get the next error down the cause stack. RootCause(err) would get the deepest cause.

Could also consider enhancing Is(), or adding an alternate form, for comparing against any of the causes in the stack.

merry.Details() does not print attached values

Calling merry.Details(err) does not print an error's attached values, i.e. merry.New("x").WithValue("lololol", "asdasdasd").

Could it be made to print it? If it is just a matter of implementation, I would be happy to submit a pull request.

Deal with nil errors

Most functions which take errors should allow for nil to be passed, and should return nil in these cases.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.