Skip to content
This repository has been archived by the owner on Aug 29, 2024. It is now read-only.

Commit

Permalink
Simplify the StackErr API so it's just Handle
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-gregory-ovo committed Aug 22, 2023
1 parent e825672 commit c552c63
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 287 deletions.
9 changes: 1 addition & 8 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ linters:
- wastedassign # disabled because of generics

linters-settings:
gomoddirectives:
replace-local: true

wrapcheck:
ignorePackageGlobs:
- github.com/ovotech/stackerr
Expand All @@ -34,12 +31,8 @@ linters-settings:
- standard
- default
- prefix(github.com/ovotech)
- prefix(github.com/ovotech/go-sync)
- prefix(github.com/ovotech/stackerr)
- blank
- dot
skip-generated: true
custom-order: true

varnamelen:
ignore-names:
- ID
91 changes: 5 additions & 86 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
Make your errors beautiful, because writing them is boring enough.

* Automatically wrap your errors with the package and calling function name.
* Keep your code DRY by declaring the things you want in your errors once.
* Or keep it simple, and just plainly wrap your errors with `Handle(err, "your", "args")`.
* Keep it simple, and just plainly wrap your errors with `Handle(err, "myFunction", "some", "args")`.
* Full control over what is/isn't included in your errors. Include as much detail as you need to help with debugging.
* Performant. StackErr is only invoked when handling errors.
* Fully interoperable with other error handlers.
* Customisable to suit your needs.

## Installation

Expand All @@ -24,17 +23,12 @@ import (
"log"
"strconv"

. "github.com/ovotech/stackerr"
"github.com/ovotech/stackerr"
)

func myFunction(input string) error {
// Create a new StackErr to with the input parameter as we want to know that in all of our errors.
stackErr := NewStackErr(input)

if _, err := strconv.Atoi(input); err != nil {
// Handle the error with the mandatory function name "atoi" to tell us where the error came from, and some
// optional context which would help with debugging.
return stackErr.Handle(err, "atoi", "some", "context")
return stackerr.Handle(err, "atoi", input, "some", "added", "details")
}

return nil
Expand All @@ -45,85 +39,10 @@ func main() {
log.Panic(err)
}

// panic: main.myFunction("foo").atoi("some", "context"): strconv.Atoi: parsing "foo": invalid syntax
// panic: main.myFunction.atoi("foo", "some", "added", "details"): strconv.Atoi: parsing "foo": invalid syntax
}
```

<details>
<summary>Basic usage without instantiating</summary>
You can use StackErr without instantiating it by using the Handle function by itself.

```go
package main

import (
"log"
"strconv"

. "github.com/ovotech/stackerr"
)

func myFunction(input string) error {
if _, err := strconv.Atoi(input); err != nil {
return Handle(err, "atoi", input)
}

return nil
}

func main() {
if err := myFunction("foo"); err != nil {
log.Panic(err)
}

// panic: main.myFunction.atoi("foo"): strconv.Atoi: parsing "foo": invalid syntax
}
```

</details>


<details>
<summary>Customising StackErr</summary>
You can customise StackErr by instantiating a new struct, and then using Copy in functions you wish to use it in.

```go
package main

import (
"log"
"strconv"

. "github.com/ovotech/stackerr"
)

var myStackErr = StackErr{
Separator: " -> ",
Punctuation: "\'",
}

func myFunction(input string) error {
stackErr := myStackErr.Copy(input)

if _, err := strconv.Atoi(input); err != nil {
return stackErr.Handle(err, "atoi", "hello", "world")
}

return nil
}

func main() {
if err := myFunction("foo"); err != nil {
log.Panic(err)
}

// Note the punctuation and separator:
// panic: main.myFunction('foo').atoi('hello', 'world') -> strconv.Atoi: parsing "foo": invalid syntax
}
```

</details>

### Use with `wrapcheck`

If you use [golangci-lint](https://github.com/golangci/golangci-lint) and the wrapcheck linter, you should add this
Expand Down
84 changes: 14 additions & 70 deletions stackerr.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ const (
punctuation = "\""
)

// argsToString takes a slice of args and punctuation, and returns a string.
// If no args are passed, it will return an empty string.
// If 1+ args are passed, it will wrap them in parentheses and each arg in the punctuation e.g. ("foo", "bar").
func argsToString(args []string, punctuation string) string {
// Handle an error, providing the package & function name, locator, and optional arguments.
// Wraps the error e.g. "mypackage.myFunction.locator("arg1", "arg2") -> error.
func Handle(err error, locator string, args ...string) error {
functionName := "unknown"

// Get the name of the function invoking this function.
if pc, _, _, ok := runtime.Caller(1); ok {
details := runtime.FuncForPC(pc)
functionName = details.Name()
}

argString := ""
if len(args) > 0 {
argString = fmt.Sprintf(
Expand All @@ -25,75 +32,12 @@ func argsToString(args []string, punctuation string) string {
)
}

return argString
}

// handle is the internal mechanism for wrapping errors with extra contextual information automatically.
func handle(
err error,
loc string,
sharedArgs []string,
invokedArgs []string,
punctuation string,
separator string,
) error {
functionName := "unknown"

// Get the name of the function invoking this function.
if pc, _, _, ok := runtime.Caller(2); ok { //nolint:gomnd
details := runtime.FuncForPC(pc)
functionName = details.Name()
}

// Convert both the invokedArgs and handle sharedArgs into strings.
sharedArgsString := argsToString(sharedArgs, punctuation)
invokedArgsString := argsToString(invokedArgs, punctuation)

return fmt.Errorf(
"%s%s.%s%s%s%w",
"%s.%s%s%s%w",
functionName,
invokedArgsString,
loc,
sharedArgsString,
locator,
argString,
separator,
err,
)
}

// Handle an error, providing the module, function name and optional arguments.
// Wraps the error e.g. "mymodule.myFunction.functionName("arg1", "arg2") -> error.
func Handle(err error, functionName string, args ...string) error {
return handle(err, functionName, args, nil, punctuation, separator)
}

type StackErr struct {
// Arguments defined when the StackErr was instantiated.
arguments []string

Separator string // Separator used between errors e.g. " -> "
Punctuation string // Punctuation surrounding arguments.
}

// Handle an error.
func (s *StackErr) Handle(err error, functionName string, args ...string) error {
return handle(err, functionName, args, s.arguments, s.Punctuation, s.Separator)
}

// Copy the existing StackErr's separator and punctuation, and create a new StackErr.
func (s *StackErr) Copy(args ...string) *StackErr {
// Copy the StackErr struct's Separator and Punctuation, and return a new one.
return &StackErr{
arguments: args,
Separator: s.Separator,
Punctuation: s.Punctuation,
}
}

// NewStackErr takes optional arguments, and returns a new StackErr with the default settings.
func NewStackErr(args ...string) *StackErr {
return &StackErr{
arguments: args,
Separator: separator,
Punctuation: punctuation,
}
}
77 changes: 0 additions & 77 deletions stackerr_internal_test.go

This file was deleted.

Loading

0 comments on commit c552c63

Please sign in to comment.