Skip to content

响应错误#

返回 HTTP 错误#

处理函数可以返回错误而不是成功的响应。有许多实用函数可以返回常见的 HTTP 错误:

code.go
huma.Register(api, huma.Operation{
		OperationID: "get-thing",
		Method:      http.MethodGet,
		Path:        "/things/{thing-id}",
		Summary:     "Get a thing by ID",
}, func(ctx context.Context, input ThingRequest) (*struct{}, error) {
		// Return a 404 Not Found error
		return nil, huma.Error404NotFound("thing not found")
}

这些错误函数的命名方式类似于 Error{code}{name},并接受一条消息和错误细节,这些细节可以向用户提供更多信息。例如,huma.Error400BadRequest(msg string, errs ...error)。像 VSCode 这样的编辑器应该会在您输入时自动显示可用的错误:

VSCode 错误

默认错误响应

如果返回的错误没有关联的 HTTP 状态码,例如您使用 fmt.Errorf("my error"),则默认错误响应码为 500 Internal Server Error。使用 huma.NewError 来返回带有自定义状态码的错误。

错误模型#

错误使用 RFC 9457 Problem Details for HTTP APIs,内容类型类似于 application/problem+json,并返回一个结构,如下所示:

HTTP 响应
HTTP/2.0 422 Unprocessable Entity
Cache-Control: private
Content-Length: 241
Content-Type: application/problem+json
Link: </schemas/ErrorModel.json>; rel="describedBy"

{
  "$schema": "https://api.rest.sh/schemas/ErrorModel.json",
  "status": 422,
  "title": "Unprocessable Entity",
  "detail": "validation failed",
  "errors": [
    {
      "location": "body.title",
      "message": "expected string",
      "value": true
    },
    {
      "location": "body.reviews",
      "message": "unexpected property",
      "value": {
        "reviews": 5,
        "title": true
      }
    }
  ]
}

errors 字段是可选的,可能包含有关特定发生错误的更多细节。有关更多细节,请参阅 huma.ErrorModel

要在 errors 数组中显示 locationmessagevalue,请使用 huma.ErrorDetail 结构体。如果您需要出于任何原因用自定义逻辑包装它,可以实现 huma.ErrorDetailer 接口。

详尽错误#

推荐尽可能返回详尽错误,以防止用户因反复重试无效请求而感到沮丧,每次都得到不同的错误。

输入参数验证、主体验证、解析器等都支持返回详尽错误。因此,更倾向于在操作处理程序中使用它们,而不是自定义错误逻辑。

错误状态码#

虽然 Huma 会尽一切努力返回详尽错误,但每个单独响应只能包含一个 HTTP 状态码。下面的图表描述了返回哪些代码以及何时返回:

flowchart TD
	Request[Request has errors?] -->|yes| Panic
	Request -->|no| Continue[Continue to handler]
	Panic[Panic?] -->|yes| 500
	Panic -->|no| RequestBody[Request body too large?]
	RequestBody -->|yes| 413
	RequestBody -->|no| RequestTimeout[Request took too long to read?]
	RequestTimeout -->|yes| 408
	RequestTimeout -->|no| ParseFailure[Cannot parse input?]
	ParseFailure -->|yes| 400
	ParseFailure -->|no| ValidationFailure[Validation failed?]
	ValidationFailure -->|yes| 422
	ValidationFailure -->|no| 400

这意味着例如,可能收到一个 HTTP 408 Request Timeout 响应,同时其中还包含一个输入标头验证错误的错误细节。由于请求超时优先级更高,因此将返回该响应状态码。

错误标头#

中间件可用于向所有响应添加标头,例如用于缓存控制、速率限制等。对于特定于错误或特定处理程序错误响应的标头,您可以根据需要用附加标头包装错误:

code.go
return nil, huma.ErrorWithHeaders(
	huma.Error404NotFound("thing not found"),
	http.Header{
		"Cache-Control": {"no-store"},
	},
)

多次调用 huma.ErrorWithHeaders 是安全的,所有传递的标头将附加到任何现有标头。

任何满足 huma.HeadersError 接口的错误都会将标头添加到响应中。

自定义错误#

可以提供您自己的错误模型,并让内置的错误实用函数使用该模型而不是默认模型。这在您希望在错误响应中提供更多信息,或者您的组织对错误响应结构有要求时很有用。

这是通过将您的自定义模型定义为 huma.StatusError,然后覆盖内置的 huma.NewError 函数来实现的:

code.go
type MyError struct {
	status  int
	Message string   `json:"message"`
	Details []string `json:"details,omitempty"`
}

func (e *MyError) Error() string {
	return e.Message
}

func (e *MyError) GetStatus() int {
	return e.status
}

func main() {
	huma.NewError = func(status int, message string, errs ...error) huma.StatusError {
		details := make([]string, len(errs))
		for i, err := range errs {
			details[i] = err.Error()
		}
		return &MyError{
			status:  status,
			Message: message,
			Details: details,
		}
	}

	router := chi.NewMux()
	api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

	huma.Register(api, huma.Operation{
		OperationID: "get-error",
		Method:      http.MethodGet,
		Path:        "/error",
	}, func(ctx context.Context, i *struct{}) (*struct{}, error) {
		return nil, huma.Error404NotFound("not found", fmt.Errorf("some-other-error"))
	})

	http.ListenAndServe(":8888", router)
}

要更改返回的默认内容类型,您还可以实现 huma.ContentTypeFilter 接口。

深入了解#