Wrapping Errors with `%w`
Part of the Logic & Flow section of Coddy's GO journey — lesson 39 of 68.
When errors occur in your application, they often need additional context as they travel up through different layers of your code. Go provides a powerful mechanism for this through error wrapping using the %w format verb with fmt.Errorf.
Error wrapping allows you to add contextual information to an existing error while preserving the original error underneath. This creates an error chain that maintains the full history of what went wrong:
originalErr := errors.New("file not found")
wrappedErr := fmt.Errorf("failed to load config: %w", originalErr)The %w verb is special - it tells fmt.Errorf to wrap the error rather than just convert it to a string. This preserves the original error so it can be examined later using functions like errors.Is and errors.As.
This pattern is particularly valuable in larger applications where an error might pass through multiple functions, each adding its own context. For example, a database connection error might get wrapped with "failed to connect to database", then wrapped again with "unable to start server", creating a clear trail of what happened and where.
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
Challenge
EasyBuild a file processing system that wraps errors with contextual information as they propagate through different layers of your application. This challenge demonstrates how to use the %w format verb with fmt.Errorf to create error chains that preserve the original error while adding meaningful context.
You will receive two inputs:
- A string containing file operation details in the format
"filename,operation,status"(e.g.,"config.json,read,permission_denied") - A string containing system context in the format
"service_name,component"(e.g.,"UserService,ConfigLoader")
Your task is to:
- Parse the first input by splitting on commas to get the filename, operation, and status
- Parse the second input by splitting on commas to get the service name and component
- Create an original error based on the status using
errors.New():- If status is
"permission_denied":"permission denied" - If status is
"not_found":"file not found" - If status is
"corrupted":"file corrupted" - For any other status:
"unknown error"
- If status is
- Create a file-level wrapped error using
fmt.Errorfwith%w:"failed to [operation] file [filename]: %w" - Create a component-level wrapped error:
"[component] error: %w" - Create a service-level wrapped error:
"[service_name] operation failed: %w" - Display the error chain by printing each level:
"Original error: [original_error_message]""File level: [file_level_error_message]""Component level: [component_level_error_message]""Service level: [service_level_error_message]"
- Display a summary of the error chain:
"Error Chain Summary:""File: [filename]""Operation: [operation]""Component: [component]""Service: [service_name]""Root cause: [original_error_message]"
Use the strings package to split the input strings on commas, the errors package to create the original error, and the fmt package to create wrapped errors. This challenge demonstrates how error wrapping with %w creates a clear trail of context as errors move through different layers of your application, making debugging much more effective.
Cheat sheet
Error wrapping allows you to add contextual information to an existing error while preserving the original error. Use the %w format verb with fmt.Errorf:
originalErr := errors.New("file not found")
wrappedErr := fmt.Errorf("failed to load config: %w", originalErr)The %w verb preserves the original error so it can be examined later using functions like errors.Is and errors.As. This creates an error chain that maintains the full history of what went wrong, which is valuable when errors pass through multiple functions in larger applications.
Try it yourself
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// Read input
var fileDetails string
var systemContext string
fmt.Scanln(&fileDetails)
fmt.Scanln(&systemContext)
// Parse inputs
fileInfo := strings.Split(fileDetails, ",")
filename := fileInfo[0]
operation := fileInfo[1]
status := fileInfo[2]
contextInfo := strings.Split(systemContext, ",")
serviceName := contextInfo[0]
component := contextInfo[1]
// TODO: Write your code below
// Create original error based on status
// Create file-level wrapped error
// Create component-level wrapped error
// Create service-level wrapped error
// Print each error level and summary
}This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Logic & Flow
1Advanced Control Flow
Switch with `fallthrough`Breaking from Nested LoopsContinuing a Specific LoopThe `goto` StatementRecap - Advanced Loop Control4Project: Simple Task List
Project SetupAdding a Task7Error Handling In-Depth
Custom Error TypesWrapping Errors with `%w`Unwrapping with `errors.Is`Unwrapping with `errors.As`Understanding `panic`Using `recover`Recap - Safe Division2Structs and Methods
Defining Methods on StructsValue ReceiversPointer ReceiversChoosing ReceiversMethods vs FunctionsRecap - Struct Behavior5Maps In-Depth
Maps of StructsPointers as Map ValuesTesting for Nil MapsComparing MapsRecap - Word Frequency Counter3Interfaces (The Basics)
What is an Interface?Defining an InterfaceImplementing an InterfaceUsing Interface TypesEmpty InterfaceType AssertionsType SwitchRecap - Shapes and Behaviors