Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions openapi3/issue382_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package openapi3

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestOverridingGlobalParametersValidation(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/Test_param_override.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
2 changes: 1 addition & 1 deletion openapi3/parameter_issue223_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,5 @@ components:
doc, err := NewLoader().LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters`)
require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters (missing: [petId])`)
}
77 changes: 57 additions & 20 deletions openapi3/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,60 @@ func (value Paths) Validate(ctx context.Context) error {
pathItem = value[path]
}

normalizedPath, pathParamsCount := normalizeTemplatedPath(path)
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
}
normalizedPaths[path] = path

var globalCount uint
var commonParams []string
for _, parameterRef := range pathItem.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
globalCount++
commonParams = append(commonParams, parameter.Name)
}
}
}
for method, operation := range pathItem.Operations() {
var count uint
var setParams []string
for _, parameterRef := range operation.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
count++
setParams = append(setParams, parameter.Name)
}
}
}
if count+globalCount != pathParamsCount {
return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path)
if expected := len(setParams) + len(commonParams); expected != len(varsInPath) {
expected -= len(varsInPath)
if expected < 0 {
expected *= -1
}
missing := make(map[string]struct{}, expected)
definedParams := append(setParams, commonParams...)
for _, name := range definedParams {
if _, ok := varsInPath[name]; !ok {
missing[name] = struct{}{}
}
}
for name := range varsInPath {
got := false
for _, othername := range definedParams {
if othername == name {
got = true
break
}
}
if !got {
missing[name] = struct{}{}
}
}
if len(missing) != 0 {
missings := make([]string, 0, len(missing))
for name := range missing {
missings = append(missings, name)
}
return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings)
}
}
}

Expand Down Expand Up @@ -75,53 +104,61 @@ func (paths Paths) Find(key string) *PathItem {
return pathItem
}

normalizedPath, expected := normalizeTemplatedPath(key)
normalizedPath, expected, _ := normalizeTemplatedPath(key)
for path, pathItem := range paths {
pathNormalized, got := normalizeTemplatedPath(path)
pathNormalized, got, _ := normalizeTemplatedPath(path)
if got == expected && pathNormalized == normalizedPath {
return pathItem
}
}
return nil
}

func normalizeTemplatedPath(path string) (string, uint) {
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
if strings.IndexByte(path, '{') < 0 {
return path, 0
return path, 0, nil
}

var buf strings.Builder
buf.Grow(len(path))
var buffTpl strings.Builder
buffTpl.Grow(len(path))

var (
cc rune
count uint
isVariable bool
vars = make(map[string]struct{})
buffVar strings.Builder
)
for i, c := range path {
if isVariable {
if c == '}' {
// End path variables
// End path variable
isVariable = false

vars[buffVar.String()] = struct{}{}
buffVar = strings.Builder{}

// First append possible '*' before this character
// The character '}' will be appended
if i > 0 && cc == '*' {
buf.WriteRune(cc)
buffTpl.WriteRune(cc)
}
isVariable = false
} else {
// Skip this character
buffVar.WriteRune(c)
continue
}

} else if c == '{' {
// Begin path variable
// The character '{' will be appended
isVariable = true

// The character '{' will be appended
count++
}

// Append the character
buf.WriteRune(c)
buffTpl.WriteRune(c)
cc = c
}
return buf.String(), count
return buffTpl.String(), count, vars
}
40 changes: 40 additions & 0 deletions openapi3/testdata/Test_param_override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
openapi: 3.0.0
info:
title: customer
version: '1.0'
servers:
- url: 'httpbin.kwaf-demo.test'
paths:
'/customers/{customer_id}':
parameters:
- schema:
type: integer
name: customer_id
in: path
required: true
get:
parameters:
- schema:
type: integer
maximum: 100
name: customer_id
in: path
required: true
summary: customer
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
customer_id:
type: integer
customer_name:
type: string
operationId: get-customers-customer_id
description: Retrieve a specific customer by ID
components:
schemas: {}