Line data Source code
1 : // Package apperr provides a small error type carrying an application Code,
2 : // a client-safe message, optional details, and a wrapped cause.
3 : // This lets usecases and handlers communicate stable error shapes to clients,
4 : // while preserving the original error for logs and tracing via errors.Unwrap.
5 : package apperr
6 :
7 : import (
8 : "errors"
9 : "fmt"
10 : )
11 :
12 : // Error is the application error that flows through usecase→handler.
13 : // - Code : machine-friendly classification
14 : // - Message : client-safe, human-readable text
15 : // - Details : optional structured data (e.g., field errors, retry hints)
16 : // - Cause : wrapped underlying error for logging/diagnostics
17 : type Error struct {
18 : Code Code
19 : Message string
20 : Details map[string]any
21 : Cause error
22 : }
23 :
24 : // Error implements the error interface.
25 0 : func (e *Error) Error() string {
26 0 : if e.Message != "" {
27 0 : return fmt.Sprintf("%s: %s", e.Code, e.Message)
28 0 : }
29 0 : return string(e.Code)
30 : }
31 :
32 : // Unwrap exposes the underlying cause for errors.Is / errors.As.
33 0 : func (e *Error) Unwrap() error { return e.Cause }
34 :
35 : // New constructs a new app error without a wrapped cause.
36 4 : func New(code Code, msg string, details map[string]any) *Error {
37 4 : return &Error{Code: code, Message: msg, Details: details}
38 4 : }
39 :
40 : // Wrap constructs a new app error with a wrapped cause.
41 0 : func Wrap(code Code, msg string, cause error, details map[string]any) *Error {
42 0 : return &Error{Code: code, Message: msg, Cause: cause, Details: details}
43 0 : }
44 :
45 : // Is reports whether err is an *apperr.Error with the given Code.
46 0 : func Is(err error, code Code) bool {
47 0 : var ae *Error
48 0 : return errors.As(err, &ae) && ae.Code == code
49 0 : }
|