4
4
package responses
5
5
6
6
import (
7
- "bytes"
8
- "encoding/json"
9
- "fmt"
10
- "github.com/pb33f/libopenapi-validator/errors"
11
- "github.com/pb33f/libopenapi-validator/helpers"
12
- "github.com/pb33f/libopenapi-validator/schema_validation"
13
- "github.com/pb33f/libopenapi/datamodel/high/base"
14
- "github.com/santhosh-tekuri/jsonschema/v5"
15
- "gopkg.in/yaml.v3"
16
- "io"
17
- "net/http"
18
- "reflect"
19
- "regexp"
20
- "strconv"
21
- "strings"
7
+ "bytes"
8
+ "encoding/json"
9
+ "fmt"
10
+ "github.com/pb33f/libopenapi-validator/errors"
11
+ "github.com/pb33f/libopenapi-validator/helpers"
12
+ "github.com/pb33f/libopenapi-validator/schema_validation"
13
+ "github.com/pb33f/libopenapi/datamodel/high/base"
14
+ "github.com/santhosh-tekuri/jsonschema/v5"
15
+ "gopkg.in/yaml.v3"
16
+ "io"
17
+ "net/http"
18
+ "reflect"
19
+ "regexp"
20
+ "strconv"
21
+ "strings"
22
22
)
23
23
24
24
var instanceLocationRegex = regexp .MustCompile (`^/(\d+)` )
@@ -29,125 +29,124 @@ var instanceLocationRegex = regexp.MustCompile(`^/(\d+)`)
29
29
//
30
30
// This function is used by the ValidateResponseBody function, but can be used independently.
31
31
func ValidateResponseSchema (
32
- request * http.Request ,
33
- response * http.Response ,
34
- schema * base.Schema ,
35
- renderedSchema ,
36
- jsonSchema []byte ) (bool , []* errors.ValidationError ) {
37
-
38
- var validationErrors []* errors.ValidationError
39
-
40
- responseBody , _ := io .ReadAll (response .Body )
41
-
42
- // close the request body, so it can be re-read later by another player in the chain
43
- _ = response .Body .Close ()
44
- response .Body = io .NopCloser (bytes .NewBuffer (responseBody ))
45
-
46
- var decodedObj interface {}
47
- _ = json .Unmarshal (responseBody , & decodedObj )
48
-
49
- // no response body? failed to decode anything? nothing to do here.
50
- if responseBody == nil || decodedObj == nil {
51
- return true , nil
52
- }
53
-
54
- // create a new jsonschema compiler and add in the rendered JSON schema.
55
- compiler := jsonschema .NewCompiler ()
56
- fName := fmt .Sprintf ("%s.json" , helpers .ResponseBodyValidation )
57
- _ = compiler .AddResource (fName ,
58
- strings .NewReader (string (jsonSchema )))
59
- jsch , _ := compiler .Compile (fName )
60
-
61
- // validate the object against the schema
62
- scErrs := jsch .Validate (decodedObj )
63
- if scErrs != nil {
64
- jk := scErrs .(* jsonschema.ValidationError )
65
-
66
- // flatten the validationErrors
67
- schFlatErrs := jk .BasicOutput ().Errors
68
- var schemaValidationErrors []* errors.SchemaValidationFailure
69
- for q := range schFlatErrs {
70
- er := schFlatErrs [q ]
71
- if er .KeywordLocation == "" || strings .HasPrefix (er .Error , "doesn't validate with" ) {
72
- continue // ignore this error, it's useless tbh, utter noise.
73
- }
74
- if er .Error != "" {
75
-
76
- // re-encode the schema.
77
- var renderedNode yaml.Node
78
- _ = yaml .Unmarshal (renderedSchema , & renderedNode )
79
-
80
- // locate the violated property in the schema
81
- located := schema_validation .LocateSchemaPropertyNodeByJSONPath (renderedNode .Content [0 ], er .KeywordLocation )
82
-
83
- // extract the element specified by the instance
84
- val := instanceLocationRegex .FindStringSubmatch (er .InstanceLocation )
85
- var referenceObject string
86
-
87
- if len (val ) > 0 {
88
- referenceIndex , _ := strconv .Atoi (val [1 ])
89
- if reflect .ValueOf (decodedObj ).Type ().Kind () == reflect .Slice {
90
- found := decodedObj .([]any )[referenceIndex ]
91
- recoded , _ := json .MarshalIndent (found , "" , " " )
92
- referenceObject = string (recoded )
93
- }
94
- }
95
- if referenceObject == "" {
96
- referenceObject = string (responseBody )
97
- }
98
-
99
- violation := & errors.SchemaValidationFailure {
100
- Reason : er .Error ,
101
- Location : er .KeywordLocation ,
102
- ReferenceSchema : string (renderedSchema ),
103
- ReferenceObject : referenceObject ,
104
- OriginalError : jk ,
105
- }
106
- // if we have a location within the schema, add it to the error
107
- if located != nil {
108
-
109
- line := located .Line
110
- // if the located node is a map or an array, then the actual human interpretable
111
- // line on which the violation occurred is the line of the key, not the value.
112
- if located .Kind == yaml .MappingNode || located .Kind == yaml .SequenceNode {
113
- if line > 0 {
114
- line --
115
- }
116
- }
117
-
118
- // location of the violation within the rendered schema.
119
- violation .Line = line
120
- violation .Column = located .Column
121
- }
122
- schemaValidationErrors = append (schemaValidationErrors , violation )
123
- }
124
- }
125
-
126
- line := 0
127
- col := 0
128
- if schema .GoLow ().Type .KeyNode != nil {
129
- line = schema .GoLow ().Type .KeyNode .Line
130
- col = schema .GoLow ().Type .KeyNode .Column
131
- }
132
-
133
-
134
- // add the error to the list
135
- validationErrors = append (validationErrors , & errors.ValidationError {
136
- ValidationType : helpers .ResponseBodyValidation ,
137
- ValidationSubType : helpers .Schema ,
138
- Message : fmt .Sprintf ("%d response body for '%s' failed to validate schema" ,
139
- response .StatusCode , request .URL .Path ),
140
- Reason : fmt .Sprintf ("The response body for status code '%d' is defined as an object. " +
141
- "However, it does not meet the schema requirements of the specification" , response .StatusCode ),
142
- SpecLine : line ,
143
- SpecCol : col ,
144
- SchemaValidationErrors : schemaValidationErrors ,
145
- HowToFix : errors .HowToFixInvalidSchema ,
146
- Context : string (renderedSchema ), // attach the rendered schema to the error
147
- })
148
- }
149
- if len (validationErrors ) > 0 {
150
- return false , validationErrors
151
- }
152
- return true , nil
32
+ request * http.Request ,
33
+ response * http.Response ,
34
+ schema * base.Schema ,
35
+ renderedSchema ,
36
+ jsonSchema []byte ) (bool , []* errors.ValidationError ) {
37
+
38
+ var validationErrors []* errors.ValidationError
39
+
40
+ responseBody , _ := io .ReadAll (response .Body )
41
+
42
+ // close the request body, so it can be re-read later by another player in the chain
43
+ _ = response .Body .Close ()
44
+ response .Body = io .NopCloser (bytes .NewBuffer (responseBody ))
45
+
46
+ var decodedObj interface {}
47
+ _ = json .Unmarshal (responseBody , & decodedObj )
48
+
49
+ // no response body? failed to decode anything? nothing to do here.
50
+ if responseBody == nil || decodedObj == nil {
51
+ return true , nil
52
+ }
53
+
54
+ // create a new jsonschema compiler and add in the rendered JSON schema.
55
+ compiler := jsonschema .NewCompiler ()
56
+ fName := fmt .Sprintf ("%s.json" , helpers .ResponseBodyValidation )
57
+ _ = compiler .AddResource (fName ,
58
+ strings .NewReader (string (jsonSchema )))
59
+ jsch , _ := compiler .Compile (fName )
60
+
61
+ // validate the object against the schema
62
+ scErrs := jsch .Validate (decodedObj )
63
+ if scErrs != nil {
64
+ jk := scErrs .(* jsonschema.ValidationError )
65
+
66
+ // flatten the validationErrors
67
+ schFlatErrs := jk .BasicOutput ().Errors
68
+ var schemaValidationErrors []* errors.SchemaValidationFailure
69
+ for q := range schFlatErrs {
70
+ er := schFlatErrs [q ]
71
+ if er .KeywordLocation == "" || strings .HasPrefix (er .Error , "doesn't validate with" ) {
72
+ continue // ignore this error, it's useless tbh, utter noise.
73
+ }
74
+ if er .Error != "" {
75
+
76
+ // re-encode the schema.
77
+ var renderedNode yaml.Node
78
+ _ = yaml .Unmarshal (renderedSchema , & renderedNode )
79
+
80
+ // locate the violated property in the schema
81
+ located := schema_validation .LocateSchemaPropertyNodeByJSONPath (renderedNode .Content [0 ], er .KeywordLocation )
82
+
83
+ // extract the element specified by the instance
84
+ val := instanceLocationRegex .FindStringSubmatch (er .InstanceLocation )
85
+ var referenceObject string
86
+
87
+ if len (val ) > 0 {
88
+ referenceIndex , _ := strconv .Atoi (val [1 ])
89
+ if reflect .ValueOf (decodedObj ).Type ().Kind () == reflect .Slice {
90
+ found := decodedObj .([]any )[referenceIndex ]
91
+ recoded , _ := json .MarshalIndent (found , "" , " " )
92
+ referenceObject = string (recoded )
93
+ }
94
+ }
95
+ if referenceObject == "" {
96
+ referenceObject = string (responseBody )
97
+ }
98
+
99
+ violation := & errors.SchemaValidationFailure {
100
+ Reason : er .Error ,
101
+ Location : er .KeywordLocation ,
102
+ ReferenceSchema : string (renderedSchema ),
103
+ ReferenceObject : referenceObject ,
104
+ OriginalError : jk ,
105
+ }
106
+ // if we have a location within the schema, add it to the error
107
+ if located != nil {
108
+
109
+ line := located .Line
110
+ // if the located node is a map or an array, then the actual human interpretable
111
+ // line on which the violation occurred is the line of the key, not the value.
112
+ if located .Kind == yaml .MappingNode || located .Kind == yaml .SequenceNode {
113
+ if line > 0 {
114
+ line --
115
+ }
116
+ }
117
+
118
+ // location of the violation within the rendered schema.
119
+ violation .Line = line
120
+ violation .Column = located .Column
121
+ }
122
+ schemaValidationErrors = append (schemaValidationErrors , violation )
123
+ }
124
+ }
125
+
126
+ line := 1
127
+ col := 0
128
+ if schema .GoLow ().Type .KeyNode != nil {
129
+ line = schema .GoLow ().Type .KeyNode .Line
130
+ col = schema .GoLow ().Type .KeyNode .Column
131
+ }
132
+
133
+ // add the error to the list
134
+ validationErrors = append (validationErrors , & errors.ValidationError {
135
+ ValidationType : helpers .ResponseBodyValidation ,
136
+ ValidationSubType : helpers .Schema ,
137
+ Message : fmt .Sprintf ("%d response body for '%s' failed to validate schema" ,
138
+ response .StatusCode , request .URL .Path ),
139
+ Reason : fmt .Sprintf ("The response body for status code '%d' is defined as an object. " +
140
+ "However, it does not meet the schema requirements of the specification" , response .StatusCode ),
141
+ SpecLine : line ,
142
+ SpecCol : col ,
143
+ SchemaValidationErrors : schemaValidationErrors ,
144
+ HowToFix : errors .HowToFixInvalidSchema ,
145
+ Context : string (renderedSchema ), // attach the rendered schema to the error
146
+ })
147
+ }
148
+ if len (validationErrors ) > 0 {
149
+ return false , validationErrors
150
+ }
151
+ return true , nil
153
152
}
0 commit comments