Skip to content

请求验证#

Go 结构体标签用于为输入/输出结构体添加注释,这些注释会转换为 JSON Schema 以用于文档和验证。例如:

code.go
type Person struct {
    Name string `json:"name" doc:"Person's name" minLength:"1" maxLength:"80"`
    Age  uint   `json:"age,omitempty" doc:"Person's age" maximum:"120"`
}

字段命名#

支持标准的 json 标签,可以用于重命名字段。任何标记为 json:"-" 的字段将在 schema 中被忽略,就好像它不存在一样。

可选 / 必需#

字段是否可选/必需会自动确定,但可以使用以下逻辑根据需要覆盖:

  1. 初始假设所有字段均为必需。
  2. 如果字段有 omitempty,则为可选。
  3. 如果字段有 omitzero,则为可选。
  4. 如果字段有 required:"false",则为可选。
  5. 如果字段有 required:"true",则为必需。

指针不会影响可选/必需。无论结构体是用于请求输入还是响应输出,都适用相同的规则。一些示例:

type MyStruct struct {
    // The following are all required.
    Required1 string  `json:"required1"`
    Required2 *string `json:"required2"`
    Required3 string  `json:"required3,omitempty" required:"true"`

    // The following are all optional.
    Optional1 string  `json:"optional1,omitempty"`
    Optional2 string  `json:"optional2,omitzero"`
    Optional3 *string `json:"optional3,omitempty"`
    Optional4 *string `json:"optional4,omitempty,omitzero"`
    Optional5 string  `json:"optional5" required:"false"`
}

注意

为什么在输入中使用 omitempty,而 Go 本身仅用于 marshalling?想象一个即将向您的 API 发送请求的客户端——它仍然必须被 marshalling 成 JSON(或类似格式)。您可以将输入结构体视为建模 API 客户端会产生的输出。

可空#

在许多语言(包括 Go)中,显式空值与未定义值之间几乎没有区别。如上所述,将字段标记为可选就足以支持两种情况。JavaScript 和 TypeScript 是此规则的例外,因为它们有明确的 nullundefined 值。

Huma 试图在 schema 简洁性、可用性和广泛兼容性之间取得平衡,同时兼顾 schema 正确性和广泛的语言支持,以实现端到端 API 工具链。为此,它在有限范围内支持字段可空性,未来变化可能会修改此默认行为,因为工具变得更兼容高级 JSON Schema 功能。

字段是否可空会自动确定,但可以使用以下逻辑根据需要覆盖:

  1. 初始假设没有字段可空
  2. 如果字段是指针(包括切片):
    1. 指向 booleanintegernumberstring:除非有 omitempty,否则可空
    2. 指向 array:如果 huma.DefaultArrayNullable 为 true,则可空
    3. 指向 object不可空,由于复杂性和许多工具对 anyOf/oneOf 的支持不佳
  3. 如果字段有 nullable:"false",则不可空
  4. 如果字段有 nullable:"true"
    1. 指向 booleanintegernumberstringarray:可空
    2. 指向 objectpanic,表示当前不支持
  5. 如果结构体有一个字段 _nullable: true,则结构体可空,从而允许用户选择加入 object 而无需 anyOf/oneOf 的复杂性。

以下是一些示例:

code.go
// Make an entire struct (not its fields) nullable.
type MyStruct1 struct {
    _ struct{} `nullable:"true"`
    Field1 string `json:"field1"`
    Field2 string `json:"field2"`
}

// Make a specific scalar field nullable. This is *not* supported for
// maps or structs. Structs *must* use the method above.
type MyStruct2 struct {
    Field1 *string `json:"field1"`
    Field2 string `json:"field2" nullable:"true"`
}

可空类型将生成类似 "type": ["string", "null"] 的类型数组,这具有广泛的兼容性,并且易于降级到 OpenAPI 3.0。还要记住,您始终可以提供 自定义 schema,如果内置功能不是您需要的。

注意

Go 中的切片如果本身为 nil 而非分配但为空,则 marshalling 成 JSON 为 null。这就是为什么切片默认可空的原因。有关更多信息,请参阅 Go JSON 包文档

验证标签#

模型字段支持以下额外标签:

标签 描述 示例
doc 描述字段 doc:"Who to greet"
format 字段的格式提示 format:"date-time"
enum 逗号分隔的可能值列表 enum:"one,two,three"
default 默认值 default:"123"
minimum 最小值(包含) minimum:"1"
exclusiveMinimum 最小值(不包含) exclusiveMinimum:"0"
maximum 最大值(包含) maximum:"255"
exclusiveMaximum 最大值(不包含) exclusiveMaximum:"100"
multipleOf 值必须是此值的倍数 multipleOf:"2"
minLength 最小字符串长度 minLength:"1"
maxLength 最大字符串长度 maxLength:"80"
pattern 正则表达式模式 pattern:"[a-z]+"
patternDescription 用于错误的模式描述 patternDescription:"alphanum"
minItems 数组项的最小数量 minItems:"1"
maxItems 数组项的最大数量 maxItems:"20"
uniqueItems 数组项必须唯一 uniqueItems:"true"
minProperties 对象属性的最小数量 minProperties:"1"
maxProperties 对象属性的最大数量 maxProperties:"20"
example 示例值 example:"123"
readOnly 仅在响应中发送 readOnly:"true"
writeOnly 仅在请求中发送 writeOnly:"true"
deprecated 此字段已弃用 deprecated:"true"
hidden 从文档中隐藏字段/参数 hidden:"true"
dependentRequired 当字段存在时必需的字段 dependentRequired:"one,two"

内置字符串格式包括:

格式 描述 示例
date-time RFC3339 格式的日期和时间 2021-12-31T23:59:59Z
date-time-http HTTP 格式的日期和时间 Fri, 31 Dec 2021 23:59:59 GMT
date RFC3339 格式的日期 2021-12-31
time RFC3339 格式的时间 23:59:59
email / idn-email 电子邮件地址 kari@example.com
hostname 主机名 example.com
ipv4 IPv4 地址 127.0.0.1
ipv6 IPv6 地址 ::1
uri / iri URI https://example.com
uri-reference / iri-reference URI 引用 /path/to/resource
uri-template URI 模板 /path/{id}
json-pointer JSON 指针 /path/to/field
relative-json-pointer 相对 JSON 指针 0/1
regex 正则表达式 [a-z]+
uuid UUID 550e8400-e29b-41d4-a716-446655440000

默认值#

上面列出的 default 字段验证标签用于同时记录服务器端默认值的存在,以及自动让 Huma 为您设置该值。这对于可选但未提供时有默认值的字段很有用。

类似于标准库 JSON unmarshaler 的工作方式,推荐对于零值对您的应用程序有语义含义的标量类型使用指针。例如,如果您有一个默认为 truebool 字段,则应使用 *bool 字段并将其默认值设置为 true。这样,如果字段未提供,将使用默认值。

code.go
type MyInput struct {
	Body struct {
		Enabled *bool `json:"enabled" default:"true"`
	}
}

如果您使用 bool 而非 *bool,则零值 false 会被默认值 true 覆盖,即使客户端明确发送了 false。

仅读和仅写#

请注意,readOnlywriteOnly 验证不由 Huma 强制执行,这些字段中的值也不会被 Huma 修改。它们纯粹用于文档目的,并允许您将结构体重用于输入和输出。

您需要注意确保仅读字段不被修改。由您决定是否忽略字段值、将其与例如数据存储中的现有值比较,或采取其他操作。这是为了启用更容易的数据往返的设计选择,例如读取带有仅读创建日期的 GET 响应,修改不同字段,然后通过 PUT 发送回服务器。服务器应忽略创建日期的存在和值,否则客户端必须进行潜在的多次修改,然后才能将数据发送回服务器。

仅写字段,如果存储在数据存储中,可以与 omitempty 结合使用,然后在处理程序中设置为空值,或通过数据存储查询或投影过滤掉。它们也可以完全不存储在数据存储中,但用于计算将要存储的字段值。

注意

如果仅写字段需要在请求中为必需,但同一结构体在响应中重用,您可以使用 json:"name,omitempty"required:"true"

严格 vs. 宽松字段验证#

默认情况下,Huma 对对象中允许的字段很严格,使用 additionalProperties: false JSON Schema 设置。这意味着如果客户端发送 schema 中未定义的字段,请求将被拒绝并报错。这有助于防止拼写错误和其他问题,并推荐用于大多数 API。

如果您需要允许额外字段,例如在使用第三方服务调用您的系统时,您只关心少数字段,则可以在结构体上使用 additionalProperties:"true" 字段标签,并将其分配给虚拟的 _ 字段。

code.go
type PartialInput struct {
	_      struct{} `json:"-" additionalProperties:"true"`
	Field1 string   `json:"field1"`
	Field2 bool     `json:"field2"`
}

注意

使用 struct{} 是可选的,但高效。它用于避免为虚拟字段分配内存,因为空对象不需要空间。

高级验证#

在使用自定义 JSON Schema 时,即非从 Go 结构体生成的,可以利用更多验证规则。内置验证器会尊重以下 schema 字段:

  • not 用于否定
  • oneOf 用于互斥输入
  • anyOf 用于匹配一个或多个
  • allOf 用于 schema 联合

有关更多信息,请参阅 huma.Schema。请注意,对于某些规则,使用自定义 解析器 可能更容易实现。

深入了解#