Skip to content

Commit d93c54b

Browse files
committed
Addressed issue #85
If there is no type, but there is polymorphic data, the header is now validated against the schema.
1 parent 76395eb commit d93c54b

File tree

3 files changed

+241
-2
lines changed

3 files changed

+241
-2
lines changed

parameters/header_parameters.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package parameters
55

66
import (
77
"fmt"
8+
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
89
"net/http"
910
"strconv"
1011
"strings"
@@ -145,6 +146,14 @@ func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request,
145146
}
146147
}
147148
}
149+
if len(pType) == 0 {
150+
// validate schema as there is no type information.
151+
validationErrors = append(validationErrors, ValidateSingleParameterSchema(sch,
152+
param,
153+
p.Name,
154+
lowbase.SchemaLabel, p.Name, helpers.ParameterValidation, helpers.ParameterValidationHeader, v.options)...)
155+
156+
}
148157
} else {
149158
if p.Required != nil && *p.Required {
150159
validationErrors = append(validationErrors, errors.HeaderParameterMissing(p))

parameters/validate_parameter.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"fmt"
99
"net/url"
1010
"reflect"
11+
"strings"
12+
"sync"
1113

1214
"github.com/pb33f/libopenapi/datamodel/high/base"
1315
"github.com/pb33f/libopenapi/utils"
@@ -212,17 +214,64 @@ func formatJsonSchemaValidationError(schema *base.Schema, scErrs *jsonschema.Val
212214
schemaValidationErrors = append(schemaValidationErrors, fail)
213215
}
214216
schemaType := "undefined"
217+
line := 0
218+
col := 0
215219
if len(schema.Type) > 0 {
216220
schemaType = schema.Type[0]
221+
line = schema.GoLow().Type.KeyNode.Line
222+
col = schema.GoLow().Type.KeyNode.Column
223+
} else {
224+
var sTypes []string
225+
seen := make(map[string]struct{})
226+
extractTypes := func(s *base.SchemaProxy) {
227+
pSch := s.Schema()
228+
if pSch != nil {
229+
for _, typ := range pSch.Type {
230+
if _, ok := seen[typ]; !ok {
231+
sTypes = append(sTypes, typ)
232+
seen[typ] = struct{}{}
233+
}
234+
}
235+
}
236+
}
237+
processPoly := func(schemas []*base.SchemaProxy, wg *sync.WaitGroup) {
238+
if len(schemas) > 0 {
239+
for _, s := range schemas {
240+
extractTypes(s)
241+
}
242+
}
243+
wg.Done()
244+
}
245+
246+
// check if there is polymorphism going on here.
247+
if len(schema.AnyOf) > 0 || len(schema.AllOf) > 0 || len(schema.OneOf) > 0 {
248+
249+
wg := sync.WaitGroup{}
250+
wg.Add(3)
251+
go processPoly(schema.AnyOf, &wg)
252+
go processPoly(schema.AllOf, &wg)
253+
go processPoly(schema.OneOf, &wg)
254+
wg.Wait()
255+
256+
sep := "or"
257+
if len(schema.AllOf) > 0 {
258+
sep = "and a"
259+
}
260+
schemaType = strings.Join(sTypes, fmt.Sprintf(" %s ", sep))
261+
}
262+
263+
line = schema.GoLow().RootNode.Line
264+
col = schema.GoLow().RootNode.Column
217265
}
266+
218267
validationErrors = append(validationErrors, &errors.ValidationError{
219268
ValidationType: validationType,
220269
ValidationSubType: subValType,
221270
Message: fmt.Sprintf("%s '%s' failed to validate", entity, name),
222271
Reason: fmt.Sprintf("%s '%s' is defined as an %s, "+
223272
"however it failed to pass a schema validation", reasonEntity, name, schemaType),
224-
SpecLine: schema.GoLow().Type.KeyNode.Line,
225-
SpecCol: schema.GoLow().Type.KeyNode.Column,
273+
SpecLine: line,
274+
SpecCol: col,
226275
SchemaValidationErrors: schemaValidationErrors,
227276
HowToFix: errors.HowToFixInvalidSchema,
228277
})

parameters/validate_parameter_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package parameters
22

33
import (
4+
"github.com/pb33f/libopenapi"
5+
"github.com/stretchr/testify/assert"
6+
"net/http"
47
"testing"
58

69
"github.com/stretchr/testify/require"
@@ -13,3 +16,181 @@ func Test_ForceCompilerError(t *testing.T) {
1316
// Ideally this would result in an error response, current behavior swallows the error
1417
require.Empty(t, result)
1518
}
19+
20+
func TestHeaderSchemaNoType(t *testing.T) {
21+
22+
bytes := []byte(`{
23+
"openapi": "3.0.0",
24+
"info": {
25+
"title": "API Spec With Mandatory Header",
26+
"version": "1.0.0"
27+
},
28+
"paths": {
29+
"/api-endpoint": {
30+
"get": {
31+
"summary": "Restricted API Endpoint",
32+
"parameters": [
33+
{
34+
"name": "apiKey",
35+
"in": "header",
36+
"required": true,
37+
"schema": {
38+
"oneOf": [
39+
{
40+
"type": "boolean"
41+
},
42+
{
43+
"type": "integer"
44+
}
45+
]
46+
}
47+
}
48+
],
49+
"responses": {
50+
"200": {
51+
"description": "Successful response"
52+
}
53+
}
54+
}
55+
}
56+
},
57+
"components": {
58+
"securitySchemes": {
59+
"ApiKeyHeader": {
60+
"type": "apiKey",
61+
"name": "apiKey",
62+
"in": "header"
63+
}
64+
}
65+
},
66+
"security": [
67+
{
68+
"ApiKeyHeader": []
69+
}
70+
]
71+
}`)
72+
73+
doc, err := libopenapi.NewDocument(bytes)
74+
if err != nil {
75+
t.Fatalf("error while creating open api spec document: %v", err)
76+
}
77+
78+
req, err := http.NewRequest("GET", "/api-endpoint", nil)
79+
if err != nil {
80+
t.Fatalf("error while creating request: %v", err)
81+
}
82+
83+
req.Header.Set("Content-Type", "application/json")
84+
req.Header.Set("apiKey", "headerValue")
85+
86+
v3Model, errs := doc.BuildV3Model()
87+
if len(errs) > 0 {
88+
t.Fatalf("error while building v3 model: %v", errs)
89+
}
90+
91+
v3Model.Model.Servers = nil
92+
// render the document back to bytes and reload the model.
93+
_, doc, v3Model, errs = doc.RenderAndReload()
94+
95+
validator := NewParameterValidator(&v3Model.Model)
96+
97+
isSuccess, valErrs := validator.ValidateHeaderParams(req)
98+
99+
assert.False(t, isSuccess)
100+
assert.Len(t, valErrs, 1)
101+
assert.Equal(t, "schema 'apiKey' is defined as an boolean or integer, however it failed to pass a schema validation", valErrs[0].Reason)
102+
assert.Len(t, valErrs[0].SchemaValidationErrors, 2)
103+
assert.Equal(t, "got string, want boolean", valErrs[0].SchemaValidationErrors[0].Reason)
104+
assert.Equal(t, "got string, want integer", valErrs[0].SchemaValidationErrors[1].Reason)
105+
106+
}
107+
108+
func TestHeaderSchemaNoType_AllPoly(t *testing.T) {
109+
110+
bytes := []byte(`{
111+
"openapi": "3.0.0",
112+
"info": {
113+
"title": "API Spec With Mandatory Header",
114+
"version": "1.0.0"
115+
},
116+
"paths": {
117+
"/api-endpoint": {
118+
"get": {
119+
"summary": "Restricted API Endpoint",
120+
"parameters": [
121+
{
122+
"name": "apiKey",
123+
"in": "header",
124+
"required": true,
125+
"schema": {
126+
"oneOf": [
127+
{
128+
"type": "boolean"
129+
},
130+
{
131+
"type": "integer"
132+
}
133+
],
134+
"allOf": [
135+
{
136+
"type": "boolean"
137+
},
138+
]
139+
}
140+
}
141+
],
142+
"responses": {
143+
"200": {
144+
"description": "Successful response"
145+
}
146+
}
147+
}
148+
}
149+
},
150+
"components": {
151+
"securitySchemes": {
152+
"ApiKeyHeader": {
153+
"type": "apiKey",
154+
"name": "apiKey",
155+
"in": "header"
156+
}
157+
}
158+
},
159+
"security": [
160+
{
161+
"ApiKeyHeader": []
162+
}
163+
]
164+
}`)
165+
166+
doc, err := libopenapi.NewDocument(bytes)
167+
if err != nil {
168+
t.Fatalf("error while creating open api spec document: %v", err)
169+
}
170+
171+
req, err := http.NewRequest("GET", "/api-endpoint", nil)
172+
if err != nil {
173+
t.Fatalf("error while creating request: %v", err)
174+
}
175+
176+
req.Header.Set("Content-Type", "application/json")
177+
req.Header.Set("apiKey", "headerValue")
178+
179+
v3Model, errs := doc.BuildV3Model()
180+
if len(errs) > 0 {
181+
t.Fatalf("error while building v3 model: %v", errs)
182+
}
183+
184+
v3Model.Model.Servers = nil
185+
// render the document back to bytes and reload the model.
186+
_, doc, v3Model, errs = doc.RenderAndReload()
187+
188+
validator := NewParameterValidator(&v3Model.Model)
189+
190+
isSuccess, valErrs := validator.ValidateHeaderParams(req)
191+
192+
assert.False(t, isSuccess)
193+
assert.Len(t, valErrs, 1)
194+
assert.Equal(t, "schema 'apiKey' is defined as an boolean and a integer, however it failed to pass a schema validation", valErrs[0].Reason)
195+
assert.Len(t, valErrs[0].SchemaValidationErrors, 3)
196+
}

0 commit comments

Comments
 (0)