-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 418c2bf
Showing
10 changed files
with
805 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github: jub0bs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
name: build | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
go-version: [1.23] | ||
steps: | ||
- name: Check out Source | ||
uses: actions/checkout@v4 | ||
- name: Set up Go ${{ matrix.go-version }} | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
cache: true | ||
- name: Display Go version | ||
run: go version | ||
- name: Test | ||
run: go test -v -coverprofile=cover.out ./... | ||
- name: Upload coverage reports to Codecov | ||
uses: codecov/[email protected] | ||
with: | ||
token: ${{ secrets.CODECOV_TOKEN }} | ||
slug: jub0bs/errutil | ||
benchmark: | ||
needs: test | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest] | ||
go-version: [1.23] | ||
name: Benchmark comparison ${{ matrix.os }} @ Go ${{ matrix.go-version }} | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
- name: Check out Code (previous) | ||
uses: actions/checkout@v4 | ||
with: | ||
ref: ${{ github.base_ref }} | ||
path: previous | ||
- name: Check out Code (new) | ||
uses: actions/checkout@v4 | ||
with: | ||
path: new | ||
- name: Set up Go ${{ matrix.go-version }} | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
- name: Install benchstat | ||
run: go install golang.org/x/perf/cmd/benchstat@latest | ||
- name: Run Benchmark (previous) | ||
run: | | ||
cd previous | ||
go test -run '^$' -bench '^(BenchmarkAs|BenchmarkFind)$' -count 10 . -benchtime 10000x > benchmark.txt | ||
- name: Run Benchmark (new) | ||
run: | | ||
cd new | ||
go test -run '^$' -bench '^(BenchmarkAs|BenchmarkFind)$' -count 10 . -benchtime 10000x > benchmark.txt | ||
- name: Run benchstat | ||
# Mostly to compare allocations; | ||
# measurements of execution speed in GitHub Actions are unreliable. | ||
run: | | ||
benchstat previous/benchmark.txt new/benchmark.txt | ||
- name: Run Benchmark (against standard library) | ||
run: | | ||
cd new | ||
go test -run '^$' -bench '^(BenchmarkAsAgainstErrorsPkg|BenchmarkFindAgainstErrorsPkg)$' -count 10 . -benchtime 10000x > benchmark.txt | ||
- name: Run benchstat (against standard library) | ||
# Mostly to compare allocations; | ||
# measurements of execution speed in GitHub Actions are unreliable. | ||
run: | | ||
benchstat -col "/v@(errors errutil)" new/benchmark.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [0.1.0] (2024-10-02) | ||
|
||
[0.1.0]: https://github.com/jub0bs/errutil/releases/tag/v0.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Contributing to jub0bs/errutil | ||
|
||
jub0bs/errutil is an open-source project | ||
but currently does not accept external contributions. | ||
|
||
However, if you want to report a problem (a bug, a missing feature, | ||
a misfeature, an idea for performance improvement, etc.), | ||
feel free to [open an issue](https://github.com/jub0bs/errutil/issues/new). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 jub0bs | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# jub0bs/errutil | ||
|
||
[![tag](https://img.shields.io/github/tag/jub0bs/errutil.svg)](https://github.com/jub0bs/errutil/releases) | ||
![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.23.2-%23007d9c) | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/jub0bs/errutil.svg)](https://pkg.go.dev/github.com/jub0bs/errutil) | ||
[![license](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat)](https://github.com/jub0bs/errutil/raw/main/LICENSE) | ||
[![build](https://github.com/jub0bs/errutil/actions/workflows/errutil.yml/badge.svg)](https://github.com/jub0bs/errutil/actions/workflows/errutil.yml) | ||
[![codecov](https://codecov.io/gh/jub0bs/errutil/branch/main/graph/badge.svg?token=N208BHWQTM)](https://app.codecov.io/gh/jub0bs/errutil/tree/main) | ||
[![goreport](https://goreportcard.com/badge/jub0bs/errutil)](https://goreportcard.com/report/jub0bs/errutil) | ||
|
||
A collection of utility functions for working with [Go][golang] errors. | ||
|
||
## Installation | ||
|
||
```shell | ||
go get github.com/jub0bs/errutil | ||
``` | ||
|
||
jub0bs/errutil requires Go 1.23.1 or above. | ||
|
||
## Documentation | ||
|
||
The documentation is available on [pkg.go.dev][pkgsite]. | ||
|
||
## Code coverage | ||
|
||
![coverage](https://codecov.io/gh/jub0bs/errutil/branch/main/graphs/sunburst.svg?token=N208BHWQTM) | ||
|
||
## License | ||
|
||
All source code is covered by the [MIT License][license]. | ||
|
||
[golang]: https://go.dev/ | ||
[license]: https://github.com/jub0bs/errutil/blob/main/LICENSE | ||
[pkgsite]: https://pkg.go.dev/github.com/jub0bs/errutil |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
## Reporting security issue | ||
|
||
Please do **not** open an issue on GitHub. | ||
Instead, contact jub0bs privately on [Mastodon]. | ||
|
||
[Mastodon]: https://infosec.exchange/@jub0bs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
Package errutil provides utility functions for working with errors. | ||
The advent of [errors.As] in the standard library predates that of parametric | ||
polymorphism (generics) in the language. | ||
As a result, [errors.As] is not as ergonomic, type-safe, or efficient as it | ||
ideally could be. | ||
Functions [As] and [Find] are inspired by several unaccepted proposals | ||
(see issues [51945], [56949], and [64771]) and aim to address those limitations. | ||
In most cases, [As] can be used as a drop-in replacement for [errors.As]. | ||
[Find] is a more efficient and arguably more ergonomic alternative to [As]. | ||
Incidentally, [the error-inspection draft design proposal] suggests that [errors.As] | ||
would have been very similar to [Find] if the Go team had cracked | ||
the parametric-polymorphism nut in time for [errors.As]'s inception in the | ||
standard library. | ||
In many cases, a call to [errors.As] can advantageously be refactored into a | ||
call to [Find]. | ||
[51945]: https://github.com/golang/go/issues/51945 | ||
[56949]: https://github.com/golang/go/issues/56949 | ||
[64771]: https://github.com/golang/go/issues/64771 | ||
[the error-inspection draft design proposal]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md#the-is-and-as-functions | ||
*/ | ||
package errutil | ||
|
||
// As finds the first error in err's tree that matches target, | ||
// and if one is found, sets target to that error value and returns true. | ||
// Otherwise, it returns false. | ||
// | ||
// The tree consists of err itself, followed by the errors obtained by repeatedly | ||
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple | ||
// errors, As examines err followed by a depth-first traversal of its children. | ||
// | ||
// An error matches target if the error's concrete value is assignable to the value | ||
// pointed to by target | ||
// or if the error has a method As(any) bool such that As(target) returns true. | ||
// In the latter case, the As method is responsible for setting target. | ||
// | ||
// An error type might provide an As method so it can be treated as if it were a | ||
// different error type. | ||
// | ||
// As panics if err is not nil and target is nil. | ||
func As[T error](err error, target *T) bool { | ||
if err == nil { | ||
return false | ||
} | ||
if target == nil { | ||
panic("errutil: target cannot be nil") | ||
} | ||
return as(err, target) | ||
} | ||
|
||
func as[T error](err error, target *T) bool { | ||
for { | ||
if x, ok := err.(T); ok { | ||
*target = x | ||
return true | ||
} | ||
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) { | ||
return true | ||
} | ||
switch x := err.(type) { | ||
case interface{ Unwrap() error }: | ||
err = x.Unwrap() | ||
if err == nil { | ||
return false | ||
} | ||
case interface{ Unwrap() []error }: | ||
for _, err := range x.Unwrap() { | ||
if err == nil { | ||
continue | ||
} | ||
if as(err, target) { | ||
return true | ||
} | ||
} | ||
return false | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
// Find finds the first error in err's tree that matches type T, | ||
// and if so, returns the corresponding value and true. | ||
// Otherwise, it returns the zero value and false. | ||
// | ||
// The tree consists of err itself, followed by the errors obtained by repeatedly | ||
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple | ||
// errors, Find examines err followed by a depth-first traversal of its children. | ||
// | ||
// An error matches type T if type-asserting it to T succeeds, | ||
// or if the error has a method As(any) bool such that As(target), | ||
// where target is any non-nil value of type *T, returns true. | ||
// In the latter case, the As method is responsible for setting target. | ||
// | ||
// An error type might provide an As method so it can be treated as if it were a | ||
// different error type. | ||
func Find[T error](err error) (T, bool) { | ||
if err == nil { | ||
var zero T | ||
return zero, false | ||
} | ||
var ptr *T | ||
return find[T](err, &ptr) | ||
} | ||
|
||
func find[T error](err error, ptr2 **T) (T, bool) { | ||
for { | ||
x, ok := err.(T) | ||
if ok { | ||
return x, true | ||
} | ||
if x, ok := err.(interface{ As(any) bool }); ok { | ||
if *ptr2 == nil { | ||
*ptr2 = new(T) | ||
} | ||
if x.As(*ptr2) { | ||
return **ptr2, true | ||
} | ||
} | ||
switch x := err.(type) { | ||
case interface{ Unwrap() error }: | ||
err = x.Unwrap() | ||
if err == nil { | ||
var zero T | ||
return zero, false | ||
} | ||
case interface{ Unwrap() []error }: | ||
for _, err := range x.Unwrap() { | ||
if err == nil { | ||
continue | ||
} | ||
if x, ok := find[T](err, ptr2); ok { | ||
return x, true | ||
} | ||
} | ||
var zero T | ||
return zero, false | ||
default: | ||
var zero T | ||
return zero, false | ||
} | ||
} | ||
} |
Oops, something went wrong.