Skip to content

Commit f390834

Browse files
committed
test: force validating unkown fields in example
Signed-off-by: caozhuozi <[email protected]>
1 parent b32a1cf commit f390834

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

schema/config-schema.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
"capabilities": {
4343
"$ref": "#/$defs/ModelCapabilities"
4444
}
45-
},
46-
"additionalProperties": false
45+
}
4746
},
4847
"ModelDescriptor": {
4948
"type": "object",
@@ -92,8 +91,7 @@
9291
"description": {
9392
"type": "string"
9493
}
95-
},
96-
"additionalProperties": false
94+
}
9795
},
9896
"ModelFS": {
9997
"type": "object",
@@ -110,7 +108,6 @@
110108
"minItems": 1
111109
}
112110
},
113-
"additionalProperties": false,
114111
"required": [
115112
"type",
116113
"diffIds"

schema/example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func validate(t *testing.T, name string) {
6161
continue
6262
}
6363

64-
err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body))
64+
err = schema.Validator(example.Mediatype).ValidateNoUnknownFields(strings.NewReader(example.Body))
6565
if err == nil {
6666
printFields(t, "ok", example.Mediatype, example.Title)
6767
} else {

schema/validator.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,55 @@ import (
2626
"github.com/santhosh-tekuri/jsonschema/v5"
2727
)
2828

29-
// Validator wraps a media type string identifier and implements validation against a JSON schema.
29+
// Validate validates the given reader against the schema of the wrapped media type.
30+
// By default, unknown fields are allowed unless `additionalProperties` is explicitly set to `false`
31+
// correspondingly in the json schema.
3032
type Validator string
3133

32-
// Validate validates the given reader against the schema of the wrapped media type.
33-
func (v Validator) Validate(src io.Reader) error {
34+
func (v Validator) validateByMediaType(src io.Reader) (io.Reader, error) {
3435
// run the media type specific validation
3536
if fn, ok := validateByMediaType[v]; ok {
3637
if fn == nil {
37-
return fmt.Errorf("internal error: mapValidate is nil for %s", string(v))
38+
return nil, fmt.Errorf("internal error: mapValidate is nil for %s", string(v))
3839
}
3940
// buffer the src so the media type validation and the schema validation can both read it
4041
buf, err := io.ReadAll(src)
4142
if err != nil {
42-
return fmt.Errorf("failed to read input: %w", err)
43+
return nil, fmt.Errorf("failed to read input: %w", err)
4344
}
4445
src = bytes.NewReader(buf)
4546
err = fn(buf)
4647
if err != nil {
47-
return err
48+
return nil, err
4849
}
4950
}
5051

52+
return src, nil
53+
}
54+
55+
// Validate validates the given reader against the schema of the wrapped media type.
56+
func (v Validator) Validate(src io.Reader) error {
57+
srcReader, err := v.validateByMediaType(src)
58+
if err != nil {
59+
return err
60+
}
61+
5162
// json schema validation
52-
return v.validateSchema(src)
63+
return v.validateSchema(srcReader, false)
64+
}
65+
66+
// ValidateNoUnknownFields validates the given reader against the schema of the wrapped media type and
67+
// rejects if there are any unknown fields.
68+
func (v Validator) ValidateNoUnknownFields(src io.Reader) error {
69+
srcReader, err := v.validateByMediaType(src)
70+
if err != nil {
71+
return err
72+
}
73+
74+
return v.validateSchema(srcReader, true)
5375
}
5476

55-
func (v Validator) validateSchema(src io.Reader) error {
77+
func (v Validator) validateSchema(src io.Reader, rejectUnknownfields bool) error {
5678
if _, ok := specs[v]; !ok {
5779
return fmt.Errorf("no validator available for %s", string(v))
5880
}
@@ -94,6 +116,10 @@ func (v Validator) validateSchema(src io.Reader) error {
94116
return fmt.Errorf("failed to compile schema %s: %w", string(v), err)
95117
}
96118

119+
if rejectUnknownfields {
120+
forceSetAdditionalPropertiesFalse(schema)
121+
}
122+
97123
// read in the user input and validate
98124
var input interface{}
99125
err = json.NewDecoder(src).Decode(&input)
@@ -123,3 +149,38 @@ func validateConfig(buf []byte) error {
123149

124150
return nil
125151
}
152+
153+
// forceSetAdditionalPropertiesFalse recursively traverses the given JSON schema
154+
// and sets the `AdditionalProperties` field to `false` for all schema objects
155+
// encountered. This ensures that validation will reject any properties not explicitly
156+
// defined in the schema.
157+
//
158+
// This function modifies the schema in place.
159+
func forceSetAdditionalPropertiesFalse(schema *jsonschema.Schema) {
160+
if len(schema.Types) == 0 {
161+
return
162+
}
163+
164+
// We don't have any cases where multiple types are defined for a single field.
165+
t := schema.Types[0]
166+
if t == "object" {
167+
schema.AdditionalProperties = false
168+
}
169+
170+
// Recurse into properties
171+
if schema.Properties != nil {
172+
for _, propSchema := range schema.Properties {
173+
forceSetAdditionalPropertiesFalse(propSchema)
174+
}
175+
}
176+
177+
// Recurse into items (for arrays)
178+
if schema.Items != nil {
179+
forceSetAdditionalPropertiesFalse(schema.Items.(*jsonschema.Schema))
180+
}
181+
182+
// Recurse into additionalProperties if it's a schema
183+
if s, ok := schema.AdditionalProperties.(*jsonschema.Schema); ok {
184+
forceSetAdditionalPropertiesFalse(s)
185+
}
186+
}

0 commit comments

Comments
 (0)