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- "gopkg.in/yaml.v3"
14- "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"
1516)
1617
1718// SchemaValidator is an interface that defines the methods for validating a *base.Schema (V3+ Only) object.
@@ -22,102 +23,102 @@ import (
2223// ValidateSchemaBytes accepts a schema object to validate against, and a JSON/YAML blob that is defined as a byte array.
2324type SchemaValidator interface {
2425
25- // ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
26- 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 )
2728
28- // ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML.
29- // This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML.
30- 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 )
3132
32- // ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to
33- // validate against.
34- 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 )
3536}
3637
3738type schemaValidator struct {}
3839
3940// NewSchemaValidator will create a new SchemaValidator instance, ready to accept schemas and payloads to validate.
4041func NewSchemaValidator () SchemaValidator {
41- return & schemaValidator {}
42+ return & schemaValidator {}
4243}
4344
4445func (s * schemaValidator ) ValidateSchemaString (schema * base.Schema , payload string ) (bool , []* errors.ValidationError ) {
45- return validateSchema (schema , []byte (payload ), nil )
46+ return validateSchema (schema , []byte (payload ), nil )
4647}
4748
4849func (s * schemaValidator ) ValidateSchemaObject (schema * base.Schema , payload interface {}) (bool , []* errors.ValidationError ) {
49- return validateSchema (schema , nil , payload )
50+ return validateSchema (schema , nil , payload )
5051}
5152
5253func (s * schemaValidator ) ValidateSchemaBytes (schema * base.Schema , payload []byte ) (bool , []* errors.ValidationError ) {
53- return validateSchema (schema , payload , nil )
54+ return validateSchema (schema , payload , nil )
5455}
5556
5657func validateSchema (schema * base.Schema , payload []byte , decodedObject interface {}) (bool , []* errors.ValidationError ) {
5758
58- var validationErrors []* errors.ValidationError
59-
60- // render the schema, to be used for validation
61- renderedSchema , _ := schema .RenderInline ()
62- jsonSchema , _ := utils .ConvertYAMLtoJSON (renderedSchema )
63-
64- if decodedObject == nil {
65- _ = json .Unmarshal (payload , & decodedObject )
66- }
67- compiler := jsonschema .NewCompiler ()
68- _ = compiler .AddResource ("schema.json" , strings .NewReader (string (jsonSchema )))
69- jsch , _ := compiler .Compile ("schema.json" )
70-
71- // 4. validate the object against the schema
72- scErrs := jsch .Validate (decodedObject )
73- if scErrs != nil {
74- jk := scErrs .(* jsonschema.ValidationError )
75-
76- // flatten the validationErrors
77- schFlatErrs := jk .BasicOutput ().Errors
78- var schemaValidationErrors []* errors.SchemaValidationFailure
79- for q := range schFlatErrs {
80- er := schFlatErrs [q ]
81- if er .KeywordLocation == "" || strings .HasPrefix (er .Error , "doesn't validate with" ) {
82- continue // ignore this error, it's useless tbh, utter noise.
83- }
84- if er .Error != "" {
85-
86- // re-encode the schema.
87- var renderedNode yaml.Node
88- _ = yaml .Unmarshal (renderedSchema , & renderedNode )
89-
90- // locate the violated property in the schema
91- located := LocateSchemaPropertyNodeByJSONPath (renderedNode .Content [0 ], er .KeywordLocation )
92- violation := & errors.SchemaValidationFailure {
93- Reason : er .Error ,
94- Location : er .KeywordLocation ,
95- OriginalError : jk ,
96- }
97- // if we have a location within the schema, add it to the error
98- if located != nil {
99- // location of the violation within the rendered schema.
100- violation .Line = located .Line
101- violation .Column = located .Column
102- }
103- schemaValidationErrors = append (schemaValidationErrors , violation )
104- }
105- }
106-
107- // add the error to the list
108- validationErrors = append (validationErrors , & errors.ValidationError {
109- ValidationType : helpers .Schema ,
110- Message : "schema does not pass validation" ,
111- Reason : "Schema failed to validated against the contract requirements" ,
112- SpecLine : schema .GoLow ().Type .KeyNode .Line ,
113- SpecCol : schema .GoLow ().Type .KeyNode .Column ,
114- SchemaValidationErrors : schemaValidationErrors ,
115- HowToFix : errors .HowToFixInvalidSchema ,
116- Context : string (renderedSchema ), // attach the rendered schema to the error
117- })
118- }
119- if len (validationErrors ) > 0 {
120- return false , validationErrors
121- }
122- 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+ 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
123124}
0 commit comments