package xerrors import ( "encoding/json" "fmt" "code.nochebuena.dev/einherjar/contracts/errs" ) // Compile-time proof that *Err satisfies the contracts interfaces consumed by logz. var _ errs.CodedError = (*Err)(nil) var _ errs.ContextualError = (*Err)(nil) // Err is a structured application error carrying a Code, a human-readable // message, an optional cause, and optional key-value context fields. // // It implements the standard error interface, errors.Unwrap for cause chaining, // and json.Marshaler for API responses. It also satisfies errs.CodedError and // errs.ContextualError from contracts, enabling logz to enrich log records // automatically without importing this package. // // Use the builder methods WithContext, WithError, and WithPlatformCode to attach // additional information after construction: // // err := xerrors.New(xerrors.ErrInvalidInput, "validation failed"). // WithContext("field", "email"). // WithContext("rule", "required"). // WithError(cause) type Err struct { code Code message string err error fields map[string]any platformCode string } // New creates an Err with the given code and message. No cause is set. func New(code Code, message string) *Err { return &Err{code: code, message: message} } // Wrap creates an Err that wraps an existing error with a code and message. // The wrapped error is accessible via errors.Is, errors.As, and Err.Unwrap. func Wrap(code Code, message string, err error) *Err { return &Err{code: code, message: message, err: err} } // InvalidInput creates an Err with ErrInvalidInput code. func InvalidInput(msg string, args ...any) *Err { return New(ErrInvalidInput, fmt.Sprintf(msg, args...)) } // OutOfRange creates an Err with ErrOutOfRange code. func OutOfRange(msg string, args ...any) *Err { return New(ErrOutOfRange, fmt.Sprintf(msg, args...)) } // Unauthorized creates an Err with ErrUnauthorized code. func Unauthorized(msg string, args ...any) *Err { return New(ErrUnauthorized, fmt.Sprintf(msg, args...)) } // PermissionDenied creates an Err with ErrPermissionDenied code. func PermissionDenied(msg string, args ...any) *Err { return New(ErrPermissionDenied, fmt.Sprintf(msg, args...)) } // NotFound creates an Err with ErrNotFound code. func NotFound(msg string, args ...any) *Err { return New(ErrNotFound, fmt.Sprintf(msg, args...)) } // AlreadyExists creates an Err with ErrAlreadyExists code. func AlreadyExists(msg string, args ...any) *Err { return New(ErrAlreadyExists, fmt.Sprintf(msg, args...)) } // Aborted creates an Err with ErrAborted code. func Aborted(msg string, args ...any) *Err { return New(ErrAborted, fmt.Sprintf(msg, args...)) } // Gone creates an Err with ErrGone code. func Gone(msg string, args ...any) *Err { return New(ErrGone, fmt.Sprintf(msg, args...)) } // PreconditionFailed creates an Err with ErrPreconditionFailed code. func PreconditionFailed(msg string, args ...any) *Err { return New(ErrPreconditionFailed, fmt.Sprintf(msg, args...)) } // RateLimited creates an Err with ErrRateLimited code. func RateLimited(msg string, args ...any) *Err { return New(ErrRateLimited, fmt.Sprintf(msg, args...)) } // Cancelled creates an Err with ErrCancelled code. func Cancelled(msg string, args ...any) *Err { return New(ErrCancelled, fmt.Sprintf(msg, args...)) } // Internal creates an Err with ErrInternal code. func Internal(msg string, args ...any) *Err { return New(ErrInternal, fmt.Sprintf(msg, args...)) } // DataLoss creates an Err with ErrDataLoss code. func DataLoss(msg string, args ...any) *Err { return New(ErrDataLoss, fmt.Sprintf(msg, args...)) } // NotImplemented creates an Err with ErrNotImplemented code. func NotImplemented(msg string, args ...any) *Err { return New(ErrNotImplemented, fmt.Sprintf(msg, args...)) } // Unavailable creates an Err with ErrUnavailable code. func Unavailable(msg string, args ...any) *Err { return New(ErrUnavailable, fmt.Sprintf(msg, args...)) } // DeadlineExceeded creates an Err with ErrDeadlineExceeded code. func DeadlineExceeded(msg string, args ...any) *Err { return New(ErrDeadlineExceeded, fmt.Sprintf(msg, args...)) } // WithContext adds a key-value pair to the error's context fields and returns // the receiver for chaining. Calling it multiple times with the same key // overwrites the previous value. func (e *Err) WithContext(key string, value any) *Err { if e.fields == nil { e.fields = make(map[string]any) } e.fields[key] = value return e } // WithError sets the underlying cause and returns the receiver for chaining. func (e *Err) WithError(err error) *Err { e.err = err return e } // WithPlatformCode sets a platform-level error code and returns the receiver // for chaining. Platform codes are domain-specific identifiers (e.g. // "EMPLOYEE_NOT_FOUND") intended for consuming applications — such as a // frontend — that need to map errors to localised user-facing messages. // // Platform codes are optional. Errors that have no user-actionable meaning // (e.g. 500 internal errors) should not carry one. func (e *Err) WithPlatformCode(code string) *Err { e.platformCode = code return e } // Code returns the typed error code. func (e *Err) Code() Code { return e.code } // Message returns the human-readable error message. func (e *Err) Message() string { return e.message } // PlatformCode returns the platform-level error code, or "" if none was set. func (e *Err) PlatformCode() string { return e.platformCode } // Fields returns a shallow copy of the context fields. // Returns an empty (non-nil) map if no fields have been set. func (e *Err) Fields() map[string]any { if len(e.fields) == 0 { return map[string]any{} } out := make(map[string]any, len(e.fields)) for k, v := range e.fields { out[k] = v } return out } // Detailed returns a verbose string useful for debugging. // Format: "code: X | message: Y | cause: Z | fields: {...}" func (e *Err) Detailed() string { s := fmt.Sprintf("code: %s | message: %s", e.code, e.message) if e.err != nil { s = fmt.Sprintf("%s | cause: %v", s, e.err) } if len(e.fields) > 0 { s = fmt.Sprintf("%s | fields: %v", s, e.fields) } return s } // Error implements the error interface. // Format: "INVALID_ARGUMENT: username is required → original cause" func (e *Err) Error() string { base := fmt.Sprintf("%s: %s", e.code, e.message) if e.err != nil { base = fmt.Sprintf("%s → %v", base, e.err) } return base } // Unwrap returns the underlying cause, enabling errors.Is and errors.As // to walk the full cause chain. func (e *Err) Unwrap() error { return e.err } // ErrorCode satisfies errs.CodedError. logz calls this to append error_code // to log records without importing this package. func (e *Err) ErrorCode() string { return string(e.code) } // ErrorContext satisfies errs.ContextualError. logz calls this to append // context fields to log records. The returned map is read-only by logz; // use Fields() if you need a safe copy. func (e *Err) ErrorContext() map[string]any { return e.fields } // MarshalJSON implements json.Marshaler. // Output: {"code":"NOT_FOUND","platform_code":"...","message":"...","fields":{...}} // platform_code and fields are omitted when empty. func (e *Err) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Code string `json:"code"` PlatformCode string `json:"platform_code,omitempty"` Message string `json:"message"` Fields map[string]any `json:"fields,omitempty"` }{ Code: string(e.code), PlatformCode: e.platformCode, Message: e.message, Fields: e.fields, }) }