-
Notifications
You must be signed in to change notification settings - Fork 532
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
29 changed files
with
782 additions
and
393 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
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
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 |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<div align="center"> | ||
<h1 align="center">Fault</h1> | ||
</div> | ||
|
||
|
||
Fault is an error handling package for Go, providing rich context preservation | ||
and safe error reporting in applications. It combines debugging capabilities with | ||
secure user communication patterns. | ||
|
||
## Inspiration | ||
|
||
I goodartistscopygreatartistssteal'd a lot from [Southclaws/fault](https://github.com/Southclaws/fault). | ||
|
||
## Features | ||
|
||
Fault addresses common challenges in production error handling: | ||
|
||
- Separating internal error details from user-facing messages | ||
- Maintaining complete error context for debugging | ||
- Providing consistent error classification | ||
- Automatic source location tracking | ||
- Safe error chain unwrapping and inspection | ||
|
||
### Dual-Message Pattern | ||
Maintain separate internal and public error messages: | ||
```go | ||
fault.Wrap(err, | ||
fault.With( | ||
"database error: connection timeout", // internal message | ||
"Service temporarily unavailable" // public message | ||
), | ||
) | ||
``` | ||
|
||
### Error Chain Tracking | ||
Preserve complete error context with source locations: | ||
```go | ||
steps := fault.Unwind(err) | ||
for _, step := range steps { | ||
fmt.Printf("Error at %s: %s\n", step.Location, step.Message) | ||
} | ||
``` | ||
|
||
### Error Classification | ||
Tag errors for consistent handling: | ||
```go | ||
var DATABASE_ERROR = fault.Tag("DATABASE_ERROR") | ||
|
||
err := fault.New("connection failed", | ||
fault.WithTag(DATABASE_ERROR), | ||
) | ||
|
||
switch fault.GetTag(err) { | ||
case DATABASE_ERROR: | ||
// handle | ||
default: | ||
// handle | ||
} | ||
``` | ||
|
||
### Location Tracking | ||
Automatic capture of error locations: | ||
```go | ||
err := fault.New("initial error") // captures location | ||
err = fault.Wrap(err, fault.With(...)) // captures new location | ||
``` | ||
|
||
## Philosophy | ||
|
||
Fault is built on these principles: | ||
|
||
Fault embraces Go’s simplicity and power. By focusing only on essential | ||
abstractions, it keeps your code clean, maintainable, and in harmony with | ||
Go’s design principles. | ||
|
||
## Usage | ||
|
||
Fault integrates seamlessly with existing Go code: | ||
|
||
```go | ||
func ProcessOrder(id string) error { | ||
order, err := db.FindOrder(id) | ||
if err != nil { | ||
return fault.Wrap(err, | ||
fault.WithTag(DATABASE_ERROR), | ||
fault.With( | ||
fmt.Sprintf("failed to find order %s", id), | ||
"Order not found", | ||
), | ||
) | ||
} | ||
|
||
// ... process order ... | ||
} | ||
``` |
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,72 @@ | ||
package fault | ||
|
||
import ( | ||
"errors" | ||
"slices" | ||
) | ||
|
||
// Step represents a single step in an error chain, containing both | ||
// the error message and the location where it occurred. | ||
type Step struct { | ||
// Message contains the error message for this step | ||
Message string | ||
// Location contains the file:line where this error occurred | ||
Location string | ||
} | ||
|
||
// Unwind extracts all the steps from an error chain, producing a slice | ||
// of Steps that can be used for detailed error reporting and debugging. | ||
// It preserves both the error messages and the locations where they occurred. | ||
// | ||
// Example: | ||
// | ||
// err := New("base error") | ||
// err = fault.Wrap(err, fault.With("missing paramter", "The request is missing a parameter")) | ||
// steps := fault.Unwind(err) | ||
// for _, step := range steps { | ||
// fmt.Printf("Error at %s: %s\n", step.Location, step.Message) | ||
// } | ||
func Unwind(err error) []Step { | ||
if err == nil { | ||
return []Step{} | ||
} | ||
|
||
chain := []error{} | ||
|
||
for err != nil { | ||
chain = append(chain, err) | ||
err = errors.Unwrap(err) | ||
} | ||
|
||
lastLocation := "" | ||
steps := []Step{} | ||
for i := len(chain) - 1; i >= 0; i-- { | ||
err := chain[i] | ||
var next error | ||
if i+1 < len(chain) { | ||
next = chain[i+1] | ||
} | ||
|
||
switch unwithLocation := err.(type) { | ||
case *withLocation: | ||
{ | ||
_, ok := next.(*withLocation) | ||
if ok && unwithLocation.location != "" { | ||
steps = append(steps, Step{Message: "", Location: unwithLocation.location}) | ||
} | ||
lastLocation = unwithLocation.location | ||
} | ||
case *root: | ||
{ | ||
steps = append(steps, Step{Message: unwithLocation.message, Location: unwithLocation.location}) | ||
lastLocation = "" | ||
} | ||
default: | ||
{ | ||
steps = append(steps, Step{Message: err.Error(), Location: lastLocation}) | ||
} | ||
} | ||
} | ||
slices.Reverse(steps) | ||
return steps | ||
} |
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,32 @@ | ||
// Package fault provides a comprehensive error handling system designed for | ||
// building robust applications with rich error context and debugging | ||
// capabilities. It implements a multi-layered error handling approach with | ||
// several key features: | ||
// | ||
// - Dual-message error pattern: Separate internal (debug) and | ||
// public (user-facing) error messages. | ||
// - Error chain tracking: Capture and preserve complete error context with | ||
// source locations. | ||
// - Error tagging: Flexible error classification system for consistent error | ||
// handling. | ||
// - Stack trace preservation: Automatic capture of error locations throughout | ||
// the chain. | ||
// | ||
// It builds upon Go's standard error handling patterns while adding structured | ||
// context and safety mechanisms. | ||
// | ||
// This package is heavily inspired/copied from [fault]. We just wanted a | ||
// slightly different API. | ||
// | ||
// Basic usage: | ||
// | ||
// err := fault.New("database connection failed") | ||
// if err != nil { | ||
// return fault.Wrap(err, | ||
// fault.WithTag(DATABASE_ERROR), | ||
// fault.With("init failed: %v", "Service temporarily unavailable"), | ||
// ) | ||
// } | ||
// | ||
// [fault] https://github.com/Southclaws/fault | ||
package fault |
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 fault | ||
|
||
import ( | ||
"fmt" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
// withLocation represents an error with an associated call location. | ||
// It's used internally to track where in the code errors occur. | ||
type withLocation struct { | ||
// err is the underlying error being withLocation | ||
err error | ||
// location contains the file and line number where the error was withLocation | ||
location string | ||
} | ||
|
||
// Error implements the error interface by returning the underlying error messages | ||
// in the order they were added to the chain, from oldest to newest. | ||
// | ||
// Example: | ||
// | ||
// baseErr := fault.New("initial error") | ||
// withLocation := fault.Wrap(baseErr, fault.With }) | ||
// fmt.Println(withLocation.Error()) // Output will contain all messages in chain | ||
func (w *withLocation) Error() string { | ||
errs := []string{} | ||
chain := Unwind(w) | ||
|
||
for i := len(chain) - 1; i >= 0; i-- { | ||
errs = append(errs, chain[i].Message) | ||
} | ||
return strings.Join(errs, ": ") | ||
|
||
} | ||
|
||
// getLocation returns a string containing the file name and line number | ||
// where it was called. It skips 3 frames in the call stack to get the | ||
// actual location where the error was created rather than the internal | ||
// function calls. | ||
// | ||
// The returned string is in the format "filename:linenumber" | ||
// | ||
// Example: | ||
// | ||
// loc := getLocation() // might return "main.go:42" | ||
func getLocation() string { | ||
pc := make([]uintptr, 1) | ||
runtime.Callers(3, pc) | ||
cf := runtime.CallersFrames(pc) | ||
f, _ := cf.Next() | ||
|
||
return fmt.Sprintf("%s:%d", f.File, f.Line) | ||
} |
Oops, something went wrong.