Repository: getkin/kin-openapi
Pull request #173 (2020-02-12 10:07:50)

Change type of scopes in openapi2 to map[string]string
According to the spec https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#scopesObject the scopes are map, not a list.

```
{
  "write:pets": "modify pets in your account",
  "read:pets": "read your pets"
}
```

fenollp: Thanks for the fix. Could you add a test that for example reads an openapiv2 doc and ensures the scopes are parsed properly?

Patch:
diff --git a/README.md b/README.md
index cc528e6..fd3db0f 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Here's some projects that depend on _kin-openapi_:
   * [github.com/getkin/kin](https://github.com/getkin/kin) - "A configurable backend"
   * [github.com/danielgtaylor/apisprout](https://github.com/danielgtaylor/apisprout) - "Lightweight, blazing fast, cross-platform OpenAPI 3 mock server with validation"
   * [github.com/deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) - Generate Go server boilerplate from an OpenAPI 3 spec
-  * [github.com/dunglas/vulcain](https//github.com/dunglas/vulcain) - "Use HTTP/2 Server Push to create fast and idiomatic client-driven REST APIs"
+  * [github.com/dunglas/vulcain](https://github.com/dunglas/vulcain) - "Use HTTP/2 Server Push to create fast and idiomatic client-driven REST APIs"
   * (Feel free to add your project by [creating an issue](https://github.com/getkin/kin-openapi/issues/new) or a pull request)
 
 ## Alternative projects
@@ -193,4 +193,4 @@ func main() {
 func arrayUniqueItemsChecker(items []interface{}) bool {
 	// Check the uniqueness of the input slice(array in JSON)
 }
-```
\ No newline at end of file
+```
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index 86e7370..3da171d 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -9,6 +9,7 @@ package openapi2
 
 import (
 	"fmt"
+	"net/http"
 
 	"github.com/getkin/kin-openapi/openapi3"
 )
@@ -57,44 +58,44 @@ type PathItem struct {
 func (pathItem *PathItem) Operations() map[string]*Operation {
 	operations := make(map[string]*Operation, 8)
 	if v := pathItem.Delete; v != nil {
-		operations["DELETE"] = v
+		operations[http.MethodDelete] = v
 	}
 	if v := pathItem.Get; v != nil {
-		operations["GET"] = v
+		operations[http.MethodGet] = v
 	}
 	if v := pathItem.Head; v != nil {
-		operations["HEAD"] = v
+		operations[http.MethodHead] = v
 	}
 	if v := pathItem.Options; v != nil {
-		operations["OPTIONS"] = v
+		operations[http.MethodOptions] = v
 	}
 	if v := pathItem.Patch; v != nil {
-		operations["PATCH"] = v
+		operations[http.MethodPatch] = v
 	}
 	if v := pathItem.Post; v != nil {
-		operations["POST"] = v
+		operations[http.MethodPost] = v
 	}
 	if v := pathItem.Put; v != nil {
-		operations["PUT"] = v
+		operations[http.MethodPut] = v
 	}
 	return operations
 }
 
 func (pathItem *PathItem) GetOperation(method string) *Operation {
 	switch method {
-	case "DELETE":
+	case http.MethodDelete:
 		return pathItem.Delete
-	case "GET":
+	case http.MethodGet:
 		return pathItem.Get
-	case "HEAD":
+	case http.MethodHead:
 		return pathItem.Head
-	case "OPTIONS":
+	case http.MethodOptions:
 		return pathItem.Options
-	case "PATCH":
+	case http.MethodPatch:
 		return pathItem.Patch
-	case "POST":
+	case http.MethodPost:
 		return pathItem.Post
-	case "PUT":
+	case http.MethodPut:
 		return pathItem.Put
 	default:
 		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
@@ -103,19 +104,19 @@ func (pathItem *PathItem) GetOperation(method string) *Operation {
 
 func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
 	switch method {
-	case "DELETE":
+	case http.MethodDelete:
 		pathItem.Delete = operation
-	case "GET":
+	case http.MethodGet:
 		pathItem.Get = operation
-	case "HEAD":
+	case http.MethodHead:
 		pathItem.Head = operation
-	case "OPTIONS":
+	case http.MethodOptions:
 		pathItem.Options = operation
-	case "PATCH":
+	case http.MethodPatch:
 		pathItem.Patch = operation
-	case "POST":
+	case http.MethodPost:
 		pathItem.Post = operation
-	case "PUT":
+	case http.MethodPut:
 		pathItem.Put = operation
 	default:
 		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
@@ -178,14 +179,14 @@ type Header struct {
 type SecurityRequirements []map[string][]string
 
 type SecurityScheme struct {
-	Ref              string        `json:"$ref,omitempty"`
-	Description      string        `json:"description,omitempty"`
-	Type             string        `json:"type,omitempty"`
-	In               string        `json:"in,omitempty"`
-	Name             string        `json:"name,omitempty"`
-	Flow             string        `json:"flow,omitempty"`
-	AuthorizationURL string        `json:"authorizationUrl,omitempty"`
-	TokenURL         string        `json:"tokenUrl,omitempty"`
-	Scopes           []string      `json:"scopes,omitempty"`
-	Tags             openapi3.Tags `json:"tags,omitempty"`
+	Ref              string            `json:"$ref,omitempty"`
+	Description      string            `json:"description,omitempty"`
+	Type             string            `json:"type,omitempty"`
+	In               string            `json:"in,omitempty"`
+	Name             string            `json:"name,omitempty"`
+	Flow             string            `json:"flow,omitempty"`
+	AuthorizationURL string            `json:"authorizationUrl,omitempty"`
+	TokenURL         string            `json:"tokenUrl,omitempty"`
+	Scopes           map[string]string `json:"scopes,omitempty"`
+	Tags             openapi3.Tags     `json:"tags,omitempty"`
 }
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index ef480d8..63f9728 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -302,8 +302,8 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 		flows := &openapi3.OAuthFlows{}
 		result.Flows = flows
 		scopesMap := make(map[string]string)
-		for _, scope := range securityScheme.Scopes {
-			scopesMap[scope] = ""
+		for scope, desc := range securityScheme.Scopes {
+			scopesMap[scope] = desc
 		}
 		flow := &openapi3.OAuthFlow{
 			AuthorizationURL: securityScheme.AuthorizationURL,
@@ -643,8 +643,8 @@ func FromV3SecurityScheme(swagger *openapi3.Swagger, ref *openapi3.SecuritySchem
 			} else {
 				return nil, nil
 			}
-			for scope := range flow.Scopes {
-				result.Scopes = append(result.Scopes, scope)
+			for scope, desc := range flow.Scopes {
+				result.Scopes[scope] = desc
 			}
 		}
 	default:
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 36c1c8d..2ed578a 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -3,6 +3,7 @@ package openapi3
 import (
 	"context"
 	"fmt"
+	"net/http"
 
 	"github.com/getkin/kin-openapi/jsoninfo"
 )
@@ -36,54 +37,54 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 func (pathItem *PathItem) Operations() map[string]*Operation {
 	operations := make(map[string]*Operation, 4)
 	if v := pathItem.Connect; v != nil {
-		operations["CONNECT"] = v
+		operations[http.MethodConnect] = v
 	}
 	if v := pathItem.Delete; v != nil {
-		operations["DELETE"] = v
+		operations[http.MethodDelete] = v
 	}
 	if v := pathItem.Get; v != nil {
-		operations["GET"] = v
+		operations[http.MethodGet] = v
 	}
 	if v := pathItem.Head; v != nil {
-		operations["HEAD"] = v
+		operations[http.MethodHead] = v
 	}
 	if v := pathItem.Options; v != nil {
-		operations["OPTIONS"] = v
+		operations[http.MethodOptions] = v
 	}
 	if v := pathItem.Patch; v != nil {
-		operations["PATCH"] = v
+		operations[http.MethodPatch] = v
 	}
 	if v := pathItem.Post; v != nil {
-		operations["POST"] = v
+		operations[http.MethodPost] = v
 	}
 	if v := pathItem.Put; v != nil {
-		operations["PUT"] = v
+		operations[http.MethodPut] = v
 	}
 	if v := pathItem.Trace; v != nil {
-		operations["TRACE"] = v
+		operations[http.MethodTrace] = v
 	}
 	return operations
 }
 
 func (pathItem *PathItem) GetOperation(method string) *Operation {
 	switch method {
-	case "CONNECT":
+	case http.MethodConnect:
 		return pathItem.Connect
-	case "DELETE":
+	case http.MethodDelete:
 		return pathItem.Delete
-	case "GET":
+	case http.MethodGet:
 		return pathItem.Get
-	case "HEAD":
+	case http.MethodHead:
 		return pathItem.Head
-	case "OPTIONS":
+	case http.MethodOptions:
 		return pathItem.Options
-	case "PATCH":
+	case http.MethodPatch:
 		return pathItem.Patch
-	case "POST":
+	case http.MethodPost:
 		return pathItem.Post
-	case "PUT":
+	case http.MethodPut:
 		return pathItem.Put
-	case "TRACE":
+	case http.MethodTrace:
 		return pathItem.Trace
 	default:
 		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
@@ -92,23 +93,23 @@ func (pathItem *PathItem) GetOperation(method string) *Operation {
 
 func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
 	switch method {
-	case "CONNECT":
+	case http.MethodConnect:
 		pathItem.Connect = operation
-	case "DELETE":
+	case http.MethodDelete:
 		pathItem.Delete = operation
-	case "GET":
+	case http.MethodGet:
 		pathItem.Get = operation
-	case "HEAD":
+	case http.MethodHead:
 		pathItem.Head = operation
-	case "OPTIONS":
+	case http.MethodOptions:
 		pathItem.Options = operation
-	case "PATCH":
+	case http.MethodPatch:
 		pathItem.Patch = operation
-	case "POST":
+	case http.MethodPost:
 		pathItem.Post = operation
-	case "PUT":
+	case http.MethodPut:
 		pathItem.Put = operation
-	case "TRACE":
+	case http.MethodTrace:
 		pathItem.Trace = operation
 	default:
 		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
diff --git a/openapi3/paths.go b/openapi3/paths.go
index 3b46b39..d738e9d 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -19,9 +19,6 @@ func (paths Paths) Validate(c context.Context) error {
 		if path == "" || path[0] != '/' {
 			return fmt.Errorf("Path '%v' does not start with '/'", path)
 		}
-		if strings.Contains(path, "//") {
-			return fmt.Errorf("Path '%v' contains '//'", path)
-		}
 		normalizedPaths[path] = path
 		if err := pathItem.Validate(c); err != nil {
 			return err
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 1a5fda1..84521d4 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -535,6 +535,11 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 			contentType.Schema = schema
 		}
 	}
+	for _, link := range value.Links {
+		if err := swaggerLoader.resolveLinkRef(swagger, link, refDocumentPath); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -705,6 +710,45 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 	return nil
 }
 
+func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, path *url.URL) error {
+	// Prevent infinite recursion
+	visited := swaggerLoader.visited
+	if _, isVisited := visited[component]; isVisited {
+		return nil
+	}
+	visited[component] = struct{}{}
+
+	const prefix = "#/components/links/"
+	if ref := component.Ref; len(ref) > 0 {
+		if isSingleRefElement(ref) {
+			var link Link
+			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &link); err != nil {
+				return err
+			}
+
+			component.Value = &link
+		} else {
+			components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
+			if err != nil {
+				return err
+			}
+			definitions := components.Links
+			if definitions == nil {
+				return failedToResolveRefFragmentPart(ref, "links")
+			}
+			resolved := definitions[id]
+			if resolved == nil {
+				return failedToResolveRefFragmentPart(ref, id)
+			}
+			if err := swaggerLoader.resolveLinkRef(swagger, resolved, componentPath); err != nil {
+				return err
+			}
+			component.Value = resolved.Value
+		}
+	}
+	return nil
+}
+
 func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) {
 	// Prevent infinite recursion
 	visited := swaggerLoader.visitedFiles
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 807a768..06ce223 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -208,8 +208,14 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn
 	var dec valueDecoder
 	switch param.In {
 	case openapi3.ParameterInPath:
+		if len(input.PathParams) == 0 {
+			return nil, nil
+		}
 		dec = &pathParamDecoder{pathParams: input.PathParams}
 	case openapi3.ParameterInQuery:
+		if len(input.GetQueryParams()) == 0 {
+			return nil, nil
+		}
 		dec = &urlValuesDecoder{values: input.GetQueryParams()}
 	case openapi3.ParameterInHeader:
 		dec = &headerParamDecoder{header: input.Request.Header}
@@ -219,25 +225,81 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn
 		return nil, fmt.Errorf("unsupported parameter's 'in': %s", param.In)
 	}
 
-	return decodeValue(dec, param.Name, sm, param.Schema)
+	return decodeValue(dec, param.Name, sm, param.Schema, param.Required)
 }
 
-func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, error) {
 	var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error)
-	switch schema.Value.Type {
-	case "array":
-		decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
-			return dec.DecodeArray(param, sm, schema)
+
+	if len(schema.Value.AllOf) > 0 {
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.AllOf {
+			value, err = decodeValue(dec, param, sm, sr, required)
+			if value == nil || err != nil {
+				break
+			}
+		}
+		return value, err
+	}
+
+	if len(schema.Value.AnyOf) > 0 {
+		for _, sr := range schema.Value.AnyOf {
+			value, _ := decodeValue(dec, param, sm, sr, required)
+			if value != nil {
+				return value, nil
+			}
 		}
-	case "object":
-		decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
-			return dec.DecodeObject(param, sm, schema)
+		if required == true {
+			return nil, fmt.Errorf("decoding anyOf for parameter %q failed", param)
+		} else {
+			return nil, nil
+		}
+
+	}
+
+	if len(schema.Value.OneOf) > 0 {
+		isMatched := 0
+		var value interface{}
+		for _, sr := range schema.Value.OneOf {
+			v, _ := decodeValue(dec, param, sm, sr, required)
+			if v != nil {
+				value = v
+				isMatched++
+			}
+		}
+		if isMatched == 1 {
+			return value, nil
+		} else if isMatched > 1 {
+			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
+		}
+		if required == true {
+			return nil, fmt.Errorf("decoding oneOf failed: %q is required", param)
+		} else {
+			return nil, nil
 		}
-	default:
-		decodeFn = dec.DecodePrimitive
+	}
+	if schema.Value.Not != nil {
+		// TODO(decode not): handle decoding "not" JSON Schema
+		return nil, errors.New("not implemented: decoding 'not'")
 	}
 
-	return decodeFn(param, sm, schema)
+	if schema.Value.Type != "" {
+		switch schema.Value.Type {
+		case "array":
+			decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+				return dec.DecodeArray(param, sm, schema)
+			}
+		case "object":
+			decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+				return dec.DecodeObject(param, sm, schema)
+			}
+		default:
+			decodeFn = dec.DecodePrimitive
+		}
+		return decodeFn(param, sm, schema)
+	}
+	return nil, nil
 }
 
 // pathParamDecoder decodes values of path parameters.
@@ -549,7 +611,7 @@ func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializ
 		return nil, nil
 	}
 	if err != nil {
-		return nil, fmt.Errorf("decode param %q: %s", param, err)
+		return nil, fmt.Errorf("decoding param %q: %s", param, err)
 	}
 	return parsePrimitive(cookie.Value, schema)
 }
@@ -565,7 +627,7 @@ func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.Serializatio
 		return nil, nil
 	}
 	if err != nil {
-		return nil, fmt.Errorf("decode param %q: %s", param, err)
+		return nil, fmt.Errorf("decoding param %q: %s", param, err)
 	}
 	return parseArray(strings.Split(cookie.Value, ","), schema)
 }
@@ -581,7 +643,7 @@ func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.Serializati
 		return nil, nil
 	}
 	if err != nil {
-		return nil, fmt.Errorf("decode param %q: %s", param, err)
+		return nil, fmt.Errorf("decoding param %q: %s", param, err)
 	}
 	props, err := propsFromString(cookie.Value, ",", ",")
 	if err != nil {
@@ -820,7 +882,7 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.
 		}
 		sm := enc.SerializationMethod()
 
-		if value, err = decodeValue(dec, name, sm, prop); err != nil {
+		if value, err = decodeValue(dec, name, sm, prop, false); err != nil {
 			return nil, err
 		}
 		obj[name] = value


Test patch:
diff --git a/openapi2/openapi2_test.go b/openapi2/openapi2_test.go
new file mode 100644
index 0000000..7819485
--- /dev/null
+++ b/openapi2/openapi2_test.go
@@ -0,0 +1,25 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestReadingSwagger(t *testing.T) {
+	var swagger Swagger
+
+	input, err := ioutil.ReadFile("testdata/swagger.json")
+	require.NoError(t, err)
+
+	err = json.Unmarshal(input, &swagger)
+	require.NoError(t, err)
+
+	output, err := json.Marshal(swagger)
+	require.NoError(t, err)
+
+	assert.JSONEq(t, string(input), string(output))
+}
diff --git a/openapi2/testdata/swagger.json b/openapi2/testdata/swagger.json
new file mode 100644
index 0000000..91484ff
--- /dev/null
+++ b/openapi2/testdata/swagger.json
@@ -0,0 +1,1 @@
+{"info":{"title":"Swagger Petstore","description":"This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0.3"},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"schemes":["https","http"],"host":"petstore.swagger.io","basePath":"/v2","paths":{"/pet":{"post":{"summary":"Add a new pet to the store","tags":["pet"],"operationId":"addPet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"summary":"Update an existing pet","tags":["pet"],"operationId":"updatePet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","tags":["pet"],"operationId":"findPetsByStatus","parameters":[{"in":"query","name":"status","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"default":"available","enum":["available","pending","sold"],"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid status value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","tags":["pet"],"operationId":"findPetsByTags","parameters":[{"in":"query","name":"tags","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid tag value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"delete":{"summary":"Deletes a pet","tags":["pet"],"operationId":"deletePet","parameters":[{"in":"header","name":"api_key","type":"string"},{"in":"path","name":"petId","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"get":{"summary":"Find pet by ID","description":"Returns a single pet","tags":["pet"],"operationId":"getPetById","parameters":[{"in":"path","name":"petId","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"api_key":[]}]},"post":{"summary":"Updates a pet in the store with form data","tags":["pet"],"operationId":"updatePetWithForm","parameters":[{"in":"path","name":"petId","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"name","description":"Updated name of the pet","type":"string"},{"in":"formData","name":"status","description":"Updated status of the pet","type":"string"}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"summary":"uploads an image","tags":["pet"],"operationId":"uploadFile","parameters":[{"in":"path","name":"petId","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"additionalMetadata","description":"Additional data to pass to server","type":"string"},{"in":"formData","name":"file","description":"file to upload","type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"consumes":["multipart/form-data"],"produces":["application/json"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","tags":["store"],"operationId":"getInventory","responses":{"200":{"description":"successful operation","schema":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"}}},"produces":["application/json"],"security":[{"api_key":[]}]}},"/store/order":{"post":{"summary":"Place an order for a pet","tags":["store"],"operationId":"placeOrder","parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/store/order/{orderId}":{"delete":{"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","tags":["store"],"operationId":"deleteOrder","parameters":[{"in":"path","name":"orderId","description":"ID of the order that needs to be deleted","required":true,"type":"integer","format":"int64","minimum":1}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value \u003e= 1 and \u003c= 10. Other values will generated exceptions","tags":["store"],"operationId":"getOrderById","parameters":[{"in":"path","name":"orderId","description":"ID of pet that needs to be fetched","required":true,"type":"integer","format":"int64","minimum":1,"maximum":10}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]}},"/user":{"post":{"summary":"Create user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"createUser","parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithArray":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithArrayInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithList":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithListInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/login":{"get":{"summary":"Logs user into the system","tags":["user"],"operationId":"loginUser","parameters":[{"in":"query","name":"username","description":"The user name for login","required":true,"type":"string"},{"in":"query","name":"password","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Expires-After":{"description":"date in UTC when token expires","type":"string"},"X-Rate-Limit":{"description":"calls per hour allowed by the user","type":"integer"}}},"400":{"description":"Invalid username/password supplied"}},"produces":["application/json","application/xml"]}},"/user/logout":{"get":{"summary":"Logs out current logged in user session","tags":["user"],"operationId":"logoutUser","responses":{"default":{"description":"successful operation"}},"produces":["application/json","application/xml"]}},"/user/{username}":{"delete":{"summary":"Delete user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"deleteUser","parameters":[{"in":"path","name":"username","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Get user by user name","tags":["user"],"operationId":"getUserByName","parameters":[{"in":"path","name":"username","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"put":{"summary":"Updated user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"updateUser","parameters":[{"in":"path","name":"username","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}}},"definitions":{"ApiResponse":{"properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"},"type":{"type":"string"}},"type":"object"},"Category":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Category"}},"Order":{"properties":{"complete":{"type":"boolean"},"id":{"format":"int64","type":"integer"},"petId":{"format":"int64","type":"integer"},"quantity":{"format":"int32","type":"integer"},"shipDate":{"format":"date-time","type":"string"},"status":{"description":"Order Status","enum":["placed","approved","delivered"],"type":"string"}},"type":"object","xml":{"name":"Order"}},"Pet":{"properties":{"category":{"$ref":"#/definitions/Category"},"id":{"format":"int64","type":"integer"},"name":{"example":"doggie","type":"string"},"photoUrls":{"items":{"type":"string","xml":{"name":"photoUrl"}},"type":"array","xml":{"wrapped":true}},"status":{"description":"pet status in the store","enum":["available","pending","sold"],"type":"string"},"tags":{"items":{"$ref":"#/definitions/Tag"},"type":"array","xml":{"wrapped":true}}},"required":["name","photoUrls"],"type":"object","xml":{"name":"Pet"}},"Tag":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Tag"}},"User":{"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"id":{"format":"int64","type":"integer"},"lastName":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"description":"User Status","format":"int32","type":"integer"},"username":{"type":"string"}},"type":"object","xml":{"name":"User"}}},"securityDefinitions":{"api_key":{"type":"apiKey","in":"header","name":"api_key"},"petstore_auth":{"type":"oauth2","flow":"implicit","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}]}
\ No newline at end of file
diff --git a/openapi3/swagger_loader_test.go b/openapi3/swagger_loader_test.go
index b5d1194..62b0c48 100644
--- a/openapi3/swagger_loader_test.go
+++ b/openapi3/swagger_loader_test.go
@@ -443,3 +443,48 @@ func TestLoadYamlFileWithExternalPathRef(t *testing.T) {
 	require.NotNil(t, swagger.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
 	require.Equal(t, "string", swagger.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
 }
+
+func TestResolveResponseLinkRef(t *testing.T) {
+	source := []byte(`
+openapi: 3.0.1
+info:
+  title: My API
+  version: 1.0.0
+components:
+  links:
+    Father:
+        description: link to to the father
+        operationId: getUserById
+        parameters:
+          "id": "$response.body#/fatherId"
+paths:
+  /users/{id}:
+    get:
+      operationId: getUserById,
+      parameters:
+        - name: id,
+          in: path
+          schema:
+            type: string
+      responses:
+        200:
+          description: A test response
+          content:
+            application/json:
+          links:
+            father:
+              $ref: '#/components/links/Father'
+`)
+	loader := openapi3.NewSwaggerLoader()
+	doc, err := loader.LoadSwaggerFromData(source)
+	require.NoError(t, err)
+
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+
+	response := doc.Paths[`/users/{id}`].Get.Responses.Get(200).Value
+	link := response.Links[`father`].Value
+	require.NotNil(t, link)
+	require.Equal(t, "getUserById", link.OperationID)
+	require.Equal(t, "link to to the father", link.Description)
+}
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 249bfce..49fb3a1 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -43,8 +43,26 @@ func TestDecodeParameter(t *testing.T) {
 		numberSchema  = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "number"}}
 		booleanSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "boolean"}}
 		stringSchema  = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "string"}}
-		arraySchema   = arrayOf(stringSchema)
-		objectSchema  = objectOf("id", stringSchema, "name", stringSchema)
+		allofSchema   = &openapi3.SchemaRef{
+			Value: &openapi3.Schema{
+				AllOf: []*openapi3.SchemaRef{
+					integerSchema,
+					numberSchema,
+				}}}
+		anyofSchema = &openapi3.SchemaRef{
+			Value: &openapi3.Schema{
+				AnyOf: []*openapi3.SchemaRef{
+					integerSchema,
+					stringSchema,
+				}}}
+		oneofSchema = &openapi3.SchemaRef{
+			Value: &openapi3.Schema{
+				OneOf: []*openapi3.SchemaRef{
+					booleanSchema,
+					integerSchema,
+				}}}
+		arraySchema  = arrayOf(stringSchema)
+		objectSchema = objectOf("id", stringSchema, "name", stringSchema)
 	)
 
 	type testCase struct {
@@ -418,6 +436,63 @@ func TestDecodeParameter(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "query Allof",
+			testCases: []testCase{
+				{
+					name:  "allofSchema integer and number",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema},
+					query: "param=1",
+					want:  float64(1),
+				},
+				{
+					name:  "allofSchema string",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema},
+					query: "param=abdf",
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "abdf"},
+				},
+			},
+		},
+		{
+			name: "query Anyof",
+			testCases: []testCase{
+				{
+					name:  "anyofSchema integer",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema},
+					query: "param=1",
+					want:  float64(1),
+				},
+				{
+					name:  "anyofSchema string",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema},
+					query: "param=abdf",
+					want:  "abdf",
+				},
+			},
+		},
+		{
+			name: "query Oneof",
+			testCases: []testCase{
+				{
+					name:  "oneofSchema boolean",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
+					query: "param=true",
+					want:  true,
+				},
+				{
+					name:  "oneofSchema int",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
+					query: "param=1122",
+					want:  float64(1122),
+				},
+				{
+					name:  "oneofSchema string",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
+					query: "param=abcd",
+					want:  nil,
+				},
+			},
+		},
 		{
 			name: "query array",
 			testCases: []testCase{
diff --git a/openapi3filter/router_test.go b/openapi3filter/router_test.go
index ed7fe9e..4eba2c3 100644
--- a/openapi3filter/router_test.go
+++ b/openapi3filter/router_test.go
@@ -128,31 +128,31 @@ func TestRouter(t *testing.T) {
 			}
 		}
 	}
-	expect("GET", "/not_existing", nil, nil)
-	expect("DELETE", "/hello", helloDELETE, nil)
-	expect("GET", "/hello", helloGET, nil)
-	expect("HEAD", "/hello", helloHEAD, nil)
-	expect("PATCH", "/hello", helloPATCH, nil)
-	expect("POST", "/hello", helloPOST, nil)
-	expect("PUT", "/hello", helloPUT, nil)
-	expect("GET", "/params/a/b/c/d", paramsGET, map[string]string{
+	expect(http.MethodGet, "/not_existing", nil, nil)
+	expect(http.MethodDelete, "/hello", helloDELETE, nil)
+	expect(http.MethodGet, "/hello", helloGET, nil)
+	expect(http.MethodHead, "/hello", helloHEAD, nil)
+	expect(http.MethodPatch, "/hello", helloPATCH, nil)
+	expect(http.MethodPost, "/hello", helloPOST, nil)
+	expect(http.MethodPut, "/hello", helloPUT, nil)
+	expect(http.MethodGet, "/params/a/b/c/d", paramsGET, map[string]string{
 		"x": "a",
 		"y": "b",
 		"z": "c/d",
 	})
-	expect("POST", "/partial", nil, nil)
+	expect(http.MethodPost, "/partial", nil, nil)
 	swagger.Servers = append(swagger.Servers, &openapi3.Server{
 		URL: "https://www.example.com/api/v1/",
 	}, &openapi3.Server{
 		URL: "https://{d0}.{d1}.com/api/v1/",
 	})
-	expect("GET", "/hello", nil, nil)
-	expect("GET", "/api/v1/hello", nil, nil)
-	expect("GET", "www.example.com/api/v1/hello", nil, nil)
-	expect("GET", "https:///api/v1/hello", nil, nil)
-	expect("GET", "https://www.example.com/hello", nil, nil)
-	expect("GET", "https://www.example.com/api/v1/hello", helloGET, map[string]string{})
-	expect("GET", "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{
+	expect(http.MethodGet, "/hello", nil, nil)
+	expect(http.MethodGet, "/api/v1/hello", nil, nil)
+	expect(http.MethodGet, "www.example.com/api/v1/hello", nil, nil)
+	expect(http.MethodGet, "https:///api/v1/hello", nil, nil)
+	expect(http.MethodGet, "https://www.example.com/hello", nil, nil)
+	expect(http.MethodGet, "https://www.example.com/api/v1/hello", helloGET, map[string]string{})
+	expect(http.MethodGet, "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{
 		"d0": "domain0",
 		"d1": "domain1",
 	})
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index d597471..7921592 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -74,6 +74,48 @@ func TestFilter(t *testing.T) {
 								Schema: openapi3.NewStringSchema().WithMaxLength(2).NewRef(),
 							},
 						},
+						{
+							Value: &openapi3.Parameter{
+								In:   "query",
+								Name: "queryArgAnyOf",
+								Schema: openapi3.NewAnyOfSchema(
+									openapi3.NewStringSchema().WithMaxLength(2),
+									openapi3.NewDateTimeSchema(),
+								).NewRef(),
+							},
+						},
+						{
+							Value: &openapi3.Parameter{
+								In:   "query",
+								Name: "queryArgOneOf",
+								Schema: openapi3.NewOneOfSchema(
+									openapi3.NewStringSchema().WithMaxLength(2),
+									openapi3.NewInt32Schema(),
+								).NewRef(),
+							},
+						},
+						{
+							Value: &openapi3.Parameter{
+								In:   "query",
+								Name: "queryArgAllOf",
+								Schema: openapi3.NewAllOfSchema(
+									openapi3.NewDateTimeSchema(),
+									openapi3.NewStringSchema(),
+								).NewRef(),
+							},
+						},
+						// TODO(decode not): handle decoding "not" JSON Schema
+						// {
+						// 	Value: &openapi3.Parameter{
+						// 		In:   "query",
+						// 		Name: "queryArgNot",
+						// 		Schema: &openapi3.SchemaRef{
+						// 			Value: &openapi3.Schema{
+						// 				Not: &openapi3.SchemaRef{
+						// 					Value: openapi3.NewInt32Schema(),
+						// 				}}},
+						// 	},
+						// },
 						{
 							Value: &openapi3.Parameter{
 								In:      "query",
@@ -159,15 +201,16 @@ func TestFilter(t *testing.T) {
 	var err error
 	var req ExampleRequest
 	var resp ExampleResponse
-
+	resp = ExampleResponse{
+		Status: 200,
+	}
 	// Test paths
+
 	req = ExampleRequest{
 		Method: "POST",
 		URL:    "http://example.com/api/prefix/v/suffix",
 	}
-	resp = ExampleResponse{
-		Status: 200,
-	}
+
 	err = expect(req, resp)
 	require.NoError(t, err)
 
@@ -204,11 +247,62 @@ func TestFilter(t *testing.T) {
 	// Test query parameter openapi3filter
 	req = ExampleRequest{
 		Method: "POST",
-		URL:    "http://example.com/api/prefix/v/suffix?queryArg=a",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgAnyOf=ae&queryArgOneOf=ac&queryArgAllOf=2017-12-31T11:59:59",
+	}
+	err = expect(req, resp)
+	require.NoError(t, err)
+
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgAnyOf=2017-12-31T11:59:59",
 	}
 	err = expect(req, resp)
 	require.NoError(t, err)
 
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgAnyOf=123",
+	}
+	err = expect(req, resp)
+	require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgOneOf=567",
+	}
+	err = expect(req, resp)
+	require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgOneOf=2017-12-31T11:59:59",
+	}
+	err = expect(req, resp)
+	require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgAllOf=abdfg",
+	}
+	err = expect(req, resp)
+	require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	// TODO(decode not): handle decoding "not" JSON Schema
+	// req = ExampleRequest{
+	// 	Method: "POST",
+	// 	URL:    "http://example.com/api/prefix/v/suffix?queryArgNot=abdfg",
+	// }
+	// err = expect(req, resp)
+	// require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	// TODO(decode not): handle decoding "not" JSON Schema
+	// req = ExampleRequest{
+	// 	Method: "POST",
+	// 	URL:    "http://example.com/api/prefix/v/suffix?queryArgNot=123",
+	// }
+	// err = expect(req, resp)
+	// require.IsType(t, &openapi3filter.RequestError{}, err)
+
 	req = ExampleRequest{
 		Method: "POST",
 		URL:    "http://example.com/api/prefix/v/suffix?queryArg=EXCEEDS_MAX_LENGTH",