这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions docs/docs/features/request-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ Huma tries to balance schema simplicity, usability, and broad compatibility with
Fields being nullable is determined automatically but can be overridden as needed using the logic below:

1. Start with no fields as nullable
2. If a field is a pointer:
2. If a field is a pointer (including slices):
1. To a `boolean`, `integer`, `number`, `string`: it is nullable unless it has `omitempty`.
2. To an `array`, `object`: it is **not** nullable, due to complexity and bad support for `anyOf`/`oneOf` in many tools.
2. To an `array`: it is nullable if `huma.DefaultArrayNullable` is true.
3. To an `object`: it is **not** nullable, due to complexity and bad support for `anyOf`/`oneOf` in many tools.
3. If a field has `nullable:"false"`, it is not nullable
4. If a field has `nullable:"true"`:
1. To a `boolean`, `integer`, `number`, `string`: it is nullable
2. To an `array`, `object`: **panic** saying this is not currently supported
1. To a `boolean`, `integer`, `number`, `string`, `array`: it is nullable
2. To an `object`: **panic** saying this is not currently supported
5. If a struct has a field `_` with `nullable: true`, the struct is nullable enabling users to opt-in for `object` without the `anyOf`/`oneOf` complication.

Here are some examples:
Expand All @@ -77,7 +78,7 @@ type MyStruct1 struct {
}

// Make a specific scalar field nullable. This is *not* supported for
// slices, maps, or structs. Structs *must* use the method above.
// maps or structs. Structs *must* use the method above.
type MyStruct2 struct {
Field1 *string `json:"field1"`
Field2 string `json:"field2" nullable:"true"`
Expand All @@ -86,6 +87,10 @@ type MyStruct2 struct {

Nullable types will generate a type array like `"type": ["string", "null"]` which has broad compatibility and is easy to downgrade to OpenAPI 3.0. Also keep in mind you can always provide a [custom schema](./schema-customization.md) if the built-in features aren't exactly what you need.

!!! info "Note"

Slices in Go marshal into JSON as `null` if the slice itself is `nil` rather than allocated but empty. This is why slices are nullable by default. See the [Go JSON package documentation](https://pkg.go.dev/encoding/json#Marshal) for more information.

## Validation Tags

The following additional tags are supported on model fields:
Expand Down
8 changes: 7 additions & 1 deletion schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import (
// ErrSchemaInvalid is sent when there is a problem building the schema.
var ErrSchemaInvalid = errors.New("schema is invalid")

// DefaultArrayNullable controls whether arrays are nullable by default. Set
// this to `false` to make arrays non-nullable by default, but be aware that
// any `nil` slice will still encode as `null` in JSON. See also:
// https://pkg.go.dev/encoding/json#Marshal.
var DefaultArrayNullable = true

// JSON Schema type constants
const (
TypeBoolean = "boolean"
Expand Down Expand Up @@ -763,7 +769,7 @@ func schemaFromType(r Registry, t reflect.Type) *Schema {
s.ContentEncoding = "base64"
} else {
s.Type = TypeArray
s.Nullable = true
s.Nullable = DefaultArrayNullable
s.Items = r.Schema(t.Elem(), true, t.Name()+"Item")

if t.Kind() == reflect.Array {
Expand Down
56 changes: 56 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,46 @@ func TestSchema(t *testing.T) {
"required": ["int"]
}`,
},
{
name: "field-nullable-array",
input: struct {
Int []int64 `json:"int" nullable:"true"`
}{},
expected: `{
"type": "object",
"additionalProperties": false,
"properties": {
"int": {
"type": ["array", "null"],
"items": {
"type": "integer",
"format": "int64"
}
}
},
"required": ["int"]
}`,
},
{
name: "field-non-nullable-array",
input: struct {
Int []int64 `json:"int" nullable:"false"`
}{},
expected: `{
"type": "object",
"additionalProperties": false,
"properties": {
"int": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
}
}
},
"required": ["int"]
}`,
},
{
name: "field-nullable-struct",
input: struct {
Expand Down Expand Up @@ -1319,6 +1359,22 @@ func TestMarshalDiscriminator(t *testing.T) {
}`, string(b))
}

func TestSchemaArrayNotNullable(t *testing.T) {
huma.DefaultArrayNullable = false
defer func() {
huma.DefaultArrayNullable = true
}()

type Value struct {
Field []string `json:"field"`
}

r := huma.NewMapRegistry("#/components/schemas/", huma.DefaultSchemaNamer)
s := r.Schema(reflect.TypeOf(Value{}), false, "")

assert.Equal(t, "array", s.Properties["field"].Type)
}

type BenchSub struct {
Visible bool `json:"visible" default:"true"`
Metrics []float64 `json:"metrics" maxItems:"31"`
Expand Down
Loading