Repository: go-openapi/runtime (go)

Issue:
Unable to set a parameter of type file as optional
I am not sure if no one needed this before, but I have a use case where the file type parameter is optional, but the code below returns error before validating whether the field is required.

https://github.com/go-openapi/runtime/blob/3f9800fb72fae00e99a99db505da3b330f847e8e/middleware/parameter.go#L206-L213

I can throw in an additional if condition to check if the file is optional and return nil. Please let me know if that works.

Pull request description:
Fixes #246
Signed-off-by: Makarand <mnsd@divisionsinc.com>

Fixes the error with optional file parameter

Patch:
diff --git a/middleware/parameter.go b/middleware/parameter.go
index 8fa0cf4..9aaf659 100644
--- a/middleware/parameter.go
+++ b/middleware/parameter.go
@@ -206,7 +206,11 @@ func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams
 		if p.parameter.Type == "file" {
 			file, header, ffErr := request.FormFile(p.parameter.Name)
 			if ffErr != nil {
-				return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
+				if p.parameter.Required {
+					return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
+				} else {
+					return nil
+				}
 			}
 			target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
 			return nil


Test patch:
diff --git a/middleware/request_test.go b/middleware/request_test.go
index 0c424a8..4970c9e 100644
--- a/middleware/request_test.go
+++ b/middleware/request_test.go
@@ -406,7 +406,7 @@ type fileRequest struct {
 func paramsForFileUpload() *UntypedRequestBinder {
 	nameParam := spec.FormDataParam("name").Typed("string", "")
 
-	fileParam := spec.FileParam("file")
+	fileParam := spec.FileParam("file").AsRequired()
 
 	params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
 	return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
@@ -469,4 +469,63 @@ func TestBindingFileUpload(t *testing.T) {
 
 	data = fileRequest{}
 	assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+
+	writer = multipart.NewWriter(body)
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	req, _ = http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	data = fileRequest{}
+	assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+}
+
+func paramsForOptionalFileUpload() *UntypedRequestBinder {
+	nameParam := spec.FormDataParam("name").Typed("string", "")
+	fileParam := spec.FileParam("file").AsOptional()
+
+	params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
+	return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
+}
+
+func TestBindingOptionalFileUpload(t *testing.T) {
+	binder := paramsForOptionalFileUpload()
+
+	body := bytes.NewBuffer(nil)
+	writer := multipart.NewWriter(body)
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	urlStr := "http://localhost:8002/hello"
+	req, _ := http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	data := fileRequest{}
+	assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+	assert.Equal(t, "the-name", data.Name)
+	assert.Nil(t, data.File.Data)
+	assert.Nil(t, data.File.Header)
+
+	writer = multipart.NewWriter(body)
+	part, err := writer.CreateFormFile("file", "plain-jane.txt")
+	assert.NoError(t, err)
+	_, _ = part.Write([]byte("the file contents"))
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	req, _ = http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+	assert.NoError(t, writer.Close())
+
+	data = fileRequest{}
+	assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+	assert.Equal(t, "the-name", data.Name)
+	assert.NotNil(t, data.File)
+	assert.NotNil(t, data.File.Header)
+	assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
+
+	bb, err := ioutil.ReadAll(data.File.Data)
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("the file contents"), bb)
 }
diff --git a/middleware/untyped_request_test.go b/middleware/untyped_request_test.go
index e44249d..93f7084 100644
--- a/middleware/untyped_request_test.go
+++ b/middleware/untyped_request_test.go
@@ -111,6 +111,54 @@ func TestUntypedFileUpload(t *testing.T) {
 
 	data = make(map[string]interface{})
 	assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+
+	writer = multipart.NewWriter(body)
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	req, _ = http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	data = make(map[string]interface{})
+	assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+}
+
+func TestUntypedOptionalFileUpload(t *testing.T) {
+	binder := paramsForOptionalFileUpload()
+
+	body := bytes.NewBuffer(nil)
+	writer := multipart.NewWriter(body)
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	urlStr := "http://localhost:8002/hello"
+	req, _ := http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	data := make(map[string]interface{})
+	assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+	assert.Equal(t, "the-name", data["name"])
+
+	writer = multipart.NewWriter(body)
+	part, err := writer.CreateFormFile("file", "plain-jane.txt")
+	assert.NoError(t, err)
+	_, _ = part.Write([]byte("the file contents"))
+	_ = writer.WriteField("name", "the-name")
+	assert.NoError(t, writer.Close())
+
+	req, _ = http.NewRequest("POST", urlStr, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+	assert.NoError(t, writer.Close())
+
+	data = make(map[string]interface{})
+	assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+	assert.Equal(t, "the-name", data["name"])
+	assert.NotNil(t, data["file"])
+	assert.IsType(t, runtime.File{}, data["file"])
+	file := data["file"].(runtime.File)
+	assert.NotNil(t, file.Header)
+	assert.Equal(t, "plain-jane.txt", file.Header.Filename)
+
 }
 
 func TestUntypedBindingTypesForValid(t *testing.T) {