Repository: getkin/kin-openapi (go)

Issue:
query param with anyOf failed to parse
query param with anyOf failed to parse

Openapi yaml with:

paths:
/resource-store:
get:
operationId: SearchResources
tags:
- Resource Store)
parameters:
- name: resource-type
in: query
description: Type of the resource to get
required: true
schema:
$ref: '#/components/schemas/RESOURCEType'
responses:
xxxx

components:
schemas:
RESOURCEType:
anyOf:
- type: string
enum:
- MEM
- CPU
- DISK
- type: string

====
when I succeeded to build and run the server, it panics at requests with this message:
echo: http: panic serving [::1]:56426: schema has non primitive type ""
goroutine 12 [running]:
net/http.(*conn).serve.func1(0xc000088140)
/usr/local/go/src/net/http/server.go:1769 +0x139
panic(0x86c960, 0xc0004740a0)
/usr/local/go/src/runtime/panic.go:522 +0x1b5
github.com/getkin/kin-openapi/openapi3filter.parsePrimitive(0xc00002c6e0, 0x3, 0xc00022cd00, 0x23, 0xc00005b7b8, 0x3, 0x3)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go:705 +0xa1e
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).DecodePrimitive(0xc000010040, 0xc000161fe0, 0xd, 0xc000466080, 0xc00022cd00, 0x8, 0xc000010040, 0x5, 0x0)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go:412 +0x329
github.com/getkin/kin-openapi/openapi3filter.decodeValue(0x9c6ba0, 0xc000010040, 0xc000161fe0, 0xd, 0xc000466080, 0xc00022cd00, 0x0, 0x0, 0x0, 0x0)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go:243 +0xcf
github.com/getkin/kin-openapi/openapi3filter.decodeStyledParameter(0xc0000e0500, 0xc00045c450, 0x0, 0x0, 0x0, 0x0)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go:225 +0x134
github.com/getkin/kin-openapi/openapi3filter.ValidateParameter(0x9c7f20, 0xc00045c4b0, 0xc00045c450, 0xc0000e0500, 0x0, 0x0)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/validate_request.go:95 +0x350
github.com/getkin/kin-openapi/openapi3filter.ValidateRequest(0x9c7f20, 0xc00045c4b0, 0xc00045c450, 0x9b4cf0, 0x9117e0)
/home/admin/go/src/github.com/getkin/kin-openapi/openapi3filter/validate_request.go:49 +0x243
github.com/deepmap/oapi-codegen/pkg/middleware.ValidateRequestFromContext(0x9d4600, 0xc0000de5a0, 0xc000142b60, 0x0, 0x5d259d4c, 0xb0915dc)
/home/admin/go/src/github.com/deepmap/oapi-codegen/pkg/middleware/oapi_validate.go:115 +0x537
github.com/deepmap/oapi-codegen/pkg/middleware.OapiRequestValidatorWithOptions.func1.1(0x9d4600, 0xc0000de5a0, 0xd09620, 0xc00005bc28)
/home/admin/go/src/github.com/deepmap/oapi-codegen/pkg/middleware/oapi_validate.go:69 +0x54
github.com/labstack/echo/middleware.LoggerWithConfig.func2.1(0x9d4600, 0xc0000de5a0, 0x2, 0x2)
/home/admin/go/src/github.com/labstack/echo/middleware/logger.go:119 +0x1c0
github.com/labstack/echo.(*Echo).ServeHTTP(0xc000224380, 0x9c6fa0, 0xc0000c6000, 0xc0000e8000)
/home/admin/go/src/github.com/labstack/echo/echo.go:616 +0x22d
net/http.serverHandler.ServeHTTP(0xc00014a9c0, 0x9c6fa0, 0xc0000c6000, 0xc0000e8000)
/usr/local/go/src/net/http/server.go:2774 +0xa8
net/http.(*conn).serve(0xc000088140, 0x9c7e60, 0xc000458840)

The request is:
curl "localhost:8080/resource-store?resource-type=CPU"

I solved the issue by simply declaring:
RESOURCEType:
type: string

Still, I'd like to understand the cause behind the former syntax and the possibility to get the former syntax work.

Thanks,
Charlie

Pull request description:
add anyof, allof, oneof in parameter
Fix #106 query param with anyOf failed to parse . Now I have added AnyOf , OneOf and AllOf but haven't Not. because for Not ,i have no idea currently.

Patch:
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 807a768..813bbac 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 input.PathParams == nil {
+			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,91 @@ 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
+			}
 		}
-	case "object":
-		decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
-			return dec.DecodeObject(param, sm, schema)
+		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
+			}
 		}
-	default:
-		decodeFn = dec.DecodePrimitive
+		if required == true {
+			return nil, fmt.Errorf("decode AnyOf failed " + param)
+		} else {
+			return nil, nil
+		}
+
 	}
 
-	return decodeFn(param, sm, schema)
+	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("decode Oneof failed, matched times:%d", isMatched)
+		}
+		if required == true {
+			return nil, fmt.Errorf("decode Oneof failed, " + param + "is required")
+		} else {
+			return nil, nil
+		}
+	}
+	// TODO: if schema.Value.Not isn't nil , how to deal with it?
+	if schema.Value.Not != nil {
+		v, err := decodeValue(dec, param, sm, schema.Value.Not, required)
+		if v != nil {
+			return nil, fmt.Errorf("decode Not failed")
+		}
+		if err != nil {
+			return nil, fmt.Errorf("Currently the 3pp can't decode Not type")
+		}
+		if required == true {
+			return nil, fmt.Errorf("Currently the 3pp can't decode Not type")
+		}
+		return nil, nil
+	}
+
+	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.
@@ -820,7 +892,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/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/validation_test.go b/openapi3filter/validation_test.go
index d597471..1b09161 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -74,6 +74,47 @@ 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(),
+							},
+						},
+						{
+							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 +200,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 +246,60 @@ 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)
+
+	req = ExampleRequest{
+		Method: "POST",
+		URL:    "http://example.com/api/prefix/v/suffix?queryArgNot=abdfg",
+	}
+	err = expect(req, resp)
+	require.IsType(t, &openapi3filter.RequestError{}, err)
+
+	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",