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

Commit

Permalink
feature: Support the upload of multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
Karl Haas committed Aug 2, 2023
1 parent 6f2bafe commit 99bf8e8
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 26 deletions.
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:%d", f.Filename, f.Size)

}
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:3672,file.go:348,types.go:507", 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

0 comments on commit 99bf8e8

Please sign in to comment.