Skip to content

Commit

Permalink
Merge pull request #314 from CptKirk/nested-struct-visibility
Browse files Browse the repository at this point in the history
Nested struct visibility
  • Loading branch information
danielgtaylor authored Mar 17, 2024
2 parents 734d276 + 9321465 commit 428dcfd
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 6 deletions.
22 changes: 22 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,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

0 comments on commit 428dcfd

Please sign in to comment.