错误模型
每个 Plumego handler 都用同两个函数返回错误:contract.WriteError 和 contract.NewErrorBuilder。这条唯一的写入路径保证所有错误响应具有一致的 JSON 格式,客户端可以可靠地解析。
响应报文格式
Section titled “响应报文格式”{ "error": { "type": "required_field_missing", "code": "REQUIRED", "message": "name is required", "category": "validation_error", "details": { "field": "name" } }, "request_id": "0jx9f3kp2q"}request_id 在 middleware/requestid 运行后自动注入,无需手动设置。
使用 NewErrorBuilder,绝不要直接构造 APIError 字面量:
import "github.com/spcent/plumego/contract"
err := contract.NewErrorBuilder(). Type(contract.TypeRequired). // 自动设置 type、code、category、HTTP 状态 Detail("field", "name"). // 可选:向 details 映射添加字段 Message("name is required"). // 覆盖默认的人类可读文本 Build()
contract.WriteError(w, r, err).Type() 是唯一必须调用的步骤,它自动填充 type、code、category 和 HTTP 状态码。只有在默认文本不够时才需要覆盖 Message 和 Detail。
ErrorType 目录
Section titled “ErrorType 目录”| 常量 | HTTP | code | category |
|---|---|---|---|
TypeValidation |
400 | VALIDATION_ERROR |
validation_error |
TypeRequired |
400 | REQUIRED_FIELD_MISSING |
validation_error |
TypeInvalidFormat |
400 | INVALID_FORMAT |
validation_error |
TypeOutOfRange |
400 | VALUE_OUT_OF_RANGE |
validation_error |
TypeDuplicate |
400 | DUPLICATE_VALUE |
validation_error |
TypeUnauthorized |
401 | UNAUTHORIZED |
auth_error |
TypeInvalidToken |
401 | INVALID_TOKEN |
auth_error |
TypeExpiredToken |
401 | EXPIRED_TOKEN |
auth_error |
TypeForbidden |
403 | FORBIDDEN |
auth_error |
TypeNotFound |
404 | RESOURCE_NOT_FOUND |
client_error |
TypeConflict |
409 | RESOURCE_CONFLICT |
client_error |
TypeAlreadyExists |
409 | RESOURCE_ALREADY_EXISTS |
client_error |
TypeGone |
410 | RESOURCE_GONE |
client_error |
TypeRateLimited |
429 | RATE_LIMITED |
rate_limit_error |
TypeInternal |
500 | INTERNAL_ERROR |
server_error |
TypeUnavailable |
503 | SERVICE_UNAVAILABLE |
server_error |
TypeTimeout |
408 | TIMEOUT |
timeout_error |
TypeMethodNotAllowed |
405 | METHOD_NOT_ALLOWED |
client_error |
TypeNotImplemented |
501 | NOT_IMPLEMENTED |
server_error |
TypeBadGateway |
502 | BAD_GATEWAY |
server_error |
TypeGatewayTimeout |
504 | GATEWAY_TIMEOUT |
timeout_error |
TypeMaintenance |
503 | MAINTENANCE_MODE |
server_error |
ErrorCategory
Section titled “ErrorCategory”category 字段将错误分组,便于可观测性和告警:
| 值 | 含义 |
|---|---|
client_error |
4xx — 客户端输入有误 |
server_error |
5xx — 基础设施或服务器逻辑故障 |
validation_error |
输入验证失败(client_error 的子集) |
auth_error |
认证或授权失败 |
rate_limit_error |
触发限流 |
timeout_error |
超时 |
Handler 模式
Section titled “Handler 模式”func (h ItemHandler) Create(w http.ResponseWriter, r *http.Request) { var req CreateItemRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeValidation). Message(err.Error()). Build()) return } if req.Name == "" { contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeRequired). Detail("field", "name"). Build()) return }
item, err := h.svc.Create(r.Context(), req) if errors.Is(err, ErrAlreadyExists) { contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeAlreadyExists). Message("item with this name already exists"). Build()) return } if err != nil { contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeInternal). Build()) return }
contract.WriteResponse(w, r, http.StatusCreated, item, nil)}一次返回多个验证错误
Section titled “一次返回多个验证错误”需要同时返回多个字段错误时,使用 Details 映射携带:
errs := map[string]string{}if req.Name == "" { errs["name"] = "required" }if req.Email == "" { errs["email"] = "required" }
if len(errs) > 0 { b := contract.NewErrorBuilder(). Type(contract.TypeValidation). Message("request validation failed") for field, msg := range errs { b = b.Detail(field, msg) } contract.WriteError(w, r, b.Build()) return}