template/pkg/result/result.go
2026-04-07 20:32:27 +00:00

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 }