Skip to content

自定义验证#

内置验证器#

Huma 提供了许多基于 JSON Schema 的内置验证器。这些验证器支持大多数基本用例,优先于编写自己的代码进行相同的检查。

内置验证器包括 minimummaximummultipleOfminLengthmaxLengthpatternenumminItemsmaxItems 等。例如:

code.go
type MyInput struct {
	ThingID string `path:"thing-id" maxLength:"12"`
	Tag     string `query:"tag" enum:"foo,bar,baz"`
	Sales   uint   `query:"sales" maximum:"1000"`
}

有关所有可用验证器的信息,请参阅 请求验证。某些验证器会自动添加,例如上面的 uint 在生成 JSON Schema 时会自动使用 minimum:"0"

解析器#

有时,您需要进行比内置验证器更复杂的验证。例如,您可能想要验证字段值不是某些已知的不良值。在这种情况下,您可以使用解析器。解析器是附加到输入的方法,在验证期间调用,并可以返回错误。

code.go
type MyInput struct {
	ThingID string `path:"thing-id"`
}

func (i *MyInput) Resolve(ctx huma.Context) []error {
	if i.ThingID == "bad" {
		return []error{&huma.ErrorDetail{
			Location: "path.thing-id",
			Message:  "Thing ID cannot be 'bad'",
			Value:    i.ThingID,
		}}
	}
	return nil
}

var _ huma.Resolver = (*MyInput)(nil)

有关更多详细信息,请参阅 解析器

示例#

以下是使用解析器为参数和主体字段提供额外验证的示例,以及如何返回详尽的错误。

code.go
// 此示例展示了如何使用解析器为参数和主体字段提供额外验证,
// 以及如何返回详尽的错误。
//
//	# 返回七个错误的示例调用
//	restish put :8888/count/3?count=15 -H Count:-3 count:9, nested.subCount: 6
//
//	# 示例成功
//	restish put :8888/count/1 count:2, nested.subCount: 4
package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/danielgtaylor/huma/v2/humacli"
	"github.com/go-chi/chi/v5"
)

// CLI 的选项。
type Options struct {
	Port int `doc:"Port to listen on." short:"p" default:"8888"`
}

// 创建一个带有附加验证的新输入类型。
type IntNot3 int

// Resolve 由 Huma 调用以验证输入。Prefix 是当前路径,例如 `path.to[3].field`,
// 如 `query.count` 或 `body.nested.subCount`。
// 解析器也可以附加到结构体,以提供跨多个字段组合的验证,例如“如果设置了 foo,则 bar 必须是 foo 值
// 的倍数”。在这种情况下,使用 `prefix.With("bar")`。
func (i IntNot3) Resolve(ctx huma.Context, prefix *huma.PathBuffer) []error {
	if i != 0 && i%3 == 0 {
		return []error{&huma.ErrorDetail{
			Location: prefix.String(),
			Message:  "Value cannot be a multiple of three",
			Value:    i,
		}}
	}
	return nil
}

// 确保我们的解析器符合预期的接口。
var _ huma.ResolverWithPath = (*IntNot3)(nil)

func main() {
	// 创建 CLI,传递一个函数,在解析自定义选项后调用。
	cli := humacli.New(func(hooks humacli.Hooks, options *Options) {
		router := chi.NewMux()

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

		// 注册问候操作。
		huma.Register(api, huma.Operation{
			OperationID: "put-count",
			Summary:     "Put a count of things",
			Method:      http.MethodPut,
			Path:        "/count/{count}",
		}, func(ctx context.Context, input *struct {
			PathCount   IntNot3 `path:"count" example:"2" minimum:"1" maximum:"10"`
			QueryCount  IntNot3 `query:"count" example:"2" minimum:"1" maximum:"10"`
			HeaderCount IntNot3 `header:"Count" example:"2" minimum:"1" maximum:"10"`
			Body        struct {
				Count  IntNot3 `json:"count" example:"2" minimum:"1" maximum:"10"`
				Nested *struct {
					SubCount IntNot3 `json:"subCount" example:"2" minimum:"1" maximum:"10"`
				} `json:"nested,omitempty"`
			}
		}) (*struct{}, error) {
			fmt.Printf("Got input: %+v\n", input)
			return nil, nil
		})

		// 告诉 CLI 如何启动您的路由器。
		hooks.OnStart(func() {
			// 启动服务器
			http.ListenAndServe(fmt.Sprintf(":%d", options.Port), router)
		})
	})

	// 运行 CLI。当不传递命令时,它会启动服务器。
	cli.Run()
}