Skip to content

Commit 3ebd4ff

Browse files
committed
A number of fixes after implementing in vacuum
Some issues with handling schemas when used as a library. Signed-off-by: Dave Shanley <[email protected]>
1 parent e882dce commit 3ebd4ff

File tree

4 files changed

+111
-121
lines changed

4 files changed

+111
-121
lines changed

paths/paths.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ func FindPath(request *http.Request, document *v3.Document) (*v3.PathItem, []*er
2525
var validationErrors []*errors.ValidationError
2626

2727
// extract base path from document to check against paths.
28-
basePaths := make([]string, len(document.Servers))
28+
var basePaths []string
2929
for i, s := range document.Servers {
3030
u, _ := url.Parse(s.URL)
31-
basePaths[i] = u.Path
31+
if u.Path != "" {
32+
basePaths[i] = u.Path
33+
}
3234
}
3335

3436
// strip any base path
@@ -200,9 +202,9 @@ pathFound:
200202
validationErrors = append(validationErrors, &errors.ValidationError{
201203
ValidationType: helpers.ParameterValidationPath,
202204
ValidationSubType: "missing",
203-
Message: fmt.Sprintf("Path '%s' not found", request.URL.Path),
204-
Reason: fmt.Sprintf("The request contains a path of '%s' "+
205-
"however that path does not exist in the specification", request.URL.Path),
205+
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
206+
Reason: fmt.Sprintf("The %s request contains a path of '%s' "+
207+
"however that path does not exist in the specification", request.Method, request.URL.Path),
206208
SpecLine: -1,
207209
SpecCol: -1,
208210
})

schema_validation/locate_schema_property.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
package schema_validation
55

66
import (
7-
"github.com/pb33f/libopenapi/utils"
8-
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
9-
"gopkg.in/yaml.v3"
7+
"github.com/pb33f/libopenapi/utils"
8+
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
9+
"gopkg.in/yaml.v3"
1010
)
1111

1212
// LocateSchemaPropertyNodeByJSONPath will locate a schema property node by a JSONPath. It converts something like
1313
// #/components/schemas/MySchema/properties/MyProperty to something like $.components.schemas.MySchema.properties.MyProperty
1414
func LocateSchemaPropertyNodeByJSONPath(doc *yaml.Node, JSONPath string) *yaml.Node {
15-
// first convert the path to something we can use as a lookup, remove the leading slash
16-
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(JSONPath)
17-
yamlPath, _ := yamlpath.NewPath(path)
18-
locatedNodes, _ := yamlPath.Find(doc)
19-
if len(locatedNodes) > 0 {
20-
return locatedNodes[0]
21-
}
22-
return nil
15+
// first convert the path to something we can use as a lookup, remove the leading slash
16+
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(JSONPath)
17+
if path != "" {
18+
return nil
19+
}
20+
yamlPath, _ := yamlpath.NewPath(path)
21+
locatedNodes, _ := yamlPath.Find(doc)
22+
if len(locatedNodes) > 0 {
23+
return locatedNodes[0]
24+
}
25+
return nil
2326
}

schema_validation/validate_document.go

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,22 @@ import (
88
"github.com/pb33f/libopenapi"
99
"github.com/pb33f/libopenapi-validator/errors"
1010
"github.com/pb33f/libopenapi-validator/helpers"
11-
"github.com/pb33f/libopenapi-validator/schema_validation/openapi_schemas"
12-
"github.com/pb33f/libopenapi/utils"
1311
"github.com/santhosh-tekuri/jsonschema/v5"
12+
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
1413
"strings"
1514
)
1615

17-
// ValidateOpenAPIDocument will validate an OpenAPI 3+ document against the OpenAPI 3.0 schema.
16+
// ValidateOpenAPIDocument will validate an OpenAPI document against the OpenAPI 2, 3.0 and 3.1 schemas (depending on version)
1817
// It will return true if the document is valid, false if it is not and a slice of ValidationError pointers.
19-
// Swagger / OpenAPI 2.0 documents are not supported by this validator (and they won't be).
2018
func ValidateOpenAPIDocument(doc libopenapi.Document) (bool, []*errors.ValidationError) {
2119

22-
// first determine if this is a swagger or an openapi document
2320
info := doc.GetSpecInfo()
24-
if info.SpecType == utils.OpenApi2 {
25-
return false, []*errors.ValidationError{{Message: "Swagger / OpenAPI 2.0 is not supported by the validator"}}
26-
}
27-
var loadedSchema string
28-
29-
// check version of openapi and load schema
30-
switch info.Version {
31-
case "3.1.0", "3.1":
32-
loadedSchema = openapi_schemas.LoadSchema3_1(info.APISchema)
33-
default:
34-
loadedSchema = openapi_schemas.LoadSchema3_0(info.APISchema)
35-
}
36-
21+
loadedSchema := info.APISchema
3722
var validationErrors []*errors.ValidationError
38-
3923
decodedDocument := *info.SpecJSON
4024

4125
compiler := jsonschema.NewCompiler()
42-
_ = compiler.AddResource("schema.json", strings.NewReader(string(loadedSchema)))
26+
_ = compiler.AddResource("schema.json", strings.NewReader(loadedSchema))
4327
jsch, _ := compiler.Compile("schema.json")
4428

4529
scErrs := jsch.Validate(decodedDocument)

schema_validation/validate_schema.go

Lines changed: 86 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +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-
"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.
2324
type 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

3738
type schemaValidator struct{}
3839

3940
// NewSchemaValidator will create a new SchemaValidator instance, ready to accept schemas and payloads to validate.
4041
func NewSchemaValidator() SchemaValidator {
41-
return &schemaValidator{}
42+
return &schemaValidator{}
4243
}
4344

4445
func (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

4849
func (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

5253
func (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

5657
func 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

Comments
 (0)