Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue when only one file was uploaded in batch receiver attribute #2365

Merged
merged 3 commits into from
Dec 21, 2022
Merged
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
145 changes: 143 additions & 2 deletions net/ghttp/ghttp_z_unit_feature_request_file_test.go
Original file line number Diff line number Diff line change
@@ -9,11 +9,12 @@ package ghttp_test
import (
"context"
"fmt"
"github.com/gogf/gf/v2/internal/json"
"strings"
"testing"
"time"

"github.com/gogf/gf/v2/internal/json"

"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
@@ -173,7 +174,7 @@ func Test_Params_File_Batch(t *testing.T) {
})
}

func Test_Params_Strict_Route_File_Single(t *testing.T) {
func Test_Params_Strict_Route_File_Single_Ptr_Attrr(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
File *ghttp.UploadFile `type:"file"`
@@ -219,6 +220,48 @@ func Test_Params_Strict_Route_File_Single(t *testing.T) {
})
}

func Test_Params_Strict_Route_File_Single_Struct_Attr(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
File ghttp.UploadFile `type:"file"`
}
type Res struct{}

dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
s := g.Server(guid.S())
s.BindHandler("/upload/single", func(ctx context.Context, req *Req) (res *Res, err error) {
var (
r = g.RequestFromCtx(ctx)
file = req.File
)
name, err := file.Save(dstDirPath)
if err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(name)
return
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// normal name
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))

srcPath := gtest.DataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
})
t.AssertNE(content, "")
t.AssertNE(content, "upload failed")
t.Assert(content, "file1.txt")
t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath))
})
}

func Test_Params_File_Upload_Required(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
@@ -274,3 +317,101 @@ func Test_Params_File_MarshalJSON(t *testing.T) {
t.Assert(strings.Contains(content, "file1.txt"), true)
})
}

// Select only one file when batch uploading
func Test_Params_Strict_Route_File_Batch_Up_One(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
Files ghttp.UploadFiles `type:"file"`
}
type Res struct{}

dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
s := g.Server(guid.S())
s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) {
var (
r = g.RequestFromCtx(ctx)
files = req.Files
)
if len(files) == 0 {
r.Response.WriteExit("upload file cannot be empty")
}
names, err := files.Save(dstDirPath)
if err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(gstr.Join(names, ","))
return
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// normal name
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))

srcPath := gtest.DataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent(ctx, "/upload/batch", g.Map{
"files": "@file:" + srcPath,
})
t.AssertNE(content, "")
t.AssertNE(content, "upload file cannot be empty")
t.AssertNE(content, "upload failed")
t.Assert(content, "file1.txt")
t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath))
})
}

// Select multiple files during batch upload
func Test_Params_Strict_Route_File_Batch_Up_Multiple(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
Files ghttp.UploadFiles `type:"file"`
}
type Res struct{}

dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
s := g.Server(guid.S())
s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) {
var (
r = g.RequestFromCtx(ctx)
files = req.Files
)
if len(files) == 0 {
r.Response.WriteExit("upload file cannot be empty")
}
names, err := files.Save(dstDirPath)
if err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(gstr.Join(names, ","))
return
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// normal name
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))

srcPath1 := gtest.DataPath("upload", "file1.txt")
srcPath2 := gtest.DataPath("upload", "file2.txt")
dstPath1 := gfile.Join(dstDirPath, "file1.txt")
dstPath2 := gfile.Join(dstDirPath, "file2.txt")
content := client.PostContent(ctx, "/upload/batch",
"files=@file:"+srcPath1+
"&files=@file:"+srcPath2,
)
t.AssertNE(content, "")
t.AssertNE(content, "upload file cannot be empty")
t.AssertNE(content, "upload failed")
t.Assert(content, "file1.txt,file2.txt")
t.Assert(gfile.GetContents(dstPath1), gfile.GetContents(srcPath1))
t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2))
})
}
26 changes: 23 additions & 3 deletions util/gconv/gconv_struct.go
Original file line number Diff line number Diff line change
@@ -145,9 +145,29 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string

// If `params` and `pointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() {
pointerElemReflectValue.Set(paramsReflectValue)
return nil
if pointerElemReflectValue.IsValid() {
switch {
// Eg:
// UploadFile => UploadFile
// *UploadFile => *UploadFile
case pointerElemReflectValue.Type() == paramsReflectValue.Type():
pointerElemReflectValue.Set(paramsReflectValue)
return nil

// Eg:
// UploadFile => *UploadFile
case pointerElemReflectValue.Kind() == reflect.Ptr && pointerElemReflectValue.Elem().IsValid() &&
pointerElemReflectValue.Elem().Type() == paramsReflectValue.Type():
pointerElemReflectValue.Elem().Set(paramsReflectValue)
return nil

// Eg:
// *UploadFile => UploadFile
case paramsReflectValue.Kind() == reflect.Ptr && paramsReflectValue.Elem().IsValid() &&
pointerElemReflectValue.Type() == paramsReflectValue.Elem().Type():
pointerElemReflectValue.Set(paramsReflectValue.Elem())
return nil
}
}

// Normal unmarshalling interfaces checks.