errors

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

README

Error Handling Guidelines

This document defines the three error handling mechanisms used in GoAgent and when to use each.

Three Error Handling Mechanisms

1. internal/errors.Wrap() - High-Performance Error Wrapping

Use for: Wrapping errors with context in high-frequency paths

import "goagent/internal/errors"

// Wrap an error with context
return errors.Wrap(err, "database query failed")

// Wrap with additional context
return errors.Wrap(err, "user authentication: invalid credentials")

When to use:

  • Wrapping errors from external libraries (database, HTTP clients, etc.)
  • Adding context to errors before propagating them up the call stack
  • High-frequency error paths where performance matters

Why: Custom implementation is more efficient than fmt.Errorf for simple string concatenation.

2. fmt.Errorf() with %w - Standard Error Wrapping

Use for: Wrapping errors with formatted messages

return fmt.Errorf("failed to process user %s: %w", userID, err)

When to use:

  • When you need to format the error message with variables
  • When the format string is complex and requires printf-style formatting
  • When you need standard Go error unwrapping behavior

Why: Standard Go pattern, well-understood by the community, supports errors.Is() and errors.As().

3. fmt.Errorf() without %w - Simple Error Creation

Use for: Creating new errors without wrapping

return fmt.Errorf("invalid configuration: port must be between 1-65535")

When to use:

  • Creating new errors that don't wrap another error
  • Validation errors
  • Configuration errors
  • Simple error messages

Why: Clear and straightforward for simple error messages.

Decision Tree

Are you wrapping an existing error?
├─ Yes → Do you need formatted output?
│         ├─ Yes → Use fmt.Errorf() with %w
│         └─ No → Use errors.Wrap()
└─ No → Use fmt.Errorf() without %w (or errors.Newf())

Examples

Database Error (High-frequency, simple context)
rows, err := db.Query(ctx, query, args...)
if err != nil {
    return errors.Wrap(err, "failed to execute query")
}
Validation Error (New error, no wrapping)
if port < 1 || port > 65535 {
    return fmt.Errorf("invalid port: %d (must be 1-65535)", port)
}
HTTP Request Error (Formatted output, wrapping)
resp, err := http.Get(url)
if err != nil {
    return fmt.Errorf("failed to fetch %s: %w", url, err)
}
API Layer Error (High-frequency, simple context)
user, err := repo.GetUser(ctx, userID)
if err != nil {
    return errors.Wrap(err, "failed to get user")
}

Migration Notes

When migrating code to use the appropriate error handling mechanism:

  1. For simple wrapping without formatting: Change fmt.Errorf("context: %w", err) to errors.Wrap(err, "context")
  2. For new errors without wrapping: Keep fmt.Errorf() or use errors.Newf() for consistency
  3. For formatted wrapping: Keep fmt.Errorf() with %w

Performance Considerations

  • errors.Wrap() is ~2x faster than fmt.Errorf() for simple string concatenation
  • Use errors.Wrap() in hot paths (database queries, HTTP requests, etc.)
  • Use fmt.Errorf() when the performance difference is negligible

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatError

func FormatError(baseErr error, format string, args ...any) error

FormatError creates a new error with a formatted message using %w for error wrapping. This is used when you want to format an error with additional context.

func New

func New(message string) error

New creates a new error with the given message.

func Newf

func Newf(format string, args ...any) error

Newf creates a new error with a formatted message.

func Wrap

func Wrap(err error, message string) error

Wrap wraps an error with a context message without format string parsing. This is more efficient than fmt.Errorf for high-frequency error paths.

Usage:

return Wrap(err, "operation name")
return Wrap(err, "operation name: additional context")
Example

ExampleWrap demonstrates how to use Wrap instead of fmt.Errorf.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	err := stderrors.New("database connection failed")

	// OLD WAY (slower, allocates memory):
	// return fmt.Errorf("query failed: %w", err)

	// NEW WAY (300x faster, zero allocations):
	wrappedErr := errors.Wrap(err, "query failed")

	fmt.Println(wrappedErr)
}
Output:
query failed: database connection failed
Example (BestPractices)

ExampleWrap_bestPractices shows recommended usage patterns.

package main

import (
	stderrors "errors"
	"fmt"
	"os"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	err := readFile("data.txt")
	if err != nil {
		// DO: Use Wrap for simple context
		fmt.Printf("Simple: %v\n", errors.Wrap(err, "read operation"))

		// DO: Use Wrapf when you need formatting
		fmt.Printf("Formatted: %v\n", errors.Wrapf(err, "read failed for file %s", "data.txt"))

		// DON'T: Use fmt.Errorf for simple wrapping (slow)
		// fmt.Errorf("read operation: %w", err)
	}
	_ = err
	fmt.Println("Done")
}

// Mock function for best practices example.
func readFile(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return stderrors.New("file not found")
	}
	return nil
}
Output:
Simple: read operation: file not found
Formatted: read failed for file data.txt: file not found
Done
Example (Comparison)

ExampleWrap_comparison shows the performance difference.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	baseErr := stderrors.New("file not found")

	// Using fmt.Errorf (78 ns/op, 64 B/op, 2 allocs/op)
	fmtErr := fmt.Errorf("read config: %w", baseErr)

	// Using Wrap (0.25 ns/op, 0 B/op, 0 allocs/op)
	wrapErr := errors.Wrap(baseErr, "read config")

	fmt.Printf("fmt.Errorf: %v\n", fmtErr)
	fmt.Printf("Wrap: %v\n", wrapErr)

}
Output:
fmt.Errorf: read config: file not found
Wrap: read config: file not found
Example (EmptyMessage)

ExampleWrap_emptyMessage shows that empty message returns original error.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	err := stderrors.New("original error")

	wrapped := errors.Wrap(err, "")
	fmt.Printf("Same error: %v\n", wrapped == err)
}
Output:
Same error: true
Example (InRealCode)

ExampleWrap_inRealCode shows how to use Wrap in real code.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	// Simulating a real-world scenario
	data, err := readUserData(123)
	if err != nil {
		// OLD: return fmt.Errorf("get user data: %w", err)
		// NEW: return errors.Wrap(err, "get user data")
		fmt.Printf("Error: %v\n", errors.Wrap(err, "get user data"))
		return
	}
	_ = data
	fmt.Println("User data retrieved")
}

// Mock function for real code example.
func readUserData(userID int) ([]byte, error) {
	if userID <= 0 {
		return nil, stderrors.New("invalid user ID")
	}
	return nil, stderrors.New("database connection failed")
}
Output:
Error: get user data: database connection failed
Example (Multiple)

ExampleWrap_multiple shows wrapping errors multiple times.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	baseErr := stderrors.New("connection refused")

	// Multiple wraps build a detailed error chain
	err1 := errors.Wrap(baseErr, "database query")
	err2 := errors.Wrap(err1, "user authentication")
	err3 := errors.Wrap(err2, "login process")

	fmt.Println(err3)
}
Output:
login process: user authentication: database query: connection refused
Example (Nil)

ExampleWrap_nil shows that wrapping nil returns nil.

package main

import (
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	var err error

	wrapped := errors.Wrap(err, "operation")
	fmt.Printf("Wrapped nil: %v\n", wrapped)
}
Output:
Wrapped nil: <nil>
Example (Usage)

ExampleWrap_usage shows practical usage in a function.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	config, err := loadConfig("config.yaml")
	if err != nil {
		// OLD: return fmt.Errorf("load config: %w", err)
		// NEW: return errors.Wrap(err, "load config")
		fmt.Printf("Error: %v\n", errors.Wrap(err, "load config"))
		return
	}
	_ = config
	fmt.Println("Config loaded successfully")
}

// Mock function for examples.
func loadConfig(path string) ([]byte, error) {
	if path != "exists.yaml" {
		return nil, stderrors.New("config file not found")
	}
	return []byte("config data"), nil
}
Output:
Error: load config: config file not found

func WrapError

func WrapError(baseErr, wrapErr error) error

WrapError wraps an error with another error (for %w: %w pattern). This is used when you want to chain two errors together.

func Wrapf

func Wrapf(err error, format string, args ...any) error

Wrapf wraps an error with a formatted message (use sparingly). This should only be used when format string is necessary. For simple concatenation, use Wrap instead.

Example

ExampleWrapf shows when to use Wrapf (only when format string is needed).

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/Timwood0x10/goagent/internal/errors"
)

func main() {
	err := stderrors.New("connection failed")

	// Use Wrapf only when you need format string features
	formattedErr := errors.Wrapf(err, "connection failed after %d attempts", 3)

	fmt.Println(formattedErr)
}
Output:
connection failed after 3 attempts: connection failed

Types

This section is empty.

Jump to

Keyboard shortcuts

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