Menu
Coddy logo textTech

errors.Is() и errors.As()

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 56 из 107.

Когда ошибки оборачиваются, простые проверки на равенство, такие как err == ErrNotFound, больше не работают. Пакет errors предоставляет две функции для проверки цепочек обернутых ошибок: errors.Is() и errors.As().

errors.Is() проверяет, соответствует ли какая-либо ошибка в цепочке определенной ошибке-индикатору (sentinel error):

import "errors"

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

func FindUser(id int) error {
    return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}

err := FindUser(42)
if errors.Is(err, ErrNotFound) {
    fmt.Println("User doesn't exist")
}

Несмотря на то, что возвращаемая ошибка обернута, errors.Is() разворачивает цепочку и находит ErrNotFound внутри.

errors.As() проверяет, соответствует ли какая-либо ошибка в цепочке определенному типу, и извлекает её:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("Invalid field:", ve.Field)
}

Используйте errors.Is() при проверке на наличие сигнальных ошибок (sentinel errors) и errors.As(), когда вам нужно получить доступ к полям пользовательского типа ошибки. Обе функции обходят всю цепочку ошибок, что делает их незаменимыми для правильной обработки обернутых ошибок.

challenge icon

Задание

Легко

Давайте создадим систему обработки файлов, которая демонстрирует, как правильно проверять цепочки обернутых ошибок с помощью errors.Is() и errors.As(). Вы создадите ошибки, которые будут оборачиваться при прохождении через различные уровни, а затем будете использовать эти функции для идентификации и извлечения информации из них.

Вы организуете свой код в двух файлах:

  • errors.go: Определите инфраструктуру ошибок для вашей файловой системы.

    Создайте сигнальную ошибку (sentinel error):

    • ErrPermissionDenied с сообщением "permission denied"

    Создайте пользовательский тип ошибки:

    • Структура FileError с полями Filename (string) и Operation (string)
    • Ее метод Error() должен возвращать: [Operation] failed on file: [Filename]

    Реализуйте две функции, которые имитируют операции с файлами и оборачивают ошибки:

    • ReadFile(filename string) error — если filename равен "secret.txt", верните ErrPermissionDenied. Если filename равен "missing.txt", верните *FileError с Operation "read" и данным Filename. В противном случае верните nil.
    • ProcessFile(filename string) error — вызывает ReadFile. Если возвращается ошибка, оберните ее с помощью fmt.Errorf с форматом "processing failed: %w". В противном случае верните nil.
  • main.go: Считайте имя файла из входных данных и вызовите ProcessFile. Используйте errors.Is() и errors.As() для проверки цепочки обернутых ошибок и вывода соответствующих сообщений в зависимости от того, что вы найдете внутри.

Будут предоставлены следующие входные данные:

  • Строка 1: Имя файла для обработки

Обработайте результат, проверяя цепочку ошибок:

  • Если errors.Is() находит ErrPermissionDenied в цепочке: выведите полное сообщение об ошибке, затем на новой строке выведите Access denied - check file permissions
  • Если errors.As() находит *FileError в цепочке: выведите полное сообщение об ошибке, затем на новой строке выведите File issue: [Filename] during [Operation]
  • Если ошибка не возникла: выведите File '[filename]' processed successfully

Например, для secret.txt ваш вывод должен быть таким:

processing failed: permission denied
Access denied - check file permissions

А для missing.txt ваш вывод должен быть таким:

processing failed: read failed on file: missing.txt
File issue: missing.txt during read

А для data.txt ваш вывод должен быть таким:

File 'data.txt' processed successfully

Обратите внимание, что даже если ошибки обернуты функцией ProcessFile, вы все равно можете обнаружить исходную сигнальную ошибку с помощью errors.Is() и извлечь пользовательский тип ошибки с помощью errors.As(). Эти функции проходят по всей цепочке ошибок, чтобы найти то, что вы ищете.

Шпаргалка

Когда ошибки обернуты, используйте errors.Is() и errors.As() из пакета errors для проверки цепочки ошибок.

errors.Is() проверяет, совпадает ли какая-либо ошибка в цепочке с эталонной ошибкой (sentinel error):

import "errors"

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

err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
    // ErrNotFound был найден в цепочке
}

errors.As() проверяет, соответствует ли какая-либо ошибка в цепочке определенному типу, и извлекает ее:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    // ve теперь содержит ValidationError из цепочки
    fmt.Println(ve.Field)
}

Используйте errors.Is() для эталонных ошибок и errors.As(), когда вам нужно получить доступ к полям пользовательских типов ошибок. Обе функции проходят по всей цепочке ошибок.

Попробуйте сами

package main

import (
	"errors"
	"fmt"
)

func main() {
	// Считать имя файла из ввода
	var filename string
	fmt.Scanln(&filename)

	// Вызвать ProcessFile с именем файла
	err := ProcessFile(filename)

	// TODO: Обработать результат, проверив цепочку ошибок
	// 
	// Используйте errors.Is(), чтобы проверить, есть ли ErrPermissionDenied в цепочке:
	// - Если найдено: вывести полную ошибку, затем "Access denied - check file permissions"
	//
	// Используйте errors.As(), чтобы проверить, есть ли *FileError в цепочке:
	// - Если найдено: вывести полную ошибку, затем "File issue: [Filename] during [Operation]"
	//
	// Если ошибки нет: вывести "File '[filename]' processed successfully"

	_ = errors.Is // подсказка: используйте errors.Is()
	_ = errors.As // подсказка: используйте errors.As()
	_ = err
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование