Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: validate hidden fields #573

Merged
merged 1 commit into from
Sep 17, 2024
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
35 changes: 28 additions & 7 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ type Schema struct {
patternRe *regexp.Regexp `yaml:"-"`
requiredMap map[string]bool `yaml:"-"`
propertyNames []string `yaml:"-"`
hidden bool `yaml:"-"`

// Precomputed validation messages. These prevent allocations during
// validation and are known at schema creation time.
Expand Down Expand Up @@ -157,10 +158,26 @@ func (s *Schema) MarshalJSON() ([]byte, error) {
if s.Nullable {
typ = []string{s.Type, "null"}
}

var contentMediaType string
if s.Format == "binary" {
contentMediaType = "application/octet-stream"
}

props := s.Properties
for _, ps := range props {
if ps.hidden {
// Copy the map to avoid modifying the original schema.
props = make(map[string]*Schema, len(s.Properties))
for k, v := range s.Properties {
if !v.hidden {
props[k] = v
}
}
break
}
}

return marshalJSON([]jsonFieldInfo{
{"type", typ, omitEmpty},
{"title", s.Title, omitEmpty},
Expand All @@ -173,7 +190,7 @@ func (s *Schema) MarshalJSON() ([]byte, error) {
{"examples", s.Examples, omitEmpty},
{"items", s.Items, omitEmpty},
{"additionalProperties", s.AdditionalProperties, omitNil},
{"properties", s.Properties, omitEmpty},
{"properties", props, omitEmpty},
{"enum", s.Enum, omitEmpty},
{"minimum", s.Minimum, omitEmpty},
{"exclusiveMinimum", s.ExclusiveMinimum, omitEmpty},
Expand Down Expand Up @@ -589,6 +606,10 @@ func SchemaFromField(registry Registry, f reflect.StructField, hint string) *Sch
fs.Deprecated = boolTag(f, "deprecated")
fs.PrecomputeMessages()

if v := f.Tag.Get("hidden"); v != "" {
fs.hidden = boolTag(f, "hidden")
}

return fs
}

Expand Down Expand Up @@ -812,12 +833,6 @@ func schemaFromType(r Registry, t reflect.Type) *Schema {
fieldRequired = boolTag(f, "required")
}

if boolTag(f, "hidden") {
// This field is deliberately ignored. It may still exist, but won't
// be documented.
continue
}

if dr := f.Tag.Get("dependentRequired"); strings.TrimSpace(dr) != "" {
dependentRequiredMap[name] = strings.Split(dr, ",")
}
Expand All @@ -827,6 +842,12 @@ func schemaFromType(r Registry, t reflect.Type) *Schema {
props[name] = fs
propNames = append(propNames, name)

if fs.hidden {
// This field is deliberately ignored. It may still exist, but won't
// be documented as a required field.
fieldRequired = false
}

if fieldRequired {
required = append(required, name)
requiredMap[name] = true
Expand Down
18 changes: 13 additions & 5 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,18 +619,26 @@ func TestSchema(t *testing.T) {
{
name: "field-skip",
input: struct {
// Not filtered out (just a normal field)
Value1 string `json:"value1"`
// Filtered out from JSON tag
Value1 string `json:"-"`
Value2 string `json:"-"`
// Filtered because it's private
value2 string
value3 string
// Filtered due to being an unsupported type
Value3 func()
Value4 func()
// Filtered due to being hidden
Value4 string `json:"value4,omitempty" hidden:"true"`
Value5 string `json:"value4,omitempty" hidden:"true"`
}{},
expected: `{
"type": "object",
"additionalProperties": false
"additionalProperties": false,
"required": ["value1"],
"properties": {
"value1": {
"type": "string"
}
}
}`,
},
{
Expand Down
22 changes: 22 additions & 0 deletions validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,28 @@ var validateTests = []struct {
input: map[string]any{"value": ""},
errs: []string{"expected length >= 1"},
},
{
name: "hidden is optional",
typ: reflect.TypeOf(struct {
Value string `json:"value" minLength:"5" hidden:"true"`
}{}),
input: map[any]any{},
},
{
name: "hidden success",
typ: reflect.TypeOf(struct {
Value string `json:"value" minLength:"5" hidden:"true"`
}{}),
input: map[any]any{"value": "abcde"},
},
{
name: "hidden fail",
typ: reflect.TypeOf(struct {
Value string `json:"value" minLength:"5" hidden:"true"`
}{}),
input: map[any]any{"value": "abc"},
errs: []string{"expected length >= 5"},
},
{
name: "dependentRequired empty success",
typ: reflect.TypeOf(struct {
Expand Down