Menu
Coddy logo textTech

Unwrapping with `errors.Is`

Part of the Logic & Flow section of Coddy's GO journey — lesson 40 of 68.

When working with wrapped errors, you need a way to check if a specific error exists anywhere in the error chain. The errors.Is function provides exactly this capability, allowing you to identify sentinel errors even when they've been wrapped multiple times.

The errors.Is function takes two parameters: the error you want to examine and the target error you're looking for. It returns true if the target error is found anywhere in the error chain:

var ErrNotFound = errors.New("not found")

wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)

if errors.Is(wrappedErr, ErrNotFound) {
    fmt.Println("This is a not found error")
}

This is the modern, recommended way to check for specific errors in Go. Unlike string comparison or direct equality checks, errors.Is works correctly with wrapped errors and follows the error chain to find matches. This makes your error handling robust and reliable, even as errors pass through multiple layers of your application.

quiz iconTest yourself

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

quiz iconTest yourself

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

quiz iconTest yourself

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

challenge icon

Challenge

Easy

Build a database connection monitoring system that uses errors.Is to identify specific connection errors in wrapped error chains. This challenge demonstrates how to use errors.Is to detect sentinel errors even when they've been wrapped with additional context through multiple application layers.

You will receive two inputs:

  • A string containing database operation details in the format "database_name,operation,error_type" (e.g., "users_db,connect,connection_timeout")
  • A string containing application context in the format "service_name,retry_count" (e.g., "AuthService,3")

Your task is to:

  1. Define three sentinel errors using errors.New():
    • ErrConnectionTimeout with message "connection timeout"
    • ErrConnectionRefused with message "connection refused"
    • ErrDatabaseNotFound with message "database not found"
  2. Parse the first input by splitting on commas to get database name, operation, and error type
  3. Parse the second input by splitting on commas to get service name and retry count
  4. Create the original error based on the error type:
    • If error type is "connection_timeout": use ErrConnectionTimeout
    • If error type is "connection_refused": use ErrConnectionRefused
    • If error type is "database_not_found": use ErrDatabaseNotFound
    • For any other error type: use errors.New("unknown database error")
  5. Create a database-level wrapped error using fmt.Errorf with %w: "failed to [operation] database [database_name]: %w"
  6. Create a service-level wrapped error: "[service_name] database operation failed after [retry_count] retries: %w"
  7. Use errors.Is to check if the final wrapped error matches each sentinel error and display the results:
    • "Checking for connection timeout: [true/false]"
    • "Checking for connection refused: [true/false]"
    • "Checking for database not found: [true/false]"
  8. Display the error chain:
    • "Error Chain:"
    • "Original: [original_error_message]"
    • "Database level: [database_level_error_message]"
    • "Service level: [service_level_error_message]"
  9. Display the error analysis:
    • "Error Analysis:"
    • "Database: [database_name]"
    • "Operation: [operation]"
    • "Service: [service_name]"
    • "Retries: [retry_count]"
    • "Error type detected: [detected_error_type]" where detected_error_type is one of "Connection Timeout", "Connection Refused", "Database Not Found", or "Unknown Error"

Use the strings package to split the input strings on commas, the errors package for sentinel errors and errors.Is, and the fmt package for error wrapping. This challenge demonstrates how errors.Is provides robust error identification that works correctly with wrapped errors, making it essential for reliable error handling in complex applications.

Cheat sheet

The errors.Is function checks if a specific error exists anywhere in an error chain, even when wrapped multiple times:

var ErrNotFound = errors.New("not found")

wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)

if errors.Is(wrappedErr, ErrNotFound) {
    fmt.Println("This is a not found error")
}

errors.Is takes two parameters: the error to examine and the target error to find. It returns true if the target error is found anywhere in the error chain.

This is the modern, recommended way to check for specific errors in Go, as it works correctly with wrapped errors unlike string comparison or direct equality checks.

Try it yourself

package main

import (
	"errors"
	"fmt"
	"strings"
)

func main() {
	// Read input
	var dbOperation string
	var appContext string
	fmt.Scanln(&dbOperation)
	fmt.Scanln(&appContext)
	
	// Parse inputs
	dbParts := strings.Split(dbOperation, ",")
	databaseName := dbParts[0]
	operation := dbParts[1]
	errorType := dbParts[2]
	
	contextParts := strings.Split(appContext, ",")
	serviceName := contextParts[0]
	retryCount := contextParts[1]
	
	// TODO: Write your code below
	// 1. Define the three sentinel errors using errors.New()
	// 2. Create the original error based on the error type
	// 3. Create database-level wrapped error using fmt.Errorf with %w
	// 4. Create service-level wrapped error
	// 5. Use errors.Is to check for each sentinel error
	// 6. Display the results and error chain
	
}
quiz iconTest yourself

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

All lessons in Logic & Flow