errors

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2026 License: MIT Imports: 3 Imported by: 0

README

Errors Package

Custom error types and error handling utilities for Nivo services.

Overview

The errors package provides structured error handling with error codes, HTTP status code mapping, and support for error wrapping and details. It ensures consistent error responses across all services.

Features

  • Error Codes: Predefined codes for common error scenarios
  • HTTP Mapping: Automatic HTTP status code mapping
  • Error Wrapping: Support for Go 1.13+ error wrapping
  • Details: Attach structured metadata to errors
  • Type Checking: Helper functions for error type identification

Error Codes

Client Errors (4xx)
  • NOT_FOUND - Resource not found
  • BAD_REQUEST - Invalid request
  • VALIDATION_ERROR - Input validation failed
  • UNAUTHORIZED - Authentication required
  • FORBIDDEN - Permission denied
  • CONFLICT - Resource conflict
  • RATE_LIMIT_EXCEEDED - Too many requests
  • PRECONDITION_FAILED - Precondition not met
Server Errors (5xx)
  • INTERNAL_ERROR - Internal server error
  • SERVICE_UNAVAILABLE - Service unavailable
  • TIMEOUT - Request timeout
  • DATABASE_ERROR - Database operation failed
Domain-Specific Errors
  • INSUFFICIENT_FUNDS - Not enough balance
  • ACCOUNT_FROZEN - Account is frozen
  • TRANSACTION_FAILED - Transaction failed
  • DUPLICATE_IDEMPOTENCY_KEY - Idempotency key already used
  • INVALID_AMOUNT - Invalid transaction amount
  • INVALID_CURRENCY - Invalid or unsupported currency

Usage

Creating Errors
import "github.com/1mb-dev/nivomoney/shared/errors"

// Using predefined constructors
err := errors.NotFound("user")
err := errors.BadRequest("missing required field: email")
err := errors.Unauthorized("invalid token")
err := errors.Internal("unexpected error occurred")

// With resource ID
err := errors.NotFoundWithID("wallet", "wallet-123")
// Output: "wallet with id wallet-123 not found"
Custom Errors
// Create custom error
err := errors.New(errors.ErrCodeValidation, "email format is invalid")

// With error code
err := errors.New(errors.ErrCodeInsufficientFunds, "balance too low")
Adding Details
// Add structured details
err := errors.Validation("invalid request").
    AddDetail("field", "email").
    AddDetail("constraint", "must be valid email format")

// Add multiple details at once
details := map[string]interface{}{
    "field": "amount",
    "min":   0.01,
    "max":   10000.00,
}
err := errors.Validation("amount out of range").WithDetails(details)
Validation with Field Errors
fieldErrors := map[string]string{
    "email":    "must be valid email",
    "password": "must be at least 8 characters",
    "age":      "must be 18 or older",
}
err := errors.ValidationWithFields("validation failed", fieldErrors)
Wrapping Errors
// Wrap existing error
dbErr := db.Query(...)
if dbErr != nil {
    return errors.DatabaseWrap(dbErr, "failed to fetch user")
}

// Wrap with formatted message
err := errors.Wrapf(dbErr, errors.ErrCodeDatabaseError,
    "failed to query table %s", tableName)

// Wrap as internal error
err := errors.InternalWrap(panicErr, "recovered from panic")
HTTP Status Code Mapping
err := errors.NotFound("user")
statusCode := err.HTTPStatusCode()  // Returns 404

// Or use the helper function
statusCode := errors.GetHTTPStatus(err)
Error Type Checking
err := someOperation()

// Check specific error types
if errors.IsNotFound(err) {
    // Handle not found
}

if errors.IsValidation(err) {
    // Handle validation error
}

if errors.IsUnauthorized(err) {
    // Handle auth error
}

if errors.IsInternal(err) {
    // Log and return generic message
}

// Get error code
code := errors.GetErrorCode(err)
switch code {
case errors.ErrCodeNotFound:
    // Handle not found
case errors.ErrCodeValidation:
    // Handle validation
}
Error Unwrapping
// Standard Go error unwrapping works
underlying := errors.Unwrap(err)

// Check if error wraps another
if errors.Is(err, sql.ErrNoRows) {
    // Handle no rows
}

// Extract specific error type
var dbError *DatabaseError
if errors.As(err, &dbError) {
    // Handle database error
}

HTTP Handler Integration

func handleError(w http.ResponseWriter, err error) {
    // Extract error details
    var customErr *errors.Error
    if errors.As(err, &customErr) {
        statusCode := customErr.HTTPStatusCode()
        response := map[string]interface{}{
            "error": map[string]interface{}{
                "code":    customErr.Code,
                "message": customErr.Message,
                "details": customErr.Details,
            },
        }
        w.WriteHeader(statusCode)
        json.NewEncoder(w).Encode(response)
        return
    }

    // Fallback for non-custom errors
    w.WriteHeader(http.StatusInternalServerError)
    json.NewEncoder(w).Encode(map[string]string{
        "error": "internal server error",
    })
}

Complete Example

package main

import (
    "database/sql"
    "github.com/1mb-dev/nivomoney/shared/errors"
)

func GetUser(id string) (*User, error) {
    // Validate input
    if id == "" {
        return nil, errors.BadRequest("user id is required")
    }

    // Query database
    user, err := db.QueryUser(id)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, errors.NotFoundWithID("user", id)
        }
        return nil, errors.DatabaseWrap(err, "failed to query user")
    }

    // Check business rules
    if user.Status == "frozen" {
        return nil, errors.AccountFrozen("user account is frozen")
    }

    return user, nil
}

func TransferMoney(from, to string, amount float64) error {
    // Validate amount
    if amount <= 0 {
        return errors.Validation("amount must be positive").
            AddDetail("amount", amount)
    }

    // Check balance
    balance := getBalance(from)
    if balance < amount {
        return errors.InsufficientFunds("insufficient balance").
            AddDetail("balance", balance).
            AddDetail("required", amount)
    }

    // Perform transfer
    if err := doTransfer(from, to, amount); err != nil {
        return errors.TransactionFailed("transfer failed").
            AddDetail("from", from).
            AddDetail("to", to).
            AddDetail("amount", amount)
    }

    return nil
}

JSON Response Format

When serialized to JSON, errors have the following structure:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "invalid input",
    "details": {
      "field": "email",
      "constraint": "must be valid email format"
    }
  }
}

Best Practices

  1. Use Specific Error Codes: Choose the most specific error code
  2. Add Context: Use WithDetails to add helpful debugging information
  3. Wrap Underlying Errors: Preserve error chains with Wrap
  4. Don't Expose Internal Details: Be careful with error messages in production
  5. Log Internal Errors: Always log full error details server-side
  6. Return Generic Messages: Return generic messages to clients for internal errors

Testing

go test ./shared/errors/...
go test -cover ./shared/errors/...

Documentation

Overview

Package errors provides custom error types and error handling utilities for Nivo services.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target interface{}) bool

As finds the first error in err's chain that matches target.

func GetHTTPStatus

func GetHTTPStatus(err error) int

GetHTTPStatus extracts the HTTP status code from an error.

func Is

func Is(err error, target error) bool

Is checks if an error is of a specific type using errors.Is.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden checks if an error is a forbidden error.

func IsInternal

func IsInternal(err error) bool

IsInternal checks if an error is an internal server error.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound checks if an error is a not found error.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized checks if an error is an unauthorized error.

func IsValidation

func IsValidation(err error) bool

IsValidation checks if an error is a validation error.

Types

type Error

type Error struct {
	Code    ErrorCode              `json:"code"`
	Message string                 `json:"message"`
	Details map[string]interface{} `json:"details,omitempty"`
	Err     error                  `json:"-"` // Underlying error (not exposed in JSON)
}

Error represents a structured error with code, message, and details.

func AccountFrozen

func AccountFrozen(message string) *Error

AccountFrozen creates an account frozen error.

func BadRequest

func BadRequest(message string) *Error

BadRequest creates a bad request error.

func Conflict

func Conflict(message string) *Error

Conflict creates a conflict error.

func Database

func Database(message string) *Error

Database creates a database error.

func DatabaseWrap

func DatabaseWrap(err error, message string) *Error

DatabaseWrap wraps a database error.

func DuplicateIdempotencyKey

func DuplicateIdempotencyKey(key string) *Error

DuplicateIdempotencyKey creates a duplicate idempotency key error.

func Forbidden

func Forbidden(message string) *Error

Forbidden creates a forbidden error.

func Gone

func Gone(message string) *Error

Gone creates a gone error for resources that are no longer available.

func InsufficientFunds

func InsufficientFunds(message string) *Error

InsufficientFunds creates an insufficient funds error.

func Internal

func Internal(message string) *Error

Internal creates an internal server error.

func InternalWrap

func InternalWrap(err error, message string) *Error

InternalWrap wraps an error as an internal server error.

func InvalidOTP

func InvalidOTP(attemptsRemaining int) *Error

InvalidOTP creates an invalid OTP error. attemptsRemaining indicates how many more attempts the user has.

func LimitExceeded

func LimitExceeded(message string) *Error

LimitExceeded creates a limit exceeded error.

func New

func New(code ErrorCode, message string) *Error

New creates a new Error with the given code and message.

func NotFound

func NotFound(resource string) *Error

NotFound creates a not found error.

func NotFoundWithID

func NotFoundWithID(resource, id string) *Error

NotFoundWithID creates a not found error with resource ID.

func Timeout

func Timeout(message string) *Error

Timeout creates a timeout error.

func TooManyRequests

func TooManyRequests(message string) *Error

TooManyRequests creates a rate limit exceeded error.

func TransactionFailed

func TransactionFailed(message string) *Error

TransactionFailed creates a transaction failed error.

func Unauthorized

func Unauthorized(message string) *Error

Unauthorized creates an unauthorized error.

func Unavailable

func Unavailable(message string) *Error

Unavailable creates a service unavailable error.

func Validation

func Validation(message string) *Error

Validation creates a validation error.

func ValidationWithFields

func ValidationWithFields(message string, fields map[string]string) *Error

ValidationWithFields creates a validation error with field details.

func VerificationExpired

func VerificationExpired() *Error

VerificationExpired creates a verification expired error. Used when a verification request has expired and is no longer valid.

func VerificationRequired

func VerificationRequired(message string) *Error

VerificationRequired creates a verification required error. Used when an operation requires OTP verification to proceed.

func Wrap

func Wrap(err error, code ErrorCode, message string) *Error

Wrap wraps an existing error with a code and message.

func Wrapf

func Wrapf(err error, code ErrorCode, format string, args ...interface{}) *Error

Wrapf wraps an error with a formatted message.

func (*Error) AddDetail

func (e *Error) AddDetail(key string, value interface{}) *Error

AddDetail adds a single detail to the error.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) HTTPStatusCode

func (e *Error) HTTPStatusCode() int

HTTPStatusCode returns the appropriate HTTP status code for this error.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying error for error wrapping support.

func (*Error) WithDetails

func (e *Error) WithDetails(details map[string]interface{}) *Error

WithDetails adds details to the error.

type ErrorCode

type ErrorCode string

ErrorCode represents a specific error type that can be used by clients.

const (
	// Client errors (4xx)
	ErrCodeNotFound          ErrorCode = "NOT_FOUND"
	ErrCodeBadRequest        ErrorCode = "BAD_REQUEST"
	ErrCodeValidation        ErrorCode = "VALIDATION_ERROR"
	ErrCodeUnauthorized      ErrorCode = "UNAUTHORIZED"
	ErrCodeForbidden         ErrorCode = "FORBIDDEN"
	ErrCodeConflict          ErrorCode = "CONFLICT"
	ErrCodeRateLimit         ErrorCode = "RATE_LIMIT_EXCEEDED"
	ErrCodePrecondition      ErrorCode = "PRECONDITION_FAILED"
	ErrCodeInsufficientFunds ErrorCode = "INSUFFICIENT_FUNDS"

	// Server errors (5xx)
	ErrCodeInternal      ErrorCode = "INTERNAL_ERROR"
	ErrCodeUnavailable   ErrorCode = "SERVICE_UNAVAILABLE"
	ErrCodeTimeout       ErrorCode = "TIMEOUT"
	ErrCodeDatabaseError ErrorCode = "DATABASE_ERROR"

	// Domain-specific errors
	ErrCodeInvalidAmount           ErrorCode = "INVALID_AMOUNT"
	ErrCodeInvalidCurrency         ErrorCode = "INVALID_CURRENCY"
	ErrCodeAccountFrozen           ErrorCode = "ACCOUNT_FROZEN"
	ErrCodeTransactionFailed       ErrorCode = "TRANSACTION_FAILED"
	ErrCodeDuplicateIdempotencyKey ErrorCode = "DUPLICATE_IDEMPOTENCY_KEY"

	// Verification errors
	ErrCodeVerificationRequired ErrorCode = "VERIFICATION_REQUIRED"
	ErrCodeVerificationExpired  ErrorCode = "VERIFICATION_EXPIRED"
	ErrCodeInvalidOTP           ErrorCode = "INVALID_OTP"
	ErrCodeLimitExceeded        ErrorCode = "LIMIT_EXCEEDED"
	ErrCodeGone                 ErrorCode = "GONE"
)

Error codes for common error scenarios.

func GetErrorCode

func GetErrorCode(err error) ErrorCode

GetErrorCode extracts the error code from an error if it's a custom Error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL