@@ -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.
3032type 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