go-multierror

repository·main·Indexed 25 days ago

https://github.com/hashicorp/go-multierror

A Go package for representing a list of multiple errors as a single error value. It provides utilities for accumulating errors via Append, flattening nested errors, and managing concurrent error collection using the Group type. The library is compatible with the standard library's error introspection tools, including errors.Is and errors.As, and supports custom error formatting and lexicographical sorting of error collections.

Tokens
2.9K
Snippets
9
Records
23
Agent score
82%

What's inside go-multierror

  1. Introspect multierrors with errors.Is and errors.As

    main

    The multierror.Error type is compatible with the standard library errors package. You can use errors.Is to check for specific error values and errors.As to extract specific error types from the collection.

    Check for an exact error value:

    err := somefunc()
    if errors.Is(err, os.ErrNotExist) {
    	// err contains os.ErrNotExist
    }

    Extract a specific error type:

    var errRich RichErrorType
    if errors.As(err, &errRich) {
    	// It has it, and now errRich is populated.
    }
    // Assume err is a multierror value
    err := somefunc()
    
    // We want to know if "err" has a "RichErrorType" in it and extract it.
    var errRich RichErrorType
    if errors.As(err, &errRich) {
    	// It has it, and now errRich is populated.
    }
  2. Migrating from go-multierror to errors.Join

    main

    As of Go 1.20, the standard library provides errors.Join. For new projects, it is recommended to use the standard library.

    Comparison of Basic Aggregation:

    Before (go-multierror):

    var result error
    result = multierror.Append(result, err1)
    result = multierror.Append(result, err2)
    return result

    After (stdlib):

    var errs []error
    if err1 != nil {
        errs = append(errs, err1)
    }
    if err2 != nil {
        errs = append(errs, err2)
    }
    if len(errs) > 0 {
        return errors.Join(errs...)
    }
    return nil

    Note on Unwrapping: go-multierror implements Unwrap() error (chaining errors one at a time), whereas errors.Join implements the newer Unwrap() []error signature introduced in Go 1.20.

    // Before (go-multierror)
    var result error
    result = multierror.Append(result, err1)
    result = multierror.Append(result, err2)
    return result
    
    // After (stdlib)
    var errs []error
    if err1 != nil {
        errs = append(errs, err1)
    }
    if err2 != nil {
        errs = append(errs, err2)
    }
    if len(errs) > 0 {
        return errors.Join(errs...)
    }
    return nil
  3. Return a multierror only if errors exist

    main

    To avoid returning a non-nil error when no errors were actually collected, use the ErrorOrNil() method on a *multierror.Error. This returns nil if the error list is empty, or the multierror.Error if it contains errors.

    var result *multierror.Error
    
    // ... accumulate errors here
    
    // Return the `error` only if errors were added to the multierror, otherwise
    // return nil since there are no errors.
    return result.ErrorOrNil()
  4. Build a list of errors using Append

    main

    The multierror.Append function is used to accumulate multiple errors into a single error value. It behaves similarly to the built-in append function: it handles nil values and existing multierror.Error types gracefully.

    var result error
    
    if err := step1(); err != nil {
    	result = multierror.Append(result, err)
    }
    if err := step2(); err != nil {
    	result = multierror.Append(result, err)
    }
    
    return result
  5. Customize error formatting

    main

    You can change how the combined error message is stringified by setting the ErrorFormat field on a *multierror.Error instance. The ErrorFormat field accepts a function with the signature func([]error) string.

    var result *multierror.Error
    
    // ... accumulate errors here, maybe using Append
    
    if result != nil {
    	result.ErrorFormat = func([]error) string {
    		return "errors!"
    	}
    }
  6. Access the underlying list of errors

    main

    If you are aware that an error might be a multierror.Error, you can use a type assertion/switch to access the underlying slice of errors via the Errors field.

    if err := something(); err != nil {
    	if merr, ok := err.(*multierror.Error); ok {
    		// Use merr.Errors
    	}
    }
  7. Sort errors within a multierror

    main

    The multierror.Error type implements the sort.Interface, allowing you to sort the collection of errors it contains using the standard library's sort package. The errors are sorted lexicographically based on their string representation (the output of the .Error() method).

    To sort a multierror.Error, pass it to sort.Sort().

  8. Use Group to collect errors from multiple goroutines

    main

    The Group type provides a concurrency primitive for executing multiple functions in parallel and coalescing any errors they return into a single multierror.Error.

    1. Use Go(f func() error) to spawn a new goroutine for the provided function. The Group manages the lifecycle and synchronization of these goroutines.
    2. Use Wait() *Error to block the current execution until all goroutines started via Go have completed. Wait returns the accumulated *Error containing all non-nil errors encountered.
  9. Convert multierror.Error to a standard error with ErrorOrNil()

    main
    When accumulating errors, the multierror.Error struct itself might exist even if the Errors slice is empty. To ensure your function returns a true nil when no errors have occurred, call ErrorOrNil() on your multierror.Error instance. This is the recommended way to convert a multierror collection back into a standard Go error interface.
  10. Retrieve all errors via WrappedErrors()

    main
    The WrappedErrors() method returns the full slice of errors contained within the multierror.Error. This method is safe to call even if the multierror.Error pointer is nil (it will return nil instead of panicking). It is an implementation of the errwrap.Wrapper interface.
  11. Customize error formatting with ErrorFormat

    main
    The multierror.Error struct includes an ErrorFormat field of type ErrorFormatFunc. By default, if this is nil, it uses ListFormatFunc. You can provide a custom function to define exactly how the collection of errors should be stringified when the Error() method is called.