Skip to content

Commit d4e2e98

Browse files
committed
Added support for jsonschema.InvalidJSONTypeError
Fixes a bug report filed in vacuum daveshanley/vacuum#271 Test confirms support. Excuse the formatting, I moved from windows to mac dev a month ago, and all my files are being cleaned automatically. Signed-off-by: Dave Shanley <[email protected]>
1 parent 2aa6245 commit d4e2e98

File tree

2 files changed

+284
-233
lines changed

2 files changed

+284
-233
lines changed

schema_validation/validate_schema.go

Lines changed: 98 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
package schema_validation
55

66
import (
7-
"encoding/json"
8-
"github.com/pb33f/libopenapi-validator/errors"
9-
"github.com/pb33f/libopenapi-validator/helpers"
10-
"github.com/pb33f/libopenapi/datamodel/high/base"
11-
"github.com/pb33f/libopenapi/utils"
12-
"github.com/santhosh-tekuri/jsonschema/v5"
13-
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
14-
"gopkg.in/yaml.v3"
15-
"strings"
7+
"encoding/json"
8+
"github.com/pb33f/libopenapi-validator/errors"
9+
"github.com/pb33f/libopenapi-validator/helpers"
10+
"github.com/pb33f/libopenapi/datamodel/high/base"
11+
"github.com/pb33f/libopenapi/utils"
12+
"github.com/santhosh-tekuri/jsonschema/v5"
13+
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
14+
"gopkg.in/yaml.v3"
15+
"strings"
1616
)
1717

1818
// SchemaValidator is an interface that defines the methods for validating a *base.Schema (V3+ Only) object.
@@ -23,102 +23,114 @@ import (
2323
// ValidateSchemaBytes accepts a schema object to validate against, and a JSON/YAML blob that is defined as a byte array.
2424
type SchemaValidator interface {
2525

26-
// ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
27-
ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError)
26+
// ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
27+
ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError)
2828

29-
// ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML.
30-
// This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML.
31-
ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError)
29+
// ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML.
30+
// This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML.
31+
ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError)
3232

33-
// ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to
34-
// validate against.
35-
ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError)
33+
// ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to
34+
// validate against.
35+
ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError)
3636
}
3737

3838
type schemaValidator struct{}
3939

4040
// NewSchemaValidator will create a new SchemaValidator instance, ready to accept schemas and payloads to validate.
4141
func NewSchemaValidator() SchemaValidator {
42-
return &schemaValidator{}
42+
return &schemaValidator{}
4343
}
4444

4545
func (s *schemaValidator) ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError) {
46-
return validateSchema(schema, []byte(payload), nil)
46+
return validateSchema(schema, []byte(payload), nil)
4747
}
4848

4949
func (s *schemaValidator) ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError) {
50-
return validateSchema(schema, nil, payload)
50+
return validateSchema(schema, nil, payload)
5151
}
5252

5353
func (s *schemaValidator) ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError) {
54-
return validateSchema(schema, payload, nil)
54+
return validateSchema(schema, payload, nil)
5555
}
5656

5757
func validateSchema(schema *base.Schema, payload []byte, decodedObject interface{}) (bool, []*errors.ValidationError) {
5858

59-
var validationErrors []*errors.ValidationError
60-
61-
// render the schema, to be used for validation
62-
renderedSchema, _ := schema.RenderInline()
63-
jsonSchema, _ := utils.ConvertYAMLtoJSON(renderedSchema)
64-
65-
if decodedObject == nil {
66-
_ = json.Unmarshal(payload, &decodedObject)
67-
}
68-
compiler := jsonschema.NewCompiler()
69-
_ = compiler.AddResource("schema.json", strings.NewReader(string(jsonSchema)))
70-
jsch, _ := compiler.Compile("schema.json")
71-
72-
// 4. validate the object against the schema
73-
scErrs := jsch.Validate(decodedObject)
74-
if scErrs != nil {
75-
jk := scErrs.(*jsonschema.ValidationError)
76-
77-
// flatten the validationErrors
78-
schFlatErrs := jk.BasicOutput().Errors
79-
var schemaValidationErrors []*errors.SchemaValidationFailure
80-
for q := range schFlatErrs {
81-
er := schFlatErrs[q]
82-
if er.KeywordLocation == "" || strings.HasPrefix(er.Error, "doesn't validate with") {
83-
continue // ignore this error, it's useless tbh, utter noise.
84-
}
85-
if er.Error != "" {
86-
87-
// re-encode the schema.
88-
var renderedNode yaml.Node
89-
_ = yaml.Unmarshal(renderedSchema, &renderedNode)
90-
91-
// locate the violated property in the schema
92-
located := LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation)
93-
violation := &errors.SchemaValidationFailure{
94-
Reason: er.Error,
95-
Location: er.KeywordLocation,
96-
OriginalError: jk,
97-
}
98-
// if we have a location within the schema, add it to the error
99-
if located != nil {
100-
// location of the violation within the rendered schema.
101-
violation.Line = located.Line
102-
violation.Column = located.Column
103-
}
104-
schemaValidationErrors = append(schemaValidationErrors, violation)
105-
}
106-
}
107-
108-
// add the error to the list
109-
validationErrors = append(validationErrors, &errors.ValidationError{
110-
ValidationType: helpers.Schema,
111-
Message: "schema does not pass validation",
112-
Reason: "Schema failed to validate against the contract requirements",
113-
SpecLine: schema.GoLow().Type.KeyNode.Line,
114-
SpecCol: schema.GoLow().Type.KeyNode.Column,
115-
SchemaValidationErrors: schemaValidationErrors,
116-
HowToFix: errors.HowToFixInvalidSchema,
117-
Context: string(renderedSchema), // attach the rendered schema to the error
118-
})
119-
}
120-
if len(validationErrors) > 0 {
121-
return false, validationErrors
122-
}
123-
return true, nil
59+
var validationErrors []*errors.ValidationError
60+
61+
// render the schema, to be used for validation
62+
renderedSchema, _ := schema.RenderInline()
63+
jsonSchema, _ := utils.ConvertYAMLtoJSON(renderedSchema)
64+
65+
if decodedObject == nil {
66+
_ = json.Unmarshal(payload, &decodedObject)
67+
}
68+
compiler := jsonschema.NewCompiler()
69+
_ = compiler.AddResource("schema.json", strings.NewReader(string(jsonSchema)))
70+
jsch, _ := compiler.Compile("schema.json")
71+
72+
// 4. validate the object against the schema
73+
scErrs := jsch.Validate(decodedObject)
74+
if scErrs != nil {
75+
var schemaValidationErrors []*errors.SchemaValidationFailure
76+
77+
// check for invalid JSON type errors.
78+
if _, ok := scErrs.(jsonschema.InvalidJSONTypeError); ok {
79+
violation := &errors.SchemaValidationFailure{
80+
Reason: scErrs.Error(),
81+
Location: "unavailable", // we don't have a location for this error, so we'll just say it's unavailable.
82+
}
83+
schemaValidationErrors = append(schemaValidationErrors, violation)
84+
}
85+
86+
if jk, ok := scErrs.(*jsonschema.ValidationError); ok {
87+
88+
// flatten the validationErrors
89+
schFlatErrs := jk.BasicOutput().Errors
90+
91+
for q := range schFlatErrs {
92+
er := schFlatErrs[q]
93+
if er.KeywordLocation == "" || strings.HasPrefix(er.Error, "doesn't validate with") {
94+
continue // ignore this error, it's useless tbh, utter noise.
95+
}
96+
if er.Error != "" {
97+
98+
// re-encode the schema.
99+
var renderedNode yaml.Node
100+
_ = yaml.Unmarshal(renderedSchema, &renderedNode)
101+
102+
// locate the violated property in the schema
103+
located := LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation)
104+
violation := &errors.SchemaValidationFailure{
105+
Reason: er.Error,
106+
Location: er.KeywordLocation,
107+
OriginalError: jk,
108+
}
109+
// if we have a location within the schema, add it to the error
110+
if located != nil {
111+
// location of the violation within the rendered schema.
112+
violation.Line = located.Line
113+
violation.Column = located.Column
114+
}
115+
schemaValidationErrors = append(schemaValidationErrors, violation)
116+
}
117+
}
118+
}
119+
120+
// add the error to the list
121+
validationErrors = append(validationErrors, &errors.ValidationError{
122+
ValidationType: helpers.Schema,
123+
Message: "schema does not pass validation",
124+
Reason: "Schema failed to validate against the contract requirements",
125+
SpecLine: schema.GoLow().Type.KeyNode.Line,
126+
SpecCol: schema.GoLow().Type.KeyNode.Column,
127+
SchemaValidationErrors: schemaValidationErrors,
128+
HowToFix: errors.HowToFixInvalidSchema,
129+
Context: string(renderedSchema), // attach the rendered schema to the error
130+
})
131+
}
132+
if len(validationErrors) > 0 {
133+
return false, validationErrors
134+
}
135+
return true, nil
124136
}

0 commit comments

Comments
 (0)