Menu
CoddyTech

使用 `errors.As` 解包错误

本课属于 Coddy GO 学习旅程的“逻辑与流程”单元。第 41 课,共 68 课。

虽然 errors.Is 可以帮助你在错误链中识别特定的错误值,但有时你需要访问自定义错误类型的实际字段和数据。errors.As 函数可以实现这一点:它会在错误链中查找第一个与特定类型匹配的错误,并将其提取出来供你检查。

errors.As 函数接受两个参数:要检查的错误,以及指向目标错误类型变量的指针。如果它在错误链中找到匹配的错误类型,就会将该错误赋给你的变量,并返回 true:

var validationErr ValidationError
if errors.As(wrappedErr, &validationErr) {
    fmt.Printf("Field: %s, Message: %s\n", validationErr.Field, validationErr.Message)
}

在处理包含额外上下文的自定义错误类型时,这一点尤其强大。与只检查是否相等的 errors.Is 不同,errors.As 会提取实际的错误实例,让你可以访问它的字段和方法。这样一来,在复杂的应用程序中,错误处理会更加完善,也能提供更多信息。

challenge icon

挑战

简单

构建一个 API 错误处理系统,使用 errors.As 从包装后的错误链中提取并检查自定义错误类型。本挑战将演示如何使用 errors.As 访问特定自定义错误类型的字段和数据,即使这些错误类型被额外的上下文包装也可以做到。

你会收到两个输入:

  • 一个字符串,其中包含 API 请求详情,格式为 "endpoint,method,error_code,error_field"(例如:"/users,POST,400,email")
  • 一个字符串,其中包含系统上下文,格式为 "service_name,request_id"(例如:"UserAPI,req_12345")

你的任务是:

  1. 创建一个名为 APIError 的自定义错误类型结构体,包含三个字段:
    • Code(int),HTTP 状态码
    • Field(string),导致错误的字段
    • Message(string),错误描述
  2. 为 APIError 实现 Error() string 方法,使其返回:"API error [code]: [message] (field: [field])"
  3. 通过按逗号拆分第一个输入,获取端点、方法、错误码和错误字段
  4. 通过按逗号拆分第二个输入,获取服务名称和请求 ID
  5. 将错误码字符串转换为整数
  6. 根据错误码创建一个 APIError 实例:
    • 如果错误码为 400:消息为 "invalid request data"
    • 如果错误码为 401:消息为 "authentication required"
    • 如果错误码为 404:消息为 "resource not found"
    • 如果错误码为 500:消息为 "internal server error"
    • 对于其他错误码:消息为 "unknown error"
  7. 使用 fmt.Errorf 和 %w 创建端点级包装错误:"[method] [endpoint] failed: %w"
  8. 创建服务级包装错误:"[service_name] request [request_id] failed: %w"
  9. 使用 errors.As 从包装后的错误链中提取 APIError,并显示提取结果:
    • 如果提取成功:"API error extracted successfully"
    • 如果提取失败:"Failed to extract API error"
  10. 如果提取成功,显示提取到的错误详情:
    • "Extracted Error Details:"
    • "Code: [code]"
    • "Field: [field]"
    • "Message: [message]"
    • "Full error: [full_error_message_from_Error_method]"
  11. 显示完整的错误链:
    • "Error Chain:"
    • "Original: [original_api_error_message]"
    • "Endpoint level: [endpoint_level_error_message]"
    • "Service level: [service_level_error_message]"
  12. 显示最终分析结果:
    • "Error Analysis:"
    • "Service: [service_name]"
    • "Request ID: [request_id]"
    • "Endpoint: [method] [endpoint]"
    • "HTTP Status: [code]"
    • "Problem Field: [field]"
    • "Error Category: [error_category]",其中 error_category 对于 400-499 的错误码为 "Client Error",对于 500-599 的错误码为 "Server Error",对于其他错误码为 "Unknown"

使用 strings 包按逗号拆分输入字符串,使用 strconv 包将错误码字符串转换为整数,使用 errors 包中的 errors.As,并使用 fmt 包包装错误。本挑战将演示 errors.As 如何让你访问包装后的错误链中自定义错误类型的特定字段和方法,从而实现复杂的错误处理和分析。

亲手试试

package main

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

func main() {
	// 读取输入
	var apiDetails string
	var systemContext string
	fmt.Scanln(&apiDetails)
	fmt.Scanln(&systemContext)
	
	// 解析输入
	apiParts := strings.Split(apiDetails, ",")
	endpoint := apiParts[0]
	method := apiParts[1]
	errorCodeStr := apiParts[2]
	errorField := apiParts[3]
	
	contextParts := strings.Split(systemContext, ",")
	serviceName := contextParts[0]
	requestID := contextParts[1]
	
	// 将错误代码转换为整数
	errorCode, _ := strconv.Atoi(errorCodeStr)
	
	// TODO: 在下方编写你的代码
	// 1. 创建包含 Code、Field 和 Message 字段的 APIError 结构体
	// 2. 为 APIError 实现 Error() 方法
	// 3. 根据错误代码创建 APIError 实例
	// 4. 创建包装后的错误(端点级和服务级)
	// 5. 使用 errors.As 从包装后的错误链中提取 APIError
	// 6. 显示提取结果和错误详情
	// 7. 显示错误链和最终分析
	
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

逻辑与流程中的所有课

自己练习: Go 在线编译器