Skip to content

Commit 3397867

Browse files
emilien-pugetdaveshanley
authored andcommitted
string and number path param validation
1 parent 13c8231 commit 3397867

File tree

6 files changed

+252
-127
lines changed

6 files changed

+252
-127
lines changed

parameters/path_parameters.go

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ package parameters
55

66
import (
77
"fmt"
8+
"net/http"
9+
"strconv"
10+
"strings"
11+
812
"github.com/pb33f/libopenapi-validator/errors"
913
"github.com/pb33f/libopenapi-validator/helpers"
1014
"github.com/pb33f/libopenapi-validator/paths"
15+
"github.com/pb33f/libopenapi/datamodel/high/base"
1116
"github.com/pb33f/libopenapi/datamodel/high/v3"
12-
"net/http"
13-
"strconv"
14-
"strings"
1517
)
1618

1719
func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError) {
@@ -41,7 +43,7 @@ func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*err
4143
submittedSegments := strings.Split(request.URL.Path, helpers.Slash)
4244
pathSegments := strings.Split(foundPath, helpers.Slash)
4345

44-
//var paramTemplate string
46+
// var paramTemplate string
4547
for x := range pathSegments {
4648
if pathSegments[x] == "" { // skip empty segments
4749
continue
@@ -50,13 +52,13 @@ func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*err
5052
if i > -1 {
5153
isMatrix := false
5254
isLabel := false
53-
//isExplode := false
55+
// isExplode := false
5456
isSimple := true
5557
paramTemplate := pathSegments[x][i+1 : len(pathSegments[x])-1]
5658
paramName := paramTemplate
5759
// check for an asterisk on the end of the parameter (explode)
5860
if strings.HasSuffix(paramTemplate, helpers.Asterisk) {
59-
//isExplode = true
61+
// isExplode = true
6062
paramName = paramTemplate[:len(paramTemplate)-1]
6163
}
6264
if strings.HasPrefix(paramTemplate, helpers.Period) {
@@ -116,41 +118,40 @@ func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*err
116118
// check if the param is within the enum
117119
if sch.Enum != nil {
118120
enumCheck(paramValue)
121+
break
119122
}
123+
validationErrors = append(validationErrors,
124+
ValidateSingleParameterSchema(
125+
sch,
126+
paramValue,
127+
"Path parameter",
128+
"The path parameter",
129+
p.Name,
130+
helpers.ParameterValidation,
131+
helpers.ParameterValidationPath,
132+
)...)
120133

121134
case helpers.Integer, helpers.Number:
122135
// simple use case is already handled in find param.
123-
if isLabel && p.Style == helpers.LabelStyle {
124-
if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil {
125-
validationErrors = append(validationErrors,
126-
errors.IncorrectPathParamNumber(p, paramValue[1:], sch))
127-
break
128-
}
129-
// check if the param is within the enum
130-
if sch.Enum != nil {
131-
enumCheck(paramValue[1:])
132-
break
133-
}
134-
}
135-
if isMatrix && p.Style == helpers.MatrixStyle {
136-
// strip off the colon and the parameter name
137-
paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1)
138-
if _, err := strconv.ParseFloat(paramValue, 64); err != nil {
139-
validationErrors = append(validationErrors,
140-
errors.IncorrectPathParamNumber(p, paramValue[1:], sch))
141-
break
142-
}
143-
// check if the param is within the enum
144-
if sch.Enum != nil {
145-
enumCheck(paramValue)
146-
break
147-
}
136+
rawParamValue, paramValueParsed, err := v.resolveNumber(sch, p, isLabel, isMatrix, paramValue)
137+
if err != nil {
138+
validationErrors = append(validationErrors, err...)
139+
break
148140
}
149141
// check if the param is within the enum
150142
if sch.Enum != nil {
151-
enumCheck(paramValue)
143+
enumCheck(rawParamValue)
152144
break
153145
}
146+
validationErrors = append(validationErrors, ValidateSingleParameterSchema(
147+
sch,
148+
paramValueParsed,
149+
"Path parameter",
150+
"The path parameter",
151+
p.Name,
152+
helpers.ParameterValidation,
153+
helpers.ParameterValidationPath,
154+
)...)
154155

155156
case helpers.Boolean:
156157
if isLabel && p.Style == helpers.LabelStyle {
@@ -280,3 +281,27 @@ func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*err
280281
}
281282
return true, nil
282283
}
284+
285+
func (v *paramValidator) resolveNumber(sch *base.Schema, p *v3.Parameter, isLabel bool, isMatrix bool, paramValue string) (string, float64, []*errors.ValidationError) {
286+
if isLabel && p.Style == helpers.LabelStyle {
287+
paramValueParsed, err := strconv.ParseFloat(paramValue[1:], 64)
288+
if err != nil {
289+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamNumber(p, paramValue[1:], sch)}
290+
}
291+
return paramValue[1:], paramValueParsed, nil
292+
}
293+
if isMatrix && p.Style == helpers.MatrixStyle {
294+
// strip off the colon and the parameter name
295+
paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1)
296+
paramValueParsed, err := strconv.ParseFloat(paramValue, 64)
297+
if err != nil {
298+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamNumber(p, paramValue[1:], sch)}
299+
}
300+
return paramValue, paramValueParsed, nil
301+
}
302+
paramValueParsed, err := strconv.ParseFloat(paramValue, 64)
303+
if err != nil {
304+
return "", 0, []*errors.ValidationError{errors.IncorrectPathParamNumber(p, paramValue[1:], sch)}
305+
}
306+
return paramValue, paramValueParsed, nil
307+
}

parameters/path_parameters_test.go

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
package parameters
55

66
import (
7+
"net/http"
8+
"testing"
9+
710
"github.com/pb33f/libopenapi"
811
"github.com/pb33f/libopenapi-validator/paths"
912
"github.com/stretchr/testify/assert"
10-
"net/http"
11-
"testing"
13+
"github.com/stretchr/testify/require"
1214
)
1315

1416
func TestNewValidator_SimpleArrayEncodedPath(t *testing.T) {
@@ -280,7 +282,64 @@ paths:
280282

281283
assert.False(t, valid)
282284
assert.Len(t, errors, 1)
283-
assert.Equal(t, "GET Path '/burgers/hello/locate' not found", errors[0].Message)
285+
assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message)
286+
}
287+
288+
func TestNewValidator_SimpleEncodedPath_IntegerViolation(t *testing.T) {
289+
290+
spec := `openapi: 3.1.0
291+
paths:
292+
/burgers/{burgerId}/locate:
293+
parameters:
294+
- name: burgerId
295+
in: path
296+
schema:
297+
type: integer
298+
minimum: 10
299+
get:
300+
operationId: locateBurgers`
301+
302+
doc, err := libopenapi.NewDocument([]byte(spec))
303+
require.NoError(t, err)
304+
m, _ := doc.BuildV3Model()
305+
306+
v := NewParameterValidator(&m.Model)
307+
308+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/1/locate", nil)
309+
valid, errors := v.ValidatePathParams(request)
310+
311+
assert.False(t, valid)
312+
assert.Len(t, errors, 1)
313+
assert.Equal(t, "Path parameter 'burgerId' failed to validate", errors[0].Message)
314+
assert.Len(t, errors[0].SchemaValidationErrors, 1)
315+
assert.Equal(t, "Reason: must be >= 10 but found 1, Location: /minimum", errors[0].SchemaValidationErrors[0].Error())
316+
}
317+
318+
func TestNewValidator_SimpleEncodedPath_Integer(t *testing.T) {
319+
320+
spec := `openapi: 3.1.0
321+
paths:
322+
/burgers/{burgerId}/locate:
323+
parameters:
324+
- name: burgerId
325+
in: path
326+
schema:
327+
type: integer
328+
minimum: 10
329+
get:
330+
operationId: locateBurgers`
331+
332+
doc, err := libopenapi.NewDocument([]byte(spec))
333+
require.NoError(t, err)
334+
m, _ := doc.BuildV3Model()
335+
336+
v := NewParameterValidator(&m.Model)
337+
338+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/14/locate", nil)
339+
valid, errors := v.ValidatePathParams(request)
340+
341+
assert.True(t, valid)
342+
assert.Nil(t, errors)
284343
}
285344

286345
func TestNewValidator_SimpleEncodedPath_InvalidBoolean(t *testing.T) {
@@ -338,6 +397,37 @@ paths:
338397
assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message)
339398
}
340399

400+
func TestNewValidator_LabelEncodedPath_IntegerViolation(t *testing.T) {
401+
402+
spec := `openapi: 3.1.0
403+
paths:
404+
/burgers/{.burgerId}/locate:
405+
parameters:
406+
- name: burgerId
407+
in: path
408+
style: label
409+
schema:
410+
type: integer
411+
minimum: 10
412+
get:
413+
operationId: locateBurgers`
414+
415+
doc, _ := libopenapi.NewDocument([]byte(spec))
416+
417+
m, _ := doc.BuildV3Model()
418+
419+
v := NewParameterValidator(&m.Model)
420+
421+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3/locate", nil)
422+
valid, errors := v.ValidatePathParams(request)
423+
424+
assert.False(t, valid)
425+
assert.Len(t, errors, 1)
426+
assert.Equal(t, "Path parameter 'burgerId' failed to validate", errors[0].Message)
427+
assert.Len(t, errors[0].SchemaValidationErrors, 1)
428+
assert.Equal(t, "Reason: must be >= 10 but found 3, Location: /minimum", errors[0].SchemaValidationErrors[0].Error())
429+
}
430+
341431
func TestNewValidator_LabelEncodedPath_InvalidBoolean(t *testing.T) {
342432

343433
spec := `openapi: 3.1.0
@@ -684,6 +774,37 @@ paths:
684774
assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message)
685775
}
686776

777+
func TestNewValidator_MatrixEncodedPath_PrimitiveNumberViolation(t *testing.T) {
778+
779+
spec := `openapi: 3.1.0
780+
paths:
781+
/burgers/{;burgerId}/locate:
782+
parameters:
783+
- name: burgerId
784+
in: path
785+
style: matrix
786+
schema:
787+
type: integer
788+
minimum: 5
789+
get:
790+
operationId: locateBurgers`
791+
792+
doc, _ := libopenapi.NewDocument([]byte(spec))
793+
794+
m, _ := doc.BuildV3Model()
795+
796+
v := NewParameterValidator(&m.Model)
797+
798+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=3/locate", nil)
799+
valid, errors := v.ValidatePathParams(request)
800+
801+
assert.False(t, valid)
802+
assert.Len(t, errors, 1)
803+
assert.Equal(t, "Path parameter 'burgerId' failed to validate", errors[0].Message)
804+
assert.Len(t, errors[0].SchemaValidationErrors, 1)
805+
assert.Equal(t, "Reason: must be >= 5 but found 3, Location: /minimum", errors[0].SchemaValidationErrors[0].Error())
806+
}
807+
687808
func TestNewValidator_MatrixEncodedPath_ValidPrimitiveBoolean(t *testing.T) {
688809

689810
spec := `openapi: 3.1.0
@@ -1075,6 +1196,36 @@ paths:
10751196

10761197
}
10771198

1199+
func TestNewValidator_PathParamStringViolation(t *testing.T) {
1200+
1201+
spec := `openapi: 3.1.0
1202+
paths:
1203+
/burgers/{burgerId}/locate:
1204+
parameters:
1205+
- name: burgerId
1206+
in: path
1207+
schema:
1208+
type: string
1209+
minLength: 4
1210+
get:
1211+
operationId: locateBurgers`
1212+
1213+
doc, _ := libopenapi.NewDocument([]byte(spec))
1214+
1215+
m, _ := doc.BuildV3Model()
1216+
1217+
v := NewParameterValidator(&m.Model)
1218+
1219+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/big/locate", nil)
1220+
valid, errors := v.ValidatePathParams(request)
1221+
1222+
assert.False(t, valid)
1223+
assert.Len(t, errors, 1)
1224+
assert.Equal(t, "Path parameter 'burgerId' failed to validate", errors[0].Message)
1225+
assert.Len(t, errors[0].SchemaValidationErrors, 1)
1226+
assert.Equal(t, "Reason: length must be >= 4, but got 3, Location: /minLength", errors[0].SchemaValidationErrors[0].Error())
1227+
}
1228+
10781229
func TestNewValidator_PathParamIntegerEnumValid(t *testing.T) {
10791230

10801231
spec := `openapi: 3.1.0

0 commit comments

Comments
 (0)