Skip to content

请求解析器#

有时,内置验证不足以满足您的用例,或者您想对传入的请求对象进行更复杂的操作。这时,解析器就派上用场了。

任何输入结构体都可以通过实现 huma.Resolverhuma.ResolverWithPath 接口来成为解析器,包括嵌入式结构体。每个解析器接收当前的上下文,并可以返回一个详尽的错误列表。例如:

code.go
// MyInput demonstrates inputs/transformation
type MyInput struct {
	Host   string
	Name string `query:"name"`
}

func (m *MyInput) Resolve(ctx huma.Context) []error {
	// Get request info you don't normally have access to.
	m.Host = ctx.Host()

	// Transformations or other data validation
	m.Name = strings.Title(m.Name)

	return nil
}

// Then use it like any other input struct:
huma.Register(api, huma.Operation{
	OperationID: "list-things",
	Method:      http.MethodGet,
	Path:        "/things",
	Summary:     "Get a filtered list of things",
}, func(ctx context.Context, input *MyInput) (*YourOutput, error) {
	fmt.Printf("Host: %s\n", input.Host)
	fmt.Printf("Name: %s\n", input.Name)
})

建议不要保存传递给 Resolve 方法的上下文对象以供后续使用。

对于请求体中深度嵌套的结构体,您可能不知道正在验证的字段的当前位置(例如,它可能出现在多个位置或被多个请求对象共享)。huma.ResolverWithPath 接口提供了一个路径前缀,可以用于生成被验证字段的完整路径。它使用 huma.PathBuffer 来高效生成路径,重用共享缓冲区。例如:

code.go
func (m *MyInput) Resolve(ctx huma.Context, prefix *huma.PathBuffer) []error {
	return []error{&huma.ErrorDetail{
		Message: "Foo has a bad value",
		Location: prefix.With("foo"),
		Value: m.Foo,
	}}
}

验证偏好

尽可能优先使用内置验证而不是解析器,因为它会有更好的文档说明,并且 OpenAPI 工具也可以使用它来提供更好的开发者体验。

解析器错误#

解析器可以根据需要设置错误,Huma 会在调用您的处理程序之前自动返回 400 级别的错误响应。这使得解析器成为运行额外复杂验证步骤的理想位置,从而向用户提供一组详尽的错误。

code.go
type MyInput struct {
	Host   string
}

func (m *MyInput) Resolve(ctx huma.Context) []error {
	m.Host = ctx.Host()
	if m.Host == "localhost" {
		return []error{&huma.ErrorDetail{
			Message: "Unsupported host value!",
			Location: "request.host",
			Value: m.Host,
		}}
	}
	return nil
}

解析器也可以通过返回满足 huma.StatusError 接口的错误来为响应返回自定义 HTTP 状态码。错误按返回顺序处理,最后一个错误获胜,因此此功能应谨慎使用。例如:

code.go
type MyInput struct{}

func (i *MyInput) Resolve(ctx huma.Context) []error {
	return []error{huma.Error403Forbidden("nope")}
}

为什么使用详尽错误?

详尽错误可以减少用户的挫败感。与让用户发出三个请求每个返回一个新的不同错误相比,在响应一个请求时返回三个错误更好。

解包#

虽然通常不推荐,但如果您需要访问底层路由器特定的请求和响应对象,您可以使用创建 API 实例时所用的路由器特定适配器包来 Unwrap() 它们。有关更多信息,请参阅 中间件文档

实现检查#

Go 有一个技巧可以确保结构体实现了某个接口,您可以利用它来确保您的解析器会如预期般被调用。例如:

code.go
// Ensure MyInput implements huma.Resolver
var _ huma.Resolver = (*MyInput)(nil)

这会创建一个指向您的结构体的新的 nil 指针,并将其分配给类型为 huma.Resolver 的未命名变量。它将被编译,然后在优化期间被丢弃。如果您的解析器代码发生变化不再实现接口,代码将无法编译。

深入了解#