Skip to content

Commit 2ad066a

Browse files
committed
Added multipart reader
1 parent cdb1c32 commit 2ad066a

File tree

2 files changed

+100
-9
lines changed

2 files changed

+100
-9
lines changed

pkg/httprequest/query.go

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,9 @@ var (
2828

2929
// Read the request query parameters into a structure
3030
func Query(q url.Values, v any) error {
31-
rv := reflect.ValueOf(v)
32-
if rv.Kind() != reflect.Ptr {
33-
return errBadRequest.With("v must be a pointer")
34-
} else {
35-
rv = rv.Elem()
36-
}
37-
if rv.Kind() != reflect.Struct {
38-
return errBadRequest.With("v must be a pointer to a struct")
31+
rv, err := structValue(v)
32+
if err != nil {
33+
return err
3934
}
4035

4136
// Enumerate fields
@@ -70,6 +65,22 @@ func Query(q url.Values, v any) error {
7065
return nil
7166
}
7267

68+
///////////////////////////////////////////////////////////////////////////////
69+
// PRIVATE METHODS
70+
71+
func structValue(v any) (reflect.Value, error) {
72+
rv := reflect.ValueOf(v)
73+
if rv.Kind() != reflect.Ptr {
74+
return reflect.ValueOf(nil), errBadRequest.With("v must be a pointer")
75+
} else {
76+
rv = rv.Elem()
77+
}
78+
if rv.Kind() != reflect.Struct {
79+
return reflect.ValueOf(nil), errBadRequest.With("v must be a pointer to a struct")
80+
}
81+
return rv, nil
82+
}
83+
7384
func jsonName(field reflect.StructField) string {
7485
tag := field.Tag.Get(tagName)
7586
if tag == "-" {
@@ -81,6 +92,30 @@ func jsonName(field reflect.StructField) string {
8192
return field.Name
8293
}
8394

95+
func writableFieldForName(v any, name string) (reflect.Value, error) {
96+
rv, err := structValue(v)
97+
if err != nil {
98+
return reflect.ValueOf(nil), err
99+
}
100+
101+
// Enumerate fields
102+
for _, field := range reflect.VisibleFields(rv.Type()) {
103+
tag := jsonName(field)
104+
if tag == "" || tag != name {
105+
continue
106+
}
107+
108+
// Get the value - return success
109+
v := rv.FieldByIndex(field.Index)
110+
if v.CanSet() {
111+
return v, nil
112+
}
113+
}
114+
115+
// Return failure
116+
return reflect.ValueOf(nil), errBadRequest.With("field %q not found or not writable", name)
117+
}
118+
84119
func setQueryValue(tag string, v reflect.Value, value []string) error {
85120
// Set zero-value
86121
if len(value) == 0 {

pkg/httprequest/read.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ import (
55
"errors"
66
"io"
77
"net/http"
8+
"reflect"
89

910
// Packages
10-
"github.com/mutablelogic/go-server/pkg/httpresponse"
11+
gomultipart "github.com/mutablelogic/go-client/pkg/multipart"
12+
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
1113
types "github.com/mutablelogic/go-server/pkg/types"
1214
)
1315

16+
///////////////////////////////////////////////////////////////////////////////
17+
// GLOBALS
18+
19+
const (
20+
FormDataMaxMemory = 256 << 20 // 256 MB
21+
)
22+
1423
///////////////////////////////////////////////////////////////////////////////
1524
// PUBLIC METHODS
1625

@@ -28,6 +37,8 @@ func Read(r *http.Request, v interface{}) error {
2837
return readJson(r, v)
2938
case types.ContentTypeTextPlain:
3039
return readString(r, v)
40+
case types.ContentTypeFormData:
41+
return readFormData(r, v)
3142
}
3243

3344
// Cannot handle this content type
@@ -66,3 +77,48 @@ func readString(r *http.Request, v any) error {
6677
return httpresponse.ErrInternalError.Withf("cannot read %T as string", v)
6778
}
6879
}
80+
81+
var (
82+
typeFile = reflect.TypeOf(gomultipart.File{})
83+
)
84+
85+
func readFormData(r *http.Request, v any) error {
86+
if err := r.ParseMultipartForm(FormDataMaxMemory); err != nil {
87+
return err
88+
}
89+
if r.MultipartForm == nil {
90+
return httpresponse.ErrBadRequest.With("Missing form data")
91+
}
92+
93+
// Set non-file fields
94+
if err := Query(r.MultipartForm.Value, v); err != nil {
95+
return err
96+
}
97+
// Set file fields - we only support one file per field
98+
for key, values := range r.MultipartForm.File {
99+
if len(values) == 0 {
100+
continue
101+
}
102+
// Get the first file for the field
103+
value, err := writableFieldForName(v, key)
104+
if err != nil {
105+
return err
106+
}
107+
switch value.Type() {
108+
case typeFile:
109+
body, err := values[0].Open()
110+
if err != nil {
111+
return errBadRequest.Withf("cannot open file %q: %v", values[0].Filename, err)
112+
}
113+
value.Set(reflect.ValueOf(gomultipart.File{
114+
Path: values[0].Filename,
115+
Body: body,
116+
}))
117+
default:
118+
return httpresponse.ErrBadRequest.Withf("cannot set field %q of type %s", key, value.Type())
119+
}
120+
}
121+
122+
// Return success
123+
return nil
124+
}

0 commit comments

Comments
 (0)