Skip to content

Commit bde5325

Browse files
authored
reproduce and fix issue #382 (#383)
1 parent 6b4444b commit bde5325

File tree

4 files changed

+113
-21
lines changed

4 files changed

+113
-21
lines changed

openapi3/issue382_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestOverridingGlobalParametersValidation(t *testing.T) {
10+
loader := NewLoader()
11+
doc, err := loader.LoadFromFile("testdata/Test_param_override.yml")
12+
require.NoError(t, err)
13+
err = doc.Validate(loader.Context)
14+
require.NoError(t, err)
15+
}

openapi3/parameter_issue223_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,5 @@ components:
112112
doc, err := NewLoader().LoadFromData([]byte(spec))
113113
require.NoError(t, err)
114114
err = doc.Validate(context.Background())
115-
require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters`)
115+
require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters (missing: [petId])`)
116116
}

openapi3/paths.go

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,60 @@ func (value Paths) Validate(ctx context.Context) error {
2121
pathItem = value[path]
2222
}
2323

24-
normalizedPath, pathParamsCount := normalizeTemplatedPath(path)
24+
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
2525
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
2626
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
2727
}
2828
normalizedPaths[path] = path
2929

30-
var globalCount uint
30+
var commonParams []string
3131
for _, parameterRef := range pathItem.Parameters {
3232
if parameterRef != nil {
3333
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
34-
globalCount++
34+
commonParams = append(commonParams, parameter.Name)
3535
}
3636
}
3737
}
3838
for method, operation := range pathItem.Operations() {
39-
var count uint
39+
var setParams []string
4040
for _, parameterRef := range operation.Parameters {
4141
if parameterRef != nil {
4242
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
43-
count++
43+
setParams = append(setParams, parameter.Name)
4444
}
4545
}
4646
}
47-
if count+globalCount != pathParamsCount {
48-
return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path)
47+
if expected := len(setParams) + len(commonParams); expected != len(varsInPath) {
48+
expected -= len(varsInPath)
49+
if expected < 0 {
50+
expected *= -1
51+
}
52+
missing := make(map[string]struct{}, expected)
53+
definedParams := append(setParams, commonParams...)
54+
for _, name := range definedParams {
55+
if _, ok := varsInPath[name]; !ok {
56+
missing[name] = struct{}{}
57+
}
58+
}
59+
for name := range varsInPath {
60+
got := false
61+
for _, othername := range definedParams {
62+
if othername == name {
63+
got = true
64+
break
65+
}
66+
}
67+
if !got {
68+
missing[name] = struct{}{}
69+
}
70+
}
71+
if len(missing) != 0 {
72+
missings := make([]string, 0, len(missing))
73+
for name := range missing {
74+
missings = append(missings, name)
75+
}
76+
return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings)
77+
}
4978
}
5079
}
5180

@@ -75,53 +104,61 @@ func (paths Paths) Find(key string) *PathItem {
75104
return pathItem
76105
}
77106

78-
normalizedPath, expected := normalizeTemplatedPath(key)
107+
normalizedPath, expected, _ := normalizeTemplatedPath(key)
79108
for path, pathItem := range paths {
80-
pathNormalized, got := normalizeTemplatedPath(path)
109+
pathNormalized, got, _ := normalizeTemplatedPath(path)
81110
if got == expected && pathNormalized == normalizedPath {
82111
return pathItem
83112
}
84113
}
85114
return nil
86115
}
87116

88-
func normalizeTemplatedPath(path string) (string, uint) {
117+
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
89118
if strings.IndexByte(path, '{') < 0 {
90-
return path, 0
119+
return path, 0, nil
91120
}
92121

93-
var buf strings.Builder
94-
buf.Grow(len(path))
122+
var buffTpl strings.Builder
123+
buffTpl.Grow(len(path))
95124

96125
var (
97126
cc rune
98127
count uint
99128
isVariable bool
129+
vars = make(map[string]struct{})
130+
buffVar strings.Builder
100131
)
101132
for i, c := range path {
102133
if isVariable {
103134
if c == '}' {
104-
// End path variables
135+
// End path variable
136+
isVariable = false
137+
138+
vars[buffVar.String()] = struct{}{}
139+
buffVar = strings.Builder{}
140+
105141
// First append possible '*' before this character
106142
// The character '}' will be appended
107143
if i > 0 && cc == '*' {
108-
buf.WriteRune(cc)
144+
buffTpl.WriteRune(cc)
109145
}
110-
isVariable = false
111146
} else {
112-
// Skip this character
147+
buffVar.WriteRune(c)
113148
continue
114149
}
150+
115151
} else if c == '{' {
116152
// Begin path variable
117-
// The character '{' will be appended
118153
isVariable = true
154+
155+
// The character '{' will be appended
119156
count++
120157
}
121158

122159
// Append the character
123-
buf.WriteRune(c)
160+
buffTpl.WriteRune(c)
124161
cc = c
125162
}
126-
return buf.String(), count
163+
return buffTpl.String(), count, vars
127164
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
openapi: 3.0.0
2+
info:
3+
title: customer
4+
version: '1.0'
5+
servers:
6+
- url: 'httpbin.kwaf-demo.test'
7+
paths:
8+
'/customers/{customer_id}':
9+
parameters:
10+
- schema:
11+
type: integer
12+
name: customer_id
13+
in: path
14+
required: true
15+
get:
16+
parameters:
17+
- schema:
18+
type: integer
19+
maximum: 100
20+
name: customer_id
21+
in: path
22+
required: true
23+
summary: customer
24+
tags: []
25+
responses:
26+
'200':
27+
description: OK
28+
content:
29+
application/json:
30+
schema:
31+
type: object
32+
properties:
33+
customer_id:
34+
type: integer
35+
customer_name:
36+
type: string
37+
operationId: get-customers-customer_id
38+
description: Retrieve a specific customer by ID
39+
components:
40+
schemas: {}

0 commit comments

Comments
 (0)