Skip to content

Commit

Permalink
Merge pull request #7 from secureworks/phughes-std-multi-error
Browse files Browse the repository at this point in the history
Rewrite MultiError logic to mirror decisions made in standard library
  • Loading branch information
phughes-scwx authored May 15, 2024
2 parents 180f60d + 7a6fb90 commit 6827797
Show file tree
Hide file tree
Showing 18 changed files with 972 additions and 574 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Go

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:

build:
name: Build
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.20
uses: actions/setup-go@v2
with:
go-version: ^1.20

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Get dependencies
run: go mod download

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...

lint:
name: Lint
runs-on: ubuntu-latest
steps:

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.42.0
config: .golangci.yaml
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
# Errors

This package provides a suite of tools meant to work with Go 1.13 error
wrapping to give users all the basics to handle errors in a useful way.
wrapping and Go 1.20 multierrors. These helpers allow users to rely on
standard Go error patterns while they include some "missing pieces" or
additional features that are useful in practice.

> _Another errors package? Why does Go need all of these error libraries?_
Because the language and the standard library have a minimal approach to error
handling that leaves out some primitives power users expect to have on
hand.
hand.

Important among these primitives are stack traces, error collections
(multi-errors and error groups) and error types. While Go 1.13 introduced
error wrapping utilities that have fixed some immediate issues, it is really
useful to have a few more tools on hand.
Important among these primitives are stack traces, explicit error collection
types (multierrors and error groups) and error context management.

While Go 1.13 introduced error wrapping utilities and Go 1.20 added a minimal
multierror collection, it is really useful to have a few more tools on hand.

### Installation

> _This package **may not be used** in environments:_
>
> 1.   _running Go 1.12 or lower;_
> 1.   _running Go 1.19 or lower;_
> 2.   _running on 32-bit architecture;_
> 3.   _running on Windows (**currently** not supported)._
Given that we are running on Go 1.13, your project should be using Go modules.
Add the following to your file:

```go
Expand All @@ -32,9 +34,21 @@ import "github.com/secureworks/errors"
Then, when you run any Go command the toolchain will resolve and fetch the
required modules automatically.

> If you are using Go 1.13 to Go 1.19, you should use the previous version of
> this library, which has the same functionality but does not support the
> specific form that Go 1.20 multierrors take:
>
> ```
> $ go get github.com/secureworks/[email protected]
> ```
Because this package re-exports the package-level functions of the standard
library `"errors"` package, you do not need to include that package as well to
get `New`, `As`, `Is`, or `Unwrap`.
get `New`, `As`, `Is`, `Unwrap`, and `Join`.
Note that `Join` is a special case: for consistency, and since our multierror is
a better implementation, `Join` returns our implementation (which uses our
formatting), not the standard library's implementation.
### Use
Expand All @@ -48,8 +62,8 @@ Package `github.com/secureworks/errors`:
- use in place of the standard library with no change in behavior;
- use the `errors.MultiError` type as either an explicit multierror
implementation or as an implicit multierror passed around with the default
`error` interface; use `errors.Append` and others to simplify multierror
management in your code;
`error` interface; use `errors.Join`, `errors.Append` and others to simplify
multierror management in your code;
- embed (singular) stack frames with `errors.NewWithFrame("...")`,
`errors.WithFrame(err)`, and `fmt.Errorf("...: %w", err)`;
- embed stack traces with `errors.NewWithStackTrace("...")` and
Expand All @@ -71,8 +85,6 @@ Package `github.com/secureworks/errors/syncerr`:
Possible improvements before reaching `v1.0` include:
- **Add support for Windows filepaths in call frames.**
- Add direct integrations with other errors packages (especially those listed
in the codebase).
- Include either a linter or a suggested [`golang-ci`][golang-ci] lint YAML
to support idiomatic use.
Expand Down
31 changes: 18 additions & 13 deletions docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
// return fmt.Errorf("contextual information: %w", err)
// }
//
// This helps us identify a root causs and place that cause in some
// This helps us identify a root cause, and place that cause in some
// program context recursively.
//
// However, we are not always in control of the full extent of our
Expand All @@ -45,17 +45,14 @@
//
// 2. to chain or group errors and extract their contextual information;
//
// 3. to format errors with all oftheir context when printing them; and
// 3. to format errors with all of their context when printing them; and
//
// 4. to retain a simple API for most use cases while retaining the
// ability to directly interact with, or tune, error chains and groups.
//
// # Error wrapping in Go 1.13
//
// This errors package is meant to be used in addition to the updates in
// https://go.dev/blog/go1.13-errors. Therefore, you shouldn't include
// it (and in fact it will cause your build to fail) if you are using Go
// 1.12 or earlier.
// This package is meant to be used with [error wrapping].
//
// Importantly: this package does not attempt to replace this system.
// Instead, errors is meant to enrich it: all the types and interfaces
Expand All @@ -68,6 +65,11 @@
//
// var Err = errors.New("example err") // Same as if we had used: import "errors"
//
// # Wrapping multiple errors in Go 1.20
//
// This package is meant to be used with the [multiple error wrapping]
// support introduced in Go 1.20.
//
// # Stack traces or call frames
//
// For debugging context this package provides the errors.Frame
Expand Down Expand Up @@ -174,7 +176,7 @@
// }
//
// if merr := actionWrapper(); merr != nil {
// fmt.Printf("%+v\n", merr.Errors())
// fmt.Printf("%+v\n", merr.Unwrap())
// }
//
// # Retrieving error information
Expand Down Expand Up @@ -268,11 +270,11 @@
// and can be formatted by the fmt package. The following verbs are
// supported:
//
// %s print the error's message context. Frames are not included in
// the message.
// %v see %s
// %+v extended format. Each Frame of the error's Frames will
// be printed in detail.
// %s print the error's message context. Frames are not included in
// the message.
// %v see %s
// %+v extended format. Each Frame of the error's Frames will
// be printed in detail.
//
// This not an exhaustive list, see the tests for more.
//
Expand Down Expand Up @@ -307,9 +309,12 @@
// PC() uintptr // Ie "programCounter," the interface for getting a frame's program counter.
//
// // Used to identify an error that coalesces multiple errors:
// Errors() []error // Ie "multiError," the interface for getting multiple merged errors.
// Unwrap() []error // Ie "multierror," the interface for getting multiple merged errors.
// }
//
// Though none of these are exported by this package, they are
// considered a part of its stable public interface.
//
// [error wrapping]: https://blog.golang.org/go1.13-errors
// [multiple error wrapping]: https://tip.golang.org/doc/go1.20#errors
package errors
27 changes: 1 addition & 26 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"bytes"
"fmt"
"io"
"regexp"
)

// Stack trace error wrapper.
Expand Down Expand Up @@ -183,30 +182,6 @@ func WithFrames(err error, ff Frames) error {
}
}

var errorfFormatMatcher = regexp.MustCompile(`%(\[\d+])?w`)

// Errorf is a shorthand for:
//
// errors.WithFrame(fmt.Errorf("some msg: %w", err))
//
// It is made available to support the best practice of adding a call
// stack frame to the error context alongside a message when building a
// chain. When possible, prefer using the full syntax instead of this
// shorthand for clarity.
//
// Using an invalid format string (one that does not wrap the given
// error) causes this method to panic.
func Errorf(format string, values ...interface{}) error {
if !errorfFormatMatcher.MatchString(format) {
panic(NewWithStackTrace(fmt.Sprintf("invalid use of errors.Errorf: "+
"format string must wrap an error, but \"%%w\" not found: %q", format)))
}
return &withFrames{
error: fmt.Errorf(format, values...),
frames: frames{getFrame(3)},
}
}

func (w *withFrames) Error() string { return w.error.Error() }

func (w *withFrames) Unwrap() error { return w.error }
Expand Down Expand Up @@ -384,7 +359,7 @@ func Opaque(err error) error {
// Currently, this only supports single errors with or without a stack
// trace or appended frames.
//
// TODO(PH): ensure ErrorFromBytes works with: multiError.
// TODO(PH): ensure ErrorFromBytes works with: multierror.
func ErrorFromBytes(byt []byte) (err error, ok bool) {
trimbyt := bytes.TrimRight(byt, "\n")
if len(trimbyt) == 0 || bytes.Equal(trimbyt, []byte("nil")) || bytes.Equal(trimbyt, []byte("<nil>")) {
Expand Down
Loading

0 comments on commit 6827797

Please sign in to comment.