Repository: gookit/validate

Issue:
Multipart files upload doesn't support sending one file
According to last pull request (https://github.com/gookit/validate/pull/289) I can send multiple files and validate them into struct, but when I have tested this feature with only one file sending I have got an error (screens pinned). Am I doing something wrong?

Sending 2 files
![Image](https://github.com/user-attachments/assets/9ddffd2b-cd5f-43ca-a926-3c6592c3d647)

Success validation
![Image](https://github.com/user-attachments/assets/9992c2d3-6d07-48a0-a5de-527bc9c9b93c)

Sending only 1 file
![Image](https://github.com/user-attachments/assets/3669bc35-7141-4c73-bff3-b8a446feeb25)

Error
![Image](https://github.com/user-attachments/assets/fc855027-e637-401c-9bb4-fc845456031b)

Request struct
![Image](https://github.com/user-attachments/assets/77f3c97d-4024-4656-83c2-489edc6b05c4)

Controller logic
![Image](https://github.com/user-attachments/assets/bb2d964e-8c78-467d-9dd1-534632bb3d0b)

Hints from issue comments:
@almas-x: To better understand the issue, could you please share the validation rules you’re using?

Solution patch:
diff --git a/data_source.go b/data_source.go
index db74ae3..c8c1006 100644
--- a/data_source.go
+++ b/data_source.go
@@ -734,13 +734,13 @@ type FormData struct {
 	// since this is by far the most common use. If you
 	// need to have more than one file per key, parse the
 	// files manually using r.MultipartForm.File.
-	Files map[string]*multipart.FileHeader
+	Files map[string][]*multipart.FileHeader
 }
 
 func newFormData() *FormData {
 	return &FormData{
 		Form:  make(map[string][]string),
-		Files: make(map[string]*multipart.FileHeader),
+		Files: make(map[string][]*multipart.FileHeader),
 	}
 }
 
@@ -788,15 +788,18 @@ func (d *FormData) AddValues(values url.Values) {
 // AddFiles adds the multipart form files to data
 func (d *FormData) AddFiles(filesMap map[string][]*multipart.FileHeader) {
 	for key, files := range filesMap {
-		if len(files) != 0 {
-			d.AddFile(key, files[0])
+		for _, file := range files {
+			d.AddFile(key, file)
 		}
 	}
 }
 
 // AddFile adds the multipart form file to data with the given key.
 func (d *FormData) AddFile(key string, file *multipart.FileHeader) {
-	d.Files[key] = file
+	if _, ok := d.Files[key]; !ok {
+		d.Files[key] = []*multipart.FileHeader{}
+	}
+	d.Files[key] = append(d.Files[key], file)
 }
 
 // Del deletes the values associated with key.
@@ -850,8 +853,11 @@ func (d FormData) Get(key string) (any, bool) {
 	}
 
 	// get uploaded file
-	if fh, ok := d.Files[key]; ok {
-		return fh, true
+	if fh, ok := d.Files[key]; ok && len(fh) > 0 {
+		if len(fh) > 1 {
+			return fh, true
+		}
+		return fh[0], true
 	}
 	return nil, false
 }
@@ -870,6 +876,15 @@ func (d FormData) Strings(key string) []string {
 // If there is no file associated with key, it returns nil. If you just want the body of the
 // file, use GetFileBytes.
 func (d FormData) GetFile(key string) *multipart.FileHeader {
+	if fh, ok := d.Files[key]; ok && len(fh) > 0 {
+		return fh[0]
+	}
+
+	return nil
+}
+
+// GetFiles returns the multipart form files associated with key, if any, as a []*multipart.FileHeader.
+func (d FormData) GetFiles(key string) []*multipart.FileHeader {
 	return d.Files[key]
 }
 
@@ -942,8 +957,8 @@ func (d FormData) Bool(key string) bool {
 // there was a problem reading the file. If you need to know whether or not the file
 // exists (i.e. whether it was provided in the request), use the FileExists method.
 func (d FormData) FileBytes(field string) ([]byte, error) {
-	fh, found := d.Files[field]
-	if !found {
+	fh := d.GetFile(field)
+	if fh == nil {
 		return nil, nil
 	}
 
@@ -957,8 +972,8 @@ func (d FormData) FileBytes(field string) ([]byte, error) {
 
 // FileMimeType get File Mime Type name. eg "image/png"
 func (d FormData) FileMimeType(field string) (mime string) {
-	fh, found := d.Files[field]
-	if !found {
+	fh := d.GetFile(field)
+	if fh == nil {
 		return
 	}
 



Test patch:
diff --git a/data_source_test.go b/data_source_test.go
index 420779b..5d97b19 100644
--- a/data_source_test.go
+++ b/data_source_test.go
@@ -122,6 +122,14 @@ func TestFormData(t *testing.T) {
 	is.NotEmpty(d.GetFile("file"))
 	d.DelFile("file")
 	is.False(d.HasFile("file"))
+
+	// files
+	d.AddFiles(map[string][]*multipart.FileHeader{"files": {&multipart.FileHeader{Filename: "test1.txt"}, &multipart.FileHeader{Filename: "test2.txt"}}})
+	is.True(d.Has("files"))
+	is.True(d.HasFile("files"))
+	is.Len(d.GetFiles("files"), 2)
+	d.DelFile("files")
+	is.False(d.HasFile("files"))
 }
 
 func TestStructData_Create(t *testing.T) {