Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

feature: Support the upload of multiple files #2389

Open
wants to merge 2 commits into
base: v1
Choose a base branch
from
Open
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
29 changes: 21 additions & 8 deletions binding/file_request_type_binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,30 @@ func (ht FileRequestTypeBinder) BinderFunc() Binder {
if !f.IsValid() {
continue
}
if _, ok := f.Interface().(File); !ok {
if f.Kind() == reflect.Slice {
for _, fh := range req.MultipartForm.File[n] {
mf, err := fh.Open()
if err != nil {
return err
}

f.Set(reflect.Append(f, reflect.ValueOf(File{
File: mf,
FileHeader: fh,
})))
}
continue
}
mf, mh, err := req.FormFile(n)
if err != nil {
return err
if _, ok := f.Interface().(File); ok {
mf, mh, err := req.FormFile(n)
if err != nil {
return err
}
f.Set(reflect.ValueOf(File{
File: mf,
FileHeader: mh,
}))
}
f.Set(reflect.ValueOf(File{
File: mf,
FileHeader: mh,
}))
}

return nil
Expand Down
68 changes: 50 additions & 18 deletions binding/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package binding_test

import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/gobuffalo/buffalo"
Expand All @@ -20,6 +22,10 @@ type WithFile struct {
MyFile binding.File
}

type NamedFileSlice struct {
MyFiles []binding.File `form:"thefiles"`
}

type NamedFile struct {
MyFile binding.File `form:"afile"`
}
Expand All @@ -40,6 +46,18 @@ func App() *buffalo.App {
}
return c.Render(http.StatusCreated, render.String(wf.MyFile.Filename))
})
a.POST("/named-file-slice", func(c buffalo.Context) error {
wmf := &NamedFileSlice{}
if err := c.Bind(wmf); err != nil {
return err
}
result := make([]string, len(wmf.MyFiles))
for i, f := range wmf.MyFiles {
result[i] += fmt.Sprintf("%s", f.Filename)

}
return c.Render(http.StatusCreated, render.String(strings.Join(result, ",")))
})
a.POST("/on-context", func(c buffalo.Context) error {
f, err := c.File("MyFile")
if err != nil {
Expand All @@ -54,7 +72,7 @@ func App() *buffalo.App {
func Test_File_Upload_On_Struct(t *testing.T) {
r := require.New(t)

req, err := newfileUploadRequest("/on-struct", "MyFile", "file_test.go")
req, err := newFileUploadRequest("/on-struct", "MyFile", "file_test.go")
r.NoError(err)
res := httptest.NewRecorder()

Expand All @@ -64,10 +82,23 @@ func Test_File_Upload_On_Struct(t *testing.T) {
r.Equal("file_test.go", res.Body.String())
}

func Test_File_Upload_On_Struct_WithTag_WithMultipleFiles(t *testing.T) {
r := require.New(t)

req, err := newFileUploadRequest("/named-file-slice", "thefiles", "file_test.go", "file.go", "types.go")
r.NoError(err)
res := httptest.NewRecorder()

App().ServeHTTP(res, req)

r.Equal(http.StatusCreated, res.Code)
r.Equal("file_test.go,file.go,types.go", res.Body.String())
}

func Test_File_Upload_On_Struct_WithTag(t *testing.T) {
r := require.New(t)

req, err := newfileUploadRequest("/named-file", "afile", "file_test.go")
req, err := newFileUploadRequest("/named-file", "afile", "file_test.go")
r.NoError(err)
res := httptest.NewRecorder()

Expand All @@ -80,7 +111,7 @@ func Test_File_Upload_On_Struct_WithTag(t *testing.T) {
func Test_File_Upload_On_Context(t *testing.T) {
r := require.New(t)

req, err := newfileUploadRequest("/on-context", "MyFile", "file_test.go")
req, err := newFileUploadRequest("/on-context", "MyFile", "file_test.go")
r.NoError(err)
res := httptest.NewRecorder()

Expand All @@ -92,27 +123,28 @@ func Test_File_Upload_On_Context(t *testing.T) {

// this helper method was inspired by this blog post by Matt Aimonetti:
// https://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/
func newfileUploadRequest(uri string, paramName, path string) (*http.Request, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()

func newFileUploadRequest(uri string, paramName string, paths ...string) (*http.Request, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
return nil, err
}
if _, err = io.Copy(part, file); err != nil {
return nil, err

for _, path := range paths {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
return nil, err
}
if _, err = io.Copy(part, file); err != nil {
return nil, err
}
}

if err = writer.Close(); err != nil {
if err := writer.Close(); err != nil {
return nil, err
}

req, err := http.NewRequest("POST", uri, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, err
Expand Down