Skip to content

Commit 1aaa7c8

Browse files
author
Florent Boisselier
committed
Separate validation of the integer type schema from the number type schema
1 parent 224827e commit 1aaa7c8

9 files changed

+904
-32
lines changed

errors/parameter_errors.go

+58
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,22 @@ func IncorrectCookieParamArrayBoolean(
166166
}
167167
}
168168

169+
func IncorrectQueryParamArrayInteger(
170+
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema,
171+
) *ValidationError {
172+
return &ValidationError{
173+
ValidationType: helpers.ParameterValidation,
174+
ValidationSubType: helpers.ParameterValidationQuery,
175+
Message: fmt.Sprintf("Query array parameter '%s' is not a valid integer", param.Name),
176+
Reason: fmt.Sprintf("The query parameter (which is an array) '%s' is defined as being a integer, "+
177+
"however the value '%s' is not a valid integer", param.Name, item),
178+
SpecLine: sch.Items.A.GoLow().Schema().Type.KeyNode.Line,
179+
SpecCol: sch.Items.A.GoLow().Schema().Type.KeyNode.Column,
180+
Context: itemsSchema,
181+
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, item),
182+
}
183+
}
184+
169185
func IncorrectQueryParamArrayNumber(
170186
param *v3.Parameter, item string, sch *base.Schema, itemsSchema *base.Schema,
171187
) *ValidationError {
@@ -226,6 +242,20 @@ func IncorrectQueryParamBool(param *v3.Parameter, ef string, sch *base.Schema) *
226242
}
227243
}
228244

245+
func InvalidQueryParamInteger(param *v3.Parameter, ef string, sch *base.Schema) *ValidationError {
246+
return &ValidationError{
247+
ValidationType: helpers.ParameterValidation,
248+
ValidationSubType: helpers.ParameterValidationQuery,
249+
Message: fmt.Sprintf("Query parameter '%s' is not a valid integer", param.Name),
250+
Reason: fmt.Sprintf("The query parameter '%s' is defined as being a integer, "+
251+
"however the value '%s' is not a valid integer", param.Name, ef),
252+
SpecLine: param.GoLow().Schema.KeyNode.Line,
253+
SpecCol: param.GoLow().Schema.KeyNode.Column,
254+
Context: sch,
255+
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, ef),
256+
}
257+
}
258+
229259
func InvalidQueryParamNumber(param *v3.Parameter, ef string, sch *base.Schema) *ValidationError {
230260
return &ValidationError{
231261
ValidationType: helpers.ParameterValidation,
@@ -294,6 +324,20 @@ func IncorrectReservedValues(param *v3.Parameter, ef string, sch *base.Schema) *
294324
}
295325
}
296326

327+
func InvalidHeaderParamInteger(param *v3.Parameter, ef string, sch *base.Schema) *ValidationError {
328+
return &ValidationError{
329+
ValidationType: helpers.ParameterValidation,
330+
ValidationSubType: helpers.ParameterValidationHeader,
331+
Message: fmt.Sprintf("Header parameter '%s' is not a valid integer", param.Name),
332+
Reason: fmt.Sprintf("The header parameter '%s' is defined as being a integer, "+
333+
"however the value '%s' is not a valid integer", param.Name, ef),
334+
SpecLine: param.GoLow().Schema.KeyNode.Line,
335+
SpecCol: param.GoLow().Schema.KeyNode.Column,
336+
Context: sch,
337+
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, ef),
338+
}
339+
}
340+
297341
func InvalidHeaderParamNumber(param *v3.Parameter, ef string, sch *base.Schema) *ValidationError {
298342
return &ValidationError{
299343
ValidationType: helpers.ParameterValidation,
@@ -434,6 +478,20 @@ func IncorrectPathParamEnum(param *v3.Parameter, ef string, sch *base.Schema) *V
434478
}
435479
}
436480

481+
func IncorrectPathParamInteger(param *v3.Parameter, item string, sch *base.Schema) *ValidationError {
482+
return &ValidationError{
483+
ValidationType: helpers.ParameterValidation,
484+
ValidationSubType: helpers.ParameterValidationPath,
485+
Message: fmt.Sprintf("Path parameter '%s' is not a valid integer", param.Name),
486+
Reason: fmt.Sprintf("The path parameter '%s' is defined as being a integer, "+
487+
"however the value '%s' is not a valid integer", param.Name, item),
488+
SpecLine: param.GoLow().Schema.KeyNode.Line,
489+
SpecCol: param.GoLow().Schema.KeyNode.Column,
490+
Context: sch,
491+
HowToFix: fmt.Sprintf(HowToFixParamInvalidInteger, item),
492+
}
493+
}
494+
437495
func IncorrectPathParamNumber(param *v3.Parameter, item string, sch *base.Schema) *ValidationError {
438496
return &ValidationError{
439497
ValidationType: helpers.ParameterValidation,

errors/parameters_howtofix.go

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package errors
66
const (
77
HowToFixReservedValues string = "parameter values need to URL Encoded to ensure reserved " +
88
"values are correctly encoded, for example: '%s'"
9+
HowToFixParamInvalidInteger string = "Convert the value '%s' into an integer"
910
HowToFixParamInvalidNumber string = "Convert the value '%s' into a number"
1011
HowToFixParamInvalidString string = "Convert the value '%s' into a string (cannot start with a number, or be a floating point)"
1112
HowToFixParamInvalidBoolean string = "Convert the value '%s' into a true/false value"

parameters/header_parameters.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,28 @@ func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request,
5959

6060
for _, ty := range pType {
6161
switch ty {
62-
case helpers.Integer, helpers.Number:
62+
case helpers.Integer:
63+
if _, err := strconv.ParseInt(param, 10, 64); err != nil {
64+
validationErrors = append(validationErrors,
65+
errors.InvalidHeaderParamInteger(p, strings.ToLower(param), sch))
66+
break
67+
}
68+
// check if the param is within the enum
69+
if sch.Enum != nil {
70+
matchFound := false
71+
for _, enumVal := range sch.Enum {
72+
if strings.TrimSpace(param) == fmt.Sprint(enumVal.Value) {
73+
matchFound = true
74+
break
75+
}
76+
}
77+
if !matchFound {
78+
validationErrors = append(validationErrors,
79+
errors.IncorrectCookieParamEnum(p, strings.ToLower(param), sch))
80+
}
81+
}
82+
83+
case helpers.Number:
6384
if _, err := strconv.ParseFloat(param, 64); err != nil {
6485
validationErrors = append(validationErrors,
6586
errors.InvalidHeaderParamNumber(p, strings.ToLower(param), sch))

parameters/header_parameters_test.go

+118-5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,32 @@ paths:
7171
assert.Equal(t, "", errors[0].SpecPath)
7272
}
7373

74+
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeInteger(t *testing.T) {
75+
spec := `openapi: 3.1.0
76+
paths:
77+
/vending/drinks:
78+
get:
79+
parameters:
80+
- name: coffeeCups
81+
in: header
82+
required: true
83+
schema:
84+
type: integer`
85+
86+
doc, _ := libopenapi.NewDocument([]byte(spec))
87+
m, _ := doc.BuildV3Model()
88+
v := NewParameterValidator(&m.Model)
89+
90+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil)
91+
request.Header.Set("coffeecups", "two") // headers are case-insensitive
92+
93+
valid, errors := v.ValidateHeaderParams(request)
94+
95+
assert.False(t, valid)
96+
assert.Equal(t, 1, len(errors))
97+
assert.Equal(t, "Header parameter 'coffeeCups' is not a valid integer", errors[0].Message)
98+
}
99+
74100
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeNumber(t *testing.T) {
75101
spec := `openapi: 3.1.0
76102
paths:
@@ -154,7 +180,7 @@ paths:
154180
assert.Equal(t, "Header parameter 'coffeeCups' cannot be decoded", errors[0].Message)
155181
}
156182

157-
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectNumber(t *testing.T) {
183+
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectInteger(t *testing.T) {
158184
spec := `openapi: 3.1.0
159185
paths:
160186
/vending/drinks:
@@ -167,7 +193,7 @@ paths:
167193
type: object
168194
properties:
169195
milk:
170-
type: number
196+
type: integer
171197
sugar:
172198
type: boolean`
173199

@@ -182,10 +208,10 @@ paths:
182208

183209
assert.False(t, valid)
184210
assert.Equal(t, 1, len(errors))
185-
assert.Equal(t, "got boolean, want number", errors[0].SchemaValidationErrors[0].Reason)
211+
assert.Equal(t, "got boolean, want integer", errors[0].SchemaValidationErrors[0].Reason)
186212
}
187213

188-
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectBoolean(t *testing.T) {
214+
func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectNumber(t *testing.T) {
189215
spec := `openapi: 3.1.0
190216
paths:
191217
/vending/drinks:
@@ -308,7 +334,7 @@ paths:
308334
assert.Len(t, errors, 0)
309335
}
310336

311-
func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeObject(t *testing.T) {
337+
func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeObjectNumber(t *testing.T) {
312338
spec := `openapi: 3.1.0
313339
paths:
314340
/vending/drinks:
@@ -340,6 +366,38 @@ paths:
340366
assert.Equal(t, "got boolean, want number", errors[0].SchemaValidationErrors[0].Reason)
341367
}
342368

369+
func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeObjectInteger(t *testing.T) {
370+
spec := `openapi: 3.1.0
371+
paths:
372+
/vending/drinks:
373+
get:
374+
parameters:
375+
- name: coffeeCups
376+
in: header
377+
required: true
378+
explode: true
379+
schema:
380+
type: object
381+
properties:
382+
milk:
383+
type: integer
384+
sugar:
385+
type: boolean`
386+
387+
doc, _ := libopenapi.NewDocument([]byte(spec))
388+
m, _ := doc.BuildV3Model()
389+
v := NewParameterValidator(&m.Model)
390+
391+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil)
392+
request.Header.Set("coffeecups", "milk=true,sugar=true") // default encoding.
393+
394+
valid, errors := v.ValidateHeaderParams(request)
395+
396+
assert.False(t, valid)
397+
assert.Len(t, errors, 1)
398+
assert.Equal(t, "got boolean, want integer", errors[0].SchemaValidationErrors[0].Reason)
399+
}
400+
343401
func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeArrayString(t *testing.T) {
344402
spec := `openapi: 3.1.0
345403
paths:
@@ -385,6 +443,33 @@ paths:
385443
m, _ := doc.BuildV3Model()
386444
v := NewParameterValidator(&m.Model)
387445

446+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil)
447+
request.Header.Set("coffeecups", "1.22,2.33,3.44,4.55,5.66") // default encoding.
448+
449+
valid, errors := v.ValidateHeaderParams(request)
450+
451+
assert.True(t, valid)
452+
assert.Len(t, errors, 0)
453+
}
454+
455+
func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeArrayInteger(t *testing.T) {
456+
spec := `openapi: 3.1.0
457+
paths:
458+
/vending/drinks:
459+
get:
460+
parameters:
461+
- name: coffeeCups
462+
in: header
463+
required: true
464+
schema:
465+
type: array
466+
items:
467+
type: integer`
468+
469+
doc, _ := libopenapi.NewDocument([]byte(spec))
470+
m, _ := doc.BuildV3Model()
471+
v := NewParameterValidator(&m.Model)
472+
388473
request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil)
389474
request.Header.Set("coffeecups", "1,2,3,4,5") // default encoding.
390475

@@ -557,6 +642,34 @@ paths:
557642

558643
func TestNewValidator_HeaderParamNumberInvalidEnum(t *testing.T) {
559644
spec := `openapi: 3.1.0
645+
paths:
646+
/vending/drinks:
647+
get:
648+
parameters:
649+
- name: coffeeCups
650+
in: header
651+
required: true
652+
schema:
653+
type: number
654+
enum: [1.2,2.3,99.8]`
655+
656+
doc, _ := libopenapi.NewDocument([]byte(spec))
657+
m, _ := doc.BuildV3Model()
658+
v := NewParameterValidator(&m.Model)
659+
660+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil)
661+
request.Header.Set("coffeecups", "1200.3") // that's a lot of cups dude, we only have one dishwasher.
662+
663+
valid, errors := v.ValidateHeaderParams(request)
664+
665+
assert.False(t, valid)
666+
assert.Len(t, errors, 1)
667+
assert.Equal(t, "Instead of '1200.3', "+
668+
"use one of the allowed values: '1.2, 2.3, 99.8'", errors[0].HowToFix)
669+
}
670+
671+
func TestNewValidator_HeaderParamIntegerInvalidEnum(t *testing.T) {
672+
spec := `openapi: 3.1.0
560673
paths:
561674
/vending/drinks:
562675
get:

parameters/path_parameters.go

+49-3
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,29 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
141141
helpers.ParameterValidationPath,
142142
)...)
143143

144-
case helpers.Integer, helpers.Number:
144+
case helpers.Integer:
145+
// simple use case is already handled in find param.
146+
rawParamValue, paramValueParsed, err := v.resolveInteger(sch, p, isLabel, isMatrix, paramValue)
147+
if err != nil {
148+
validationErrors = append(validationErrors, err...)
149+
break
150+
}
151+
// check if the param is within the enum
152+
if sch.Enum != nil {
153+
enumCheck(rawParamValue)
154+
break
155+
}
156+
validationErrors = append(validationErrors, ValidateSingleParameterSchema(
157+
sch,
158+
paramValueParsed,
159+
"Path parameter",
160+
"The path parameter",
161+
p.Name,
162+
helpers.ParameterValidation,
163+
helpers.ParameterValidationPath,
164+
)...)
165+
166+
case helpers.Number:
145167
// simple use case is already handled in find param.
146168
rawParamValue, paramValueParsed, err := v.resolveNumber(sch, p, isLabel, isMatrix, paramValue)
147169
if err != nil {
@@ -165,7 +187,7 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
165187

166188
case helpers.Boolean:
167189
if isLabel && p.Style == helpers.LabelStyle {
168-
if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil {
190+
if _, err := strconv.ParseBool(paramValue[1:]); err != nil {
169191
validationErrors = append(validationErrors,
170192
errors.IncorrectPathParamBool(p, paramValue[1:], sch))
171193
}
@@ -315,7 +337,31 @@ func (v *paramValidator) resolveNumber(sch *base.Schema, p *v3.Parameter, isLabe
315337
}
316338
paramValueParsed, err := strconv.ParseFloat(paramValue, 64)
317339
if err != nil {
318-
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamNumber(p, paramValue[1:], sch)}
340+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamNumber(p, paramValue, sch)}
341+
}
342+
return paramValue, paramValueParsed, nil
343+
}
344+
345+
func (v *paramValidator) resolveInteger(sch *base.Schema, p *v3.Parameter, isLabel bool, isMatrix bool, paramValue string) (string, int64, []*errors.ValidationError) {
346+
if isLabel && p.Style == helpers.LabelStyle {
347+
paramValueParsed, err := strconv.ParseInt(paramValue[1:], 10, 64)
348+
if err != nil {
349+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamInteger(p, paramValue[1:], sch)}
350+
}
351+
return paramValue[1:], paramValueParsed, nil
352+
}
353+
if isMatrix && p.Style == helpers.MatrixStyle {
354+
// strip off the colon and the parameter name
355+
paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1)
356+
paramValueParsed, err := strconv.ParseInt(paramValue, 10, 64)
357+
if err != nil {
358+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamInteger(p, paramValue[1:], sch)}
359+
}
360+
return paramValue, paramValueParsed, nil
361+
}
362+
paramValueParsed, err := strconv.ParseInt(paramValue, 10, 64)
363+
if err != nil {
364+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamInteger(p, paramValue, sch)}
319365
}
320366
return paramValue, paramValueParsed, nil
321367
}

0 commit comments

Comments
 (0)