ez

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2024 License: MIT Imports: 5 Imported by: 66

README

ez

ez is a minimalistic Go package for error handling, that makes errors a first-class citizen in your application domain.

It provides a clean, easy (pun intended) and consistent way to handle errors across different consumer roles: your application logic, end users and developers.

Based on Ben Johnson Failure is your domain awesome post.

Why ez?

Go's error handling can be challenging - while errors are core to the language, there's no prescribed way to handle them effectively. ez solves this by providing:

  • Role-Based Error Handling: Different error information for different consumers

    • 🤖 Application: Clean error codes for programmatic handling
    • 👤 End Users: Clear, actionable error messages
    • 👨💻 Developers: Detailed logical stack traces for debugging
  • Domain-Centric Design: Errors become part of your domain model, just like your Customer or Order types

  • Clean Stack Traces: Logical operation tracking without the noise of full stack traces

  • Standard Error Codes: Pre-defined, widely-applicable error codes inspired by HTTP/gRPC standards

Installation

go get github.com/vanclief/ez

Quick Start

import "github.com/vanclief/ez"

// Create a new error
err := ez.New(
    "UserService.CreateUser",  // Operation name
    ez.EINVALID,              // Error code
    "Username cannot be empty", // User-friendly message
    nil,                      // Optional underlying error
)

// Check error codes
if ez.ErrorCode(err) == ez.EINVALID {
    // Handle validation error
}

// Get user-friendly message
message := ez.ErrorMessage(err) // "Username cannot be empty"

// Get full error trace for developers
ez.ErrorStackTrace(err) // "UserService.CreateUser: <invalid> Username cannot be empty"

Core Features

1. Standardized Error Codes

Pre-defined error codes that cover most common scenarios:

const (
    ECONFLICT   = "conflict"           // Action cannot be performed
    EINTERNAL   = "internal"           // Internal error
    EINVALID    = "invalid"            // Validation failed
    ENOTFOUND   = "not_found"          // Entity does not exist
    ENOTAUTHORIZED    = "not_authorized"     // Missing permissions
    ENOTAUTHENTICATED = "not_authenticated"  // Not authenticated
    ERESOURCEEXHAUSTED = "resource_exhausted" // Resource exhausted
    ENOTIMPLEMENTED    = "not_implemented"    // Not implemented
    EUNAVAILABLE       = "unavailable"        // System unavailable
)
2. Error Wrapping

Build logical stack traces by wrapping errors:

func (s *UserService) CreateUser(ctx context.Context, user *User) error {
    const op = "UserService.CreateUser"

    // Validate user
    if user.Username == "" {
        return ez.New(op, ez.EINVALID, "Username is required", nil)
    }

    // Try to create user
    if err := s.db.CreateUser(user); err != nil {
        return ez.Wrap(op, err) // Preserves original error details
    }

    return nil
}
3. Error Data

Attach additional contextual data to errors:


Copy// Add single data field
err := ez.NewRoot(op, ez.EINVALID, "Invalid user data").
    AddData("user_id", "123")

// Add multiple data fields at once
err := ez.NewRoot(op, ez.ECONFLICT, "User already exists").
    AddDataMap(map[string]interface{}{
        "username": user.Username,
        "email":    user.Email,
    })

// Access error data
data := ez.ErrorData(err) // Returns map[string]interface{}
userID := data["user_id"].(string)

Data is preserved when wrapping errors:

err := ez.NewRoot(op, ez.ENOTFOUND, "User not found").
    AddData("user_id", "123")

wrappedErr := ez.Wrap("UserService.GetUser", err)
data := ez.ErrorData(wrappedErr) // Still contains "user_id"
4. Error Information Extraction

Easy access to error details:

// Get error code
code := ez.ErrorCode(err)    // e.g., "invalid"

// Get user message
msg := ez.ErrorMessage(err)  // e.g., "Username is required"

// Get full error trace (for developers)
ez.ErrorStacktrace(err)         // e.g., "UserService.CreateUser: <invalid> Username is required"

Example

Here's an example showing how to handle errors with ez:

func (s *UserService) CreateUser(ctx context.Context, user *User) error {
    const op = "UserService.CreateUser"

    // Validation error (end user focused)
    if user.Username == "" {
        return ez.New(op, ez.EINVALID, "Username is required", nil)
    }

    // Check for conflicts (application logic focused)
    exists, err := s.checkUserExists(user.Username)
    if err != nil {
        return ez.Wrap(op, err) // Wraps internal error for developers
    }
    if exists {
        return ez.New(op, ez.ECONFLICT,
            "Username is already taken. Please choose another one.", nil).AddData("username", user.Username)
    }

    // Database error (developer focused)
    if err := s.db.CreateUser(user); err != nil {
        return ez.Wrap(op, err)
    }

    return nil
}
Handling the Error
user := &User{Username: ""}
err := svc.CreateUser(ctx, user)

// Application logic
switch ez.ErrorCode(err) {
case ez.EINVALID:
    // Handle validation error
case ez.ECONFLICT:
    // Handle conflict error
case ez.EINTERNAL:
    // Handle internal error
}

// End user message
if err != nil {
    fmt.Println("Error:", ez.ErrorMessage(err))
    // Output: "Error: Username is already taken"
    if username, ok := data["username"].(string); ok {
        // Return specific username error
    }
}

// Developer debugging
if err != nil {
    ez.ErrorStacktrace(err)
    // Output: "UserService.CreateUser: <invalid> Username is already taken"
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	ECONFLICT          = "conflict"           // action cannot be performed
	EINTERNAL          = "internal"           // internal error
	EINVALID           = "invalid"            // validation failed
	ENOTFOUND          = "not_found"          // entity does not exist
	ENOTAUTHORIZED     = "not_authorized"     // requester does not have permissions to perform action
	ENOTAUTHENTICATED  = "not_authenticated"  // requester is not authenticated
	ERESOURCEEXHAUSTED = "resource_exhausted" // the resource has been exhausted
	ENOTIMPLEMENTED    = "not_implemented"    // the operation has not been implemented
	EUNAVAILABLE       = "unavailable"        // the system or operation is not available
)

Application error codes

Variables

This section is empty.

Functions

func ErrorCode

func ErrorCode(err error) string

ErrorCode returns the code of the root error, if available. Otherwise returns EINTERNAL.

func ErrorData added in v1.4.0

func ErrorData(err error) map[string]interface{}

ErrorData returns the data of the root error, if available.

func ErrorMessage

func ErrorMessage(err error) string

ErrorMessage returns the human-readable message of the error, if available. Otherwise returns a generic error message.

func ErrorStacktrace added in v1.1.2

func ErrorStacktrace(err error)

ErrorStacktrace prints a human-redable stacktrace of all nested errors.

func ErrorToGRPCCode added in v1.0.1

func ErrorToGRPCCode(err error) codes.Code

ErrorToGRPCCode converts an standar application error code to a GRPC error code

func ErrorToHTTPStatus added in v1.1.0

func ErrorToHTTPStatus(err error) int

ErrorToHTTPStatus converts an standar application error code to a HTTP status

func GRPCCodeToError added in v1.1.0

func GRPCCodeToError(c codes.Code) string

GRPCCodeToError converts a GRPC error code to a standar application error code

func HTTPStatusToError added in v1.1.0

func HTTPStatusToError(status int) string

HTTPStatusToError converts a HTTP error code to a standar application error code

Types

type Error

type Error struct {
	// Machine readable code
	Code string `json:"code"`
	// Human readable message
	Message string `json:"message"`
	// Logical operation
	Op string `json:"op"`
	// Nested error
	Err error `json:"err"`
	// Data about the error
	Data map[string]interface{} `json:"data,omitempty"`
}

Error defines a standar application error

func New

func New(op, code, message string, err error) *Error

New creates and returns a new error

func NewFromGRPC added in v1.0.1

func NewFromGRPC(op string, err error) *Error

NewFromGRPC wraps a GRPC Error into a standar application error

func Root added in v1.4.0

func Root(op, code, message string) *Error

Root creates a new root error

func Wrap

func Wrap(op string, err error) *Error

Wrap returns a new error that contains the passed error but with a different operation, useful for creating stacktraces

func (*Error) AddData added in v1.4.0

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

WithData adds a single key-value pair to the error's data

func (*Error) AddDataMap added in v1.4.0

func (e *Error) AddDataMap(data map[string]interface{}) *Error

WithDataMap adds multiple key-value pairs to the error's data

func (*Error) Error

func (e *Error) Error() string

Error returns the string representation of the error message.

func (*Error) String added in v1.1.2

func (e *Error) String() string

String returns a simplified string representation of the error message

Jump to

Keyboard shortcuts

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