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
22 changes: 22 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,28 @@ func TestFeatures(t *testing.T) {
// Headers: map[string]string{"Content-Type": "application/json"},
Body: `{"name":"foo"}`,
},
{
Name: "request-body-nested-struct-readOnly",
Register: func(t *testing.T, api huma.API) {
type NestedStruct struct {
Foo struct {
Bar string `json:"bar"`
} `json:"foo" readOnly:"true"`
Value string `json:"value"`
}
huma.Register(api, huma.Operation{
Method: http.MethodPost,
Path: "/body",
}, func(ctx context.Context, input *struct {
Body *NestedStruct
}) (*struct{}, error) {
return nil, nil
})
},
Method: http.MethodPost,
URL: "/body",
Body: `{"value":"test"}`,
},
{
Name: "request-body-defaults",
Register: func(t *testing.T, api huma.API) {
Expand Down
19 changes: 19 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,25 @@ func TestSchema(t *testing.T) {
"required": ["value"]
}`,
},
{
name: "field-readonly-struct",
input: struct {
Value struct {
Foo string `json:"foo"`
} `json:"value" readOnly:"true"`
}{},
expected: `{
"type": "object",
"properties": {
"value": {
"$ref": "#/components/schemas/ValueStruct",
"readOnly": true
}
},
"additionalProperties": false,
"required": ["value"]
}`,
},
{
name: "field-default-string",
input: struct {
Expand Down
26 changes: 20 additions & 6 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,13 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,

for _, k := range s.propertyNames {
v := s.Properties[k]

// Schemas are generated such that the read/write-only properties are set
// alongside the `$ref`, if it is present (i.e. for objects). If not,
// then the read/write-only properties are set directly on the schema and
// the `for` loop never runs.
readOnly := v.ReadOnly
writeOnly := v.WriteOnly
for v.Ref != "" {
v = r.SchemaFromRef(v.Ref)
}
Expand All @@ -547,7 +554,7 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,
// TODO: should we make this configurable?

// Be stricter for responses, enabling validation of the server if desired.
if mode == ModeReadFromServer && v.WriteOnly && m[k] != nil && !reflect.ValueOf(m[k]).IsZero() {
if mode == ModeReadFromServer && writeOnly && m[k] != nil && !reflect.ValueOf(m[k]).IsZero() {
res.Add(path, m[k], "write only property is non-zero")
continue
}
Expand All @@ -556,8 +563,8 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,
if !s.requiredMap[k] {
continue
}
if (mode == ModeWriteToServer && v.ReadOnly) ||
(mode == ModeReadFromServer && v.WriteOnly) {
if (mode == ModeWriteToServer && readOnly) ||
(mode == ModeReadFromServer && writeOnly) {
// These are not required for the current mode.
continue
}
Expand Down Expand Up @@ -619,6 +626,13 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m

for _, k := range s.propertyNames {
v := s.Properties[k]

// Schemas are generated such that the read/write-only properties are set
// alongside the `$ref`, if it is present (i.e. for objects). If not,
// then the read/write-only properties are set directly on the schema and
// the `for` loop never runs.
readOnly := v.ReadOnly
writeOnly := v.WriteOnly
for v.Ref != "" {
v = r.SchemaFromRef(v.Ref)
}
Expand All @@ -628,7 +642,7 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m
// TODO: should we make this configurable?

// Be stricter for responses, enabling validation of the server if desired.
if mode == ModeReadFromServer && v.WriteOnly && m[k] != nil && !reflect.ValueOf(m[k]).IsZero() {
if mode == ModeReadFromServer && writeOnly && m[k] != nil && !reflect.ValueOf(m[k]).IsZero() {
res.Add(path, m[k], "write only property is non-zero")
continue
}
Expand All @@ -637,8 +651,8 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m
if !s.requiredMap[k] {
continue
}
if (mode == ModeWriteToServer && v.ReadOnly) ||
(mode == ModeReadFromServer && v.WriteOnly) {
if (mode == ModeWriteToServer && readOnly) ||
(mode == ModeReadFromServer && writeOnly) {
// These are not required for the current mode.
continue
}
Expand Down