44package schema_validation
55
66import (
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.
2424type 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
3838type schemaValidator struct {}
3939
4040// NewSchemaValidator will create a new SchemaValidator instance, ready to accept schemas and payloads to validate.
4141func NewSchemaValidator () SchemaValidator {
42- return & schemaValidator {}
42+ return & schemaValidator {}
4343}
4444
4545func (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
4949func (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
5353func (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
5757func 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