-
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
Showing
11 changed files
with
373 additions
and
3 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,37 @@ | ||
# Handle line endings automatically for files detected as text | ||
# and leave all files detected as binary untouched. | ||
* text=auto | ||
|
||
# Force the following filetypes to have unix eols, so Windows does not break them | ||
*.* text eol=lf | ||
|
||
# Windows forced line-endings | ||
/.idea/* text eol=crlf | ||
|
||
# | ||
## These files are binary and should be left untouched | ||
# | ||
|
||
# (binary is a macro for -text -diff) | ||
*.png binary | ||
*.jpg binary | ||
*.jpeg binary | ||
*.gif binary | ||
*.ico binary | ||
*.mov binary | ||
*.mp4 binary | ||
*.mp3 binary | ||
*.flv binary | ||
*.fla binary | ||
*.swf binary | ||
*.gz binary | ||
*.zip binary | ||
*.7z binary | ||
*.ttf binary | ||
*.eot binary | ||
*.woff binary | ||
*.pyc binary | ||
*.pdf binary | ||
*.ez binary | ||
*.bz2 binary | ||
*.swp binary |
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,25 @@ | ||
*.~* | ||
|
||
*.log | ||
*.swp | ||
.idea | ||
.vscode | ||
*.patch | ||
### Go template | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, build with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
.DS_Store | ||
app | ||
demo | ||
|
||
vendor/* |
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
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 |
---|---|---|
@@ -1,2 +1,59 @@ | ||
# go-atomic-error | ||
An error object that can be set only once and implements context behavior | ||
# go-exterror | ||
|
||
Extended error routines. | ||
|
||
## AugmentedError | ||
|
||
An error object which wraps another one and adds supports for extended fields. | ||
|
||
|
||
```golang | ||
package main | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/randlabs/go-exterror" | ||
) | ||
|
||
func main() { | ||
err := exterror.NewAugmentedError( | ||
errors.New("wrapped error"), | ||
"some example message", map[string]interface{}{ | ||
"value2": 1, | ||
"value1": "hello", | ||
}, | ||
) | ||
|
||
//... | ||
} | ||
``` | ||
## AtomicError | ||
|
||
An error object that can be set only once and implements context behavior. | ||
|
||
```golang | ||
package main | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/randlabs/go-exterror" | ||
) | ||
|
||
func main() { | ||
err := exterror.NewAtomicError() | ||
|
||
// Set an error in a separate go routine | ||
go func() { | ||
err.Set(errors.New("error")) | ||
}() | ||
|
||
// Wait for the error to be set | ||
<-err.Done() | ||
} | ||
``` | ||
|
||
## License | ||
See `LICENSE` file for details. | ||
|
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,80 @@ | ||
package exterror | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
// AtomicError is a thread-safe error object. It also implements Context behavior | ||
type AtomicError struct { | ||
mtx sync.RWMutex | ||
err error | ||
done chan struct{} | ||
doneOnce sync.Once | ||
} | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
// NewAtomicError creates a new thread safe error object. | ||
func NewAtomicError() *AtomicError { | ||
e := &AtomicError{ | ||
mtx: sync.RWMutex{}, | ||
done: make(chan struct{}), | ||
doneOnce: sync.Once{}, | ||
} | ||
return e | ||
} | ||
|
||
// Set stores the passed error if the current is nil and completes the context. | ||
func (x *AtomicError) Set(err error) bool { | ||
changed := false | ||
|
||
if err != nil { | ||
x.mtx.Lock() | ||
if x.err == nil { | ||
changed = true | ||
x.err = err | ||
} | ||
x.mtx.Unlock() | ||
|
||
if changed { | ||
x.doneOnce.Do(func() { | ||
close(x.done) | ||
}) | ||
} | ||
} | ||
|
||
return changed | ||
} | ||
|
||
// Deadline returns the time when work done on behalf of this context | ||
// should be canceled. There is no Deadline for a AtomicError. | ||
func (*AtomicError) Deadline() (deadline time.Time, ok bool) { | ||
return | ||
} | ||
|
||
// Done returns a channel that's closed when work done on behalf of this | ||
// context should be canceled. | ||
func (x *AtomicError) Done() <-chan struct{} { | ||
return x.done | ||
} | ||
|
||
// Value returns the value associated with this context for key, or nil | ||
// if no value is associated with key. | ||
func (*AtomicError) Value(_ interface{}) interface{} { | ||
return nil | ||
} | ||
|
||
// Err returns nil if Done is not yet closed. | ||
// If Done is closed, Err returns a non-nil error explaining why: | ||
// Canceled if the context was canceled | ||
// or DeadlineExceeded if the context's deadline passed. | ||
// After Err returns a non-nil error, successive calls to Err return the same error. | ||
func (x *AtomicError) Err() error { | ||
x.mtx.RLock() | ||
defer x.mtx.RUnlock() | ||
|
||
return x.err | ||
} |
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,50 @@ | ||
package exterror_test | ||
|
||
import ( | ||
"errors" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/randlabs/go-exterror" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
func TestAtomicError(t *testing.T) { | ||
wg := sync.WaitGroup{} | ||
|
||
err := exterror.NewAtomicError() | ||
|
||
// Simulate two go-routines setting an error simultaneously | ||
wg.Add(2) | ||
go func() { | ||
err.Set(errors.New("error 1")) | ||
wg.Done() | ||
}() | ||
|
||
go func() { | ||
err.Set(errors.New("error 2")) | ||
wg.Done() | ||
}() | ||
|
||
wg.Wait() | ||
|
||
if err.Err().Error() != "error 1" && err.Err().Error() != "error 2" { | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestAtomicErrorContext(t *testing.T) { | ||
err := exterror.NewAtomicError() | ||
|
||
go func() { | ||
err.Set(errors.New("error")) | ||
}() | ||
|
||
select { | ||
case <-err.Done(): | ||
case <-time.After(5 * time.Second): | ||
t.FailNow() | ||
} | ||
} |
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,54 @@ | ||
package exterror | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
// AugmentedError is just an error with extended data. | ||
type AugmentedError struct { | ||
Message string | ||
Fields map[string]interface{} | ||
Err error // Underlying error that occurred during the operation. | ||
} | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
// NewAugmentedError creates a new AugmentedError. | ||
func NewAugmentedError(wrappedErr error, text string, fields map[string]interface{}) *AugmentedError { | ||
e := AugmentedError{} | ||
e.Message = text | ||
e.Fields = fields | ||
e.Err = wrappedErr | ||
return &e | ||
} | ||
|
||
// Unwrap returns the underlying error. | ||
func (e *AugmentedError) Unwrap() error { | ||
return e.Err | ||
} | ||
|
||
// Error returns a string representation of the error. | ||
func (e *AugmentedError) Error() string { | ||
if e == nil { | ||
return "" | ||
} | ||
s := e.Message | ||
if e.Fields != nil { | ||
keys := make([]string, 0, len(e.Fields)) | ||
for k := range e.Fields { | ||
keys = append(keys, k) | ||
} | ||
sort.Strings(keys) | ||
|
||
for _, k := range keys { | ||
s += fmt.Sprintf(" [%s=%v]", k, e.Fields[k]) | ||
} | ||
} | ||
if e.Err != nil { | ||
s += " [err=" + e.Err.Error() + "]" | ||
} | ||
return s | ||
} |
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,24 @@ | ||
package exterror_test | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/randlabs/go-exterror" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
func TestAugmentedError(t *testing.T) { | ||
err := exterror.NewAugmentedError( | ||
errors.New("dummy wrapped error"), | ||
"dummy message error", map[string]interface{}{ | ||
"value2": 1000, | ||
"value1": "hello", | ||
}, | ||
) | ||
|
||
if err.Error() != "dummy message error [value1=hello] [value2=1000] [err=dummy wrapped error]" { | ||
t.FailNow() | ||
} | ||
} |
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,3 @@ | ||
module github.com/randlabs/go-exterror | ||
|
||
go 1.19 |
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,22 @@ | ||
package exterror | ||
|
||
import ( | ||
"net" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
// IsNetworkError returns true if the provided error object is related to a network error. | ||
func IsNetworkError(err error) bool { | ||
if err != nil { | ||
switch err.(type) { | ||
case net.Error: | ||
return true | ||
case *net.OpError: | ||
return true | ||
case *net.DNSError: | ||
return true | ||
} | ||
} | ||
return false | ||
} |
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,17 @@ | ||
package exterror_test | ||
|
||
import ( | ||
"net" | ||
"testing" | ||
|
||
"github.com/randlabs/go-exterror" | ||
) | ||
|
||
// ----------------------------------------------------------------------------- | ||
|
||
func TestIsNetworkError(t *testing.T) { | ||
_, err := net.LookupIP("non-existent-domain.123") | ||
if !exterror.IsNetworkError(err) { | ||
t.FailNow() | ||
} | ||
} |