forked from pb33f/libopenapi-validator
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate_document.go
More file actions
137 lines (117 loc) · 4.98 KB
/
validate_document.go
File metadata and controls
137 lines (117 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package schema_validation
import (
"encoding/json"
"errors"
"fmt"
"github.com/pb33f/libopenapi"
"github.com/santhosh-tekuri/jsonschema/v6"
"go.yaml.in/yaml/v4"
"golang.org/x/text/language"
"golang.org/x/text/message"
"github.com/pb33f/libopenapi-validator/config"
liberrors "github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
)
func normalizeJSON(data any) any {
d, _ := json.Marshal(data)
var normalized any
_ = json.Unmarshal(d, &normalized)
return normalized
}
// ValidateOpenAPIDocument will validate an OpenAPI document against the OpenAPI 2, 3.0 and 3.1 schemas (depending on version)
// It will return true if the document is valid, false if it is not and a slice of ValidationError pointers.
func ValidateOpenAPIDocument(doc libopenapi.Document, opts ...config.Option) (bool, []*liberrors.ValidationError) {
options := config.NewValidationOptions(opts...)
info := doc.GetSpecInfo()
loadedSchema := info.APISchema
var validationErrors []*liberrors.ValidationError
decodedDocument := *info.SpecJSON
// Compile the JSON Schema
jsch, err := helpers.NewCompiledSchema("schema", []byte(loadedSchema), options)
if err != nil {
// schema compilation failed, return validation error instead of panicking
violation := &liberrors.SchemaValidationFailure{
Reason: fmt.Sprintf("failed to compile OpenAPI schema: %s", err.Error()),
Location: "schema compilation",
ReferenceSchema: loadedSchema,
}
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.Schema,
ValidationSubType: "compilation",
Message: "OpenAPI document schema compilation failed",
Reason: fmt.Sprintf("The OpenAPI schema failed to compile: %s", err.Error()),
SpecLine: 1,
SpecCol: 0,
SchemaValidationErrors: []*liberrors.SchemaValidationFailure{violation},
HowToFix: "check the OpenAPI schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: loadedSchema,
})
return false, validationErrors
}
// Validate the document
scErrs := jsch.Validate(normalizeJSON(decodedDocument))
var schemaValidationErrors []*liberrors.SchemaValidationFailure
if scErrs != nil {
var jk *jsonschema.ValidationError
if errors.As(scErrs, &jk) {
// flatten the validationErrors
schFlatErrs := jk.BasicOutput().Errors
// Extract property name info once before processing errors (performance optimization)
propertyInfo := extractPropertyNameFromError(jk)
for q := range schFlatErrs {
er := schFlatErrs[q]
errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{}))
if er.KeywordLocation == "" || helpers.IgnorePolyRegex.MatchString(errMsg) {
continue // ignore this error, it's useless tbh, utter noise.
}
if errMsg != "" {
// locate the violated property in the schema
located := LocateSchemaPropertyNodeByJSONPath(info.RootNode.Content[0], er.InstanceLocation)
violation := &liberrors.SchemaValidationFailure{
Reason: errMsg,
Location: er.InstanceLocation,
FieldName: helpers.ExtractFieldNameFromStringLocation(er.InstanceLocation),
FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation),
InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation),
DeepLocation: er.KeywordLocation,
AbsoluteLocation: er.AbsoluteKeywordLocation,
OriginalError: jk,
}
// if we have a location within the schema, add it to the error
if located != nil {
line := located.Line
// if the located node is a map or an array, then the actual human interpretable
// line on which the violation occurred is the line of the key, not the value.
if located.Kind == yaml.MappingNode || located.Kind == yaml.SequenceNode {
if line > 0 {
line--
}
}
// location of the violation within the rendered schema.
violation.Line = line
violation.Column = located.Column
} else {
// handles property name validation errors that don't provide useful InstanceLocation
applyPropertyNameFallback(propertyInfo, info.RootNode.Content[0], violation)
}
schemaValidationErrors = append(schemaValidationErrors, violation)
}
}
}
// add the error to the list
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.Schema,
Message: "Document does not pass validation",
Reason: fmt.Sprintf("OpenAPI document is not valid according "+
"to the %s specification", info.Version),
SchemaValidationErrors: schemaValidationErrors,
HowToFix: liberrors.HowToFixInvalidSchema,
})
}
if len(validationErrors) > 0 {
return false, validationErrors
}
return true, nil
}