Menu
Coddy logo textTech

Error Wrapping (fmt.Errorf)

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 54 of 107.

When errors pass through multiple layers of your application, knowing where an error originated becomes crucial. Go's fmt.Errorf function with the %w verb lets you wrap errors, preserving the original error while adding context.

Error wrapping creates a chain of errors. Each layer can add information about what it was trying to do when the error occurred:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    // process data...
    return nil
}

The %w verb is special—it wraps the original error inside the new one. This is different from %v, which just converts the error to a string and loses the original error's identity.

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

When LoadSettings fails, the error message shows the full chain: "loading settings: reading config file: open settings.json: no such file or directory". Each layer adds context, making debugging much easier.

The wrapped error still contains the original error inside it. In the next lesson, you'll learn how to unwrap these errors and check what's inside using errors.Is() and errors.As().

challenge icon

Challenge

Easy

Let's build an order processing system that demonstrates error wrapping across multiple layers. You'll create a chain of functions where each layer adds context to errors, making it easy to trace exactly where problems occur.

You'll organize your code across two files:

  • orders.go: Create the order processing logic with multiple layers that wrap errors as they propagate upward.

    Implement three functions that form a processing chain:

    • ValidateItem(itemID string) error - Returns an error with message "item not found" if the itemID is "INVALID", otherwise returns nil
    • ProcessOrder(orderID, itemID string) error - Calls ValidateItem. If it returns an error, wrap it with context: "processing order [orderID]: %w". Otherwise return nil
    • SubmitOrder(customerName, orderID, itemID string) error - Calls ProcessOrder. If it returns an error, wrap it with context: "submitting order for [customerName]: %w". Otherwise return nil

    Each layer should use fmt.Errorf with the %w verb to wrap the error from the layer below, building a chain of context.

  • main.go: Read order details from input, call SubmitOrder, and display the result. If an error occurs, print the full error chain. If successful, print a confirmation message.

The following inputs will be provided:

  • Line 1: Customer name
  • Line 2: Order ID
  • Line 3: Item ID

Print the result:

  • If an error occurs: Error: [full error chain]
  • If successful: Order [orderID] submitted successfully for [customerName]

For example, given Alice, ORD-123, and INVALID, your output should be:

Error: submitting order for Alice: processing order ORD-123: item not found

Notice how the error message shows the complete chain—you can trace the problem from the top-level submission attempt, through order processing, down to the actual validation failure. Each layer added its own context using %w.

And given Bob, ORD-456, and ITEM-001, your output should be:

Order ORD-456 submitted successfully for Bob

Cheat sheet

Use fmt.Errorf with the %w verb to wrap errors while preserving the original error:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    return nil
}

The %w verb wraps the original error inside the new one, unlike %v which only converts the error to a string and loses the original error's identity.

Error wrapping creates a chain where each layer adds context:

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

The resulting error message shows the full chain: "loading settings: reading config file: open settings.json: no such file or directory"

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Read customer name
	scanner.Scan()
	customerName := scanner.Text()
	
	// Read order ID
	scanner.Scan()
	orderID := scanner.Text()
	
	// Read item ID
	scanner.Scan()
	itemID := scanner.Text()
	
	// TODO: Call SubmitOrder with the input values
	// TODO: If an error occurs, print: Error: [full error chain]
	// TODO: If successful, print: Order [orderID] submitted successfully for [customerName]
	
	_ = customerName
	_ = orderID
	_ = itemID
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming