首次提交:初始化项目代码

This commit is contained in:
sunct
2026-07-31 15:18:32 +08:00
commit d6393266b0
193 changed files with 14287 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
package main
import (
"context"
"errors"
"fmt"
"strings"
"testing"
)
// ------------------------------
// 错误类型定义(核心优化)
// ------------------------------
// FileError 自定义文件服务错误结构体
type FileError struct {
Code int // 错误码
Module string // 所属模块
Message string // 错误信息
Err error // 原始错误(支持错误包装/解包)
}
// Error 实现 error 接口
func (e *FileError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%s:%d] %s: %v", e.Module, e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%s:%d] %s", e.Module, e.Code, e.Message)
}
// Unwrap 实现 errors.Unwrap 支持错误链解包
func (e *FileError) Unwrap() error {
return e.Err
}
// 预定义错误(使用 sentinel error 规范)
var ErrFileNotFound = errors.New("file not found")
// ------------------------------
// 业务服务实现
// ------------------------------
// FileService 基础文件服务
func FileService(ctx context.Context, filename string) error {
// 模块名称常量
const module = "FileService"
// 1. 空文件名校验(400
if filename == "" {
return &FileError{
Code: 400,
Module: module,
Message: "filename cannot be empty",
}
}
// 2. 文件不存在模拟(404
return &FileError{
Code: 404,
Module: module,
Message: "file does not exist",
Err: ErrFileNotFound, // 包装原始哨兵错误
}
}
// AdvancedFileService 高级文件服务(包装基础服务)
func AdvancedFileService(ctx context.Context, filename string) error {
// 调用底层服务并包装错误信息
if err := FileService(ctx, filename); err != nil {
return fmt.Errorf("advanced file service failed: %s %w", filename, err)
}
return nil
}
// ------------------------------go vet
// 测试用例
// ------------------------------
func TestFileService(t *testing.T) {
ctx := context.Background()
// 子测试用例(清晰分组,便于定位失败)
t.Run("file not found returns 404 error", func(t *testing.T) {
err := FileService(ctx, "test.txt")
// 断言哨兵错误
if !errors.Is(err, ErrFileNotFound) {
t.Fatalf("期望错误: %v, 实际错误: %v", ErrFileNotFound, err)
}
// 断言自定义错误类型与字段
var fErr *FileError
if !errors.As(err, &fErr) {
t.Fatal("期望返回 *FileError 类型")
}
if fErr.Code != 404 {
t.Errorf("错误码期望 404, 实际 %d", fErr.Code)
}
if fErr.Module != "FileService" {
t.Errorf("模块期望 FileService, 实际 %s", fErr.Module)
}
})
t.Run("empty filename returns 400 error", func(t *testing.T) {
err := FileService(ctx, "")
if err == nil {
t.Fatal("空文件名必须返回错误")
}
var fErr *FileError
if !errors.As(err, &fErr) {
t.Fatal("期望返回 *FileError 类型")
}
if fErr.Code != 400 {
t.Errorf("错误码期望 400, 实际 %d", fErr.Code)
}
})
}
func TestErrorWrapping(t *testing.T) {
original := errors.New("original error")
wrapped := fmt.Errorf("wrapped: %w", original)
// 测试 errors.Is 错误链匹配
if !errors.Is(wrapped, original) {
t.Error("errors.Is 应当匹配包装后的错误")
}
// 测试 errors.Unwrap 解包
unwrapped := errors.Unwrap(wrapped)
if unwrapped != original {
t.Errorf("解包后应当返回原始错误, 实际: %v", unwrapped)
}
}
func TestAdvancedFileService(t *testing.T) {
ctx := context.Background()
err := AdvancedFileService(ctx, "nonexistent.txt")
if err == nil {
t.Fatal("必须返回错误")
}
// 错误信息包含文件名
if !strings.Contains(err.Error(), "nonexistent.txt") {
t.Errorf("错误信息应当包含文件名, 实际: %v", err)
}
// 错误链中能找到自定义 FileError
var fErr *FileError
if !errors.As(err, &fErr) {
t.Error("错误链中应当包含 *FileError")
}
}