111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package result
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime/debug"
|
|
)
|
|
|
|
// Expect holds either a value of type T or an error.
|
|
type Expect[T any] struct {
|
|
value T
|
|
err error
|
|
}
|
|
|
|
// Ok wraps a successful value in an Expect.
|
|
func Ok[T any](v T) Expect[T] {
|
|
return Expect[T]{value: v}
|
|
}
|
|
|
|
// Fail wraps an error in an Expect.
|
|
func Fail[T any](err error) Expect[T] {
|
|
return Expect[T]{err: err}
|
|
}
|
|
|
|
// Of is a convenience constructor that bridges standard Go (value, error)
|
|
// return signatures:
|
|
//
|
|
// result.Of(os.Open("file.txt")).Expect("open config")
|
|
func Of[T any](v T, err error) Expect[T] {
|
|
return Expect[T]{value: v, err: err}
|
|
}
|
|
|
|
// Err returns the wrapped error, or nil on success.
|
|
func (r Expect[T]) Err() error {
|
|
return r.err
|
|
}
|
|
|
|
// Must returns the value or panics with the wrapped error and a stack trace.
|
|
// Prefer [Expect.Expect] — it adds a message that makes the panic site easy to
|
|
// locate in logs.
|
|
func (r Expect[T]) Must() T {
|
|
if r.err != nil {
|
|
panic(&stackError{err: r.err, stack: debug.Stack()})
|
|
}
|
|
return r.value
|
|
}
|
|
|
|
// Expect returns the value or panics with the error annotated by msg and a
|
|
// stack trace captured at this call site.
|
|
//
|
|
// data := Parse(raw).Expect("parse user input")
|
|
func (r Expect[T]) Expect(msg string) T {
|
|
if r.err != nil {
|
|
panic(&stackError{
|
|
err: fmt.Errorf("%s: %w", msg, r.err),
|
|
stack: debug.Stack(),
|
|
})
|
|
}
|
|
return r.value
|
|
}
|
|
|
|
// Unwrap returns the value and error in the standard Go (value, error) form.
|
|
// Useful at the boundary where you want to re-join normal error-return code.
|
|
func (r Expect[T]) Unwrap() (T, error) {
|
|
return r.value, r.err
|
|
}
|
|
|
|
// Catch recovers a panic produced by [Expect.Must] or [Expect.Expect] and
|
|
// stores it in *errp. Call it via defer at the entry point of any function
|
|
// that runs a happy-path call stack:
|
|
//
|
|
// func run() (err error) {
|
|
// defer result.Catch(&err)
|
|
// ...
|
|
// }
|
|
//
|
|
// The stored error retains its stack trace; retrieve it with [StackTrace].
|
|
// Panics that are not errors (e.g. nil-pointer dereferences) are re-panicked
|
|
// so genuine bugs are not silently swallowed.
|
|
func Catch(errp *error) {
|
|
v := recover()
|
|
if v == nil {
|
|
return
|
|
}
|
|
if err, ok := v.(error); ok {
|
|
*errp = err
|
|
return
|
|
}
|
|
panic(v) // not an error — let it propagate
|
|
}
|
|
|
|
// StackTrace returns the stack trace captured when [Expect.Expect] or
|
|
// [Expect.Must] panicked. Returns an empty string if err was not produced by
|
|
// this package.
|
|
func StackTrace(err error) string {
|
|
var s *stackError
|
|
if errors.As(err, &s) {
|
|
return string(s.stack)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// stackError wraps an error with a stack trace captured at the panic site.
|
|
type stackError struct {
|
|
err error
|
|
stack []byte
|
|
}
|
|
|
|
func (s *stackError) Error() string { return s.err.Error() }
|
|
func (s *stackError) Unwrap() error { return s.err }
|