Repository: getkin/kin-openapi
Pull request #399 (2021-08-10 13:18:53)

Accept multipart/form-data's part without Content-Type
handle non-file parts of `multipart/form-data` as `text/plain`. ref https://github.com/getkin/kin-openapi/issues/398


Patch:
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index b9b0cd8..2d2728b 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -809,6 +809,11 @@ const prefixUnsupportedCT = "unsupported content type"
 // The function returns ParseError when a body is invalid.
 func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
 	contentType := header.Get(headerCT)
+	if contentType == "" {
+		if _, ok := body.(*multipart.Part); ok {
+			contentType = "text/plain"
+		}
+	}
 	mediaType := parseMediaType(contentType)
 	decoder, ok := bodyDecoders[mediaType]
 	if !ok {
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index 4011932..a47c762 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -56,7 +56,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 	if len(servers) == 0 {
 		servers = append(servers, srv{})
 	}
-	muxRouter := mux.NewRouter() /*.UseEncodedPath()?*/
+	muxRouter := mux.NewRouter().UseEncodedPath()
 	r := &Router{}
 	for _, path := range orderedPaths(doc.Paths) {
 		pathItem := doc.Paths[path]


Test patch:
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 397c9e9..7ae863b 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -974,6 +974,7 @@ func TestDecodeBody(t *testing.T) {
 		{name: "c", contentType: "text/plain", data: strings.NewReader("c2")},
 		{name: "d", contentType: "application/json", data: bytes.NewReader(d)},
 		{name: "f", contentType: "application/octet-stream", data: strings.NewReader("foo"), filename: "f1"},
+		{name: "g", data: strings.NewReader("g1")},
 	})
 	require.NoError(t, err)
 
@@ -987,10 +988,14 @@ func TestDecodeBody(t *testing.T) {
 		{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
 		{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
 	})
+	require.NoError(t, err)
+
 	multipartAdditionalProps, multipartMimeAdditionalProps, err := newTestMultipartForm([]*testFormPart{
 		{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
 		{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
 	})
+	require.NoError(t, err)
+
 	multipartAdditionalPropsErr, multipartMimeAdditionalPropsErr, err := newTestMultipartForm([]*testFormPart{
 		{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
 		{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
@@ -1075,8 +1080,9 @@ func TestDecodeBody(t *testing.T) {
 				WithProperty("b", openapi3.NewIntegerSchema()).
 				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())).
 				WithProperty("d", openapi3.NewObjectSchema().WithProperty("d1", openapi3.NewStringSchema())).
-				WithProperty("f", openapi3.NewStringSchema().WithFormat("binary")),
-			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo"},
+				WithProperty("f", openapi3.NewStringSchema().WithFormat("binary")).
+				WithProperty("g", openapi3.NewStringSchema()),
+			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo", "g": "g1"},
 		},
 		{
 			name: "multipartExtraPart",
diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go
index d9ad2c9..c42d7f8 100644
--- a/routers/gorillamux/router_test.go
+++ b/routers/gorillamux/router_test.go
@@ -146,7 +146,7 @@ func TestRouter(t *testing.T) {
 	expect(r, http.MethodGet, "/params/a/b/c%2Fd", paramsGET, map[string]string{
 		"x": "a",
 		"y": "b",
-		"z": "c/d",
+		"z": "c%2Fd",
 	})
 	expect(r, http.MethodGet, "/books/War.and.Peace", paramsGET, map[string]string{
 		"bookid": "War.and.Peace",