Skip to content

Commit

Permalink
add file support to requests
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Bibby committed Apr 27, 2024
1 parent 8afc338 commit 58a04c0
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 7 deletions.
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.4.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/jmoiron/sqlx v1.3.5
github.com/joho/godotenv v1.5.1
github.com/mattn/go-sqlite3 v1.14.17
github.com/robfig/cron/v3 v3.0.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stoewer/go-strcase v1.3.0
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.21.0
Expand Down Expand Up @@ -54,7 +54,6 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tdewolff/parse/v2 v2.7.12 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/hairyhenderson/go-codeowners v0.4.0 h1:Wx/tRXb07sCyHeC8mXfio710Iu35uAy5KYiBdLHdv4Q=
github.com/hairyhenderson/go-codeowners v0.4.0/go.mod h1:iJgZeCt+W/GzXo5uchFCqvVHZY2T4TAIpvuVlKVkLxc=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
Expand Down
121 changes: 118 additions & 3 deletions request/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,101 @@ import (
"encoding/json"
"fmt"
"io"
"io/fs"
"mime/multipart"
"net/http"
"net/url"
"reflect"
"strings"
"time"

"github.com/abibby/salusa/internal/helpers"
"github.com/gorilla/mux"
)

var textUnmarshalerType = helpers.GetType[encoding.TextUnmarshaler]()
const (
// Decimal

KB = 1000
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
PB = 1000 * TB

// Binary

KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
TiB = 1024 * GiB
PiB = 1024 * TiB
)

var (
textUnmarshalerType = helpers.GetType[encoding.TextUnmarshaler]()
fsFileType = helpers.GetType[fs.File]()
)

type File struct {
file multipart.File
handle *FileInfo
}

// Close implements fs.File.
func (f *File) Close() error {
return f.file.Close()
}

// Read implements fs.File.
func (f *File) Read(b []byte) (int, error) {
return f.file.Read(b)
}

// Stat implements fs.File.
func (f *File) Stat() (fs.FileInfo, error) {
return f.handle, nil
}

type FileInfo struct{ header *multipart.FileHeader }

// IsDir implements fs.FileInfo.
func (f *FileInfo) IsDir() bool {
return false
}

// ModTime implements fs.FileInfo.
func (f *FileInfo) ModTime() time.Time {
return time.Now()
}

// Mode implements fs.FileInfo.
func (f *FileInfo) Mode() fs.FileMode {
return 0o644
}

// Name implements fs.FileInfo.
func (f *FileInfo) Name() string {
return f.header.Filename
}

// Size implements fs.FileInfo.
func (f *FileInfo) Size() int64 {
return f.header.Size
}

// Sys implements fs.FileInfo.
func (f *FileInfo) Sys() any {
return nil
}

var _ fs.FileInfo = (*FileInfo)(nil)

func Run(requestHttp *http.Request, requestStruct any) error {
urlArgs := map[string]map[string][]string{
"query": requestHttp.URL.Query(),
"path": pathArgs(requestHttp),
}

var jsonBody map[string]json.RawMessage

if requestHttp.Body != http.NoBody {
Expand All @@ -32,14 +112,20 @@ func Run(requestHttp *http.Request, requestStruct any) error {

requestHttp.Body = io.NopCloser(bytes.NewBuffer(body))

contentType := requestHttp.Header.Get("Content-Type")
contentType := strings.Split(requestHttp.Header.Get("Content-Type"), ";")[0]
switch contentType {
case "application/x-www-form-urlencoded":
bodyQuery, err := url.ParseQuery(string(body))
if err != nil {
return err
}
urlArgs["json"] = bodyQuery
case "multipart/form-data":
err := requestHttp.ParseMultipartForm(100 * MB)
if err != nil {
return err
}
urlArgs["json"] = requestHttp.MultipartForm.Value
default:
jsonBody = map[string]json.RawMessage{}
err := json.Unmarshal(body, &jsonBody)
Expand All @@ -57,10 +143,17 @@ func Run(requestHttp *http.Request, requestStruct any) error {
verr.AddError(sf.Tag.Get(tag), err.Error())
}
}

err := setFile(sf, fv, requestHttp)
if err != nil {
verr.AddError(sf.Tag.Get("json"), err.Error())
}

if jsonBody == nil {
return nil
}
err := setJSON(sf, fv, jsonBody)

err = setJSON(sf, fv, jsonBody)
if err != nil {
verr.AddError(sf.Tag.Get("json"), err.Error())
}
Expand Down Expand Up @@ -108,6 +201,28 @@ func setQuery(sf reflect.StructField, fv reflect.Value, tag string, args map[str
return nil
}

func setFile(sf reflect.StructField, fv reflect.Value, requestHttp *http.Request) error {
tagValue, ok := sf.Tag.Lookup("json")
if !ok {
return nil
}

if sf.Type != fsFileType {
return nil
}

file, handle, err := requestHttp.FormFile(tagValue)
if err != nil {
return err
}

fv.Set(reflect.ValueOf(&File{
file: file,
handle: &FileInfo{header: handle},
}))
return nil
}

func setJSON(sf reflect.StructField, fv reflect.Value, jsonBody map[string]json.RawMessage) error {
tagValue, ok := sf.Tag.Lookup("json")
if !ok {
Expand Down
38 changes: 38 additions & 0 deletions request/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package request

import (
"bytes"
"io"
"io/fs"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -152,3 +155,38 @@ func TestRun_query_url_body(t *testing.T) {
Foo: "bar",
}, structRequest)
}

func TestRun_multipart_file(t *testing.T) {
type Request struct {
Foo fs.File `json:"foo"`
}

buff := &bytes.Buffer{}
writer := multipart.NewWriter(buff)

part, err := writer.CreateFormFile("foo", "foo.txt")
assert.NoError(t, err)
_, err = part.Write([]byte("foo content"))
assert.NoError(t, err)

writer.Close()

httpRequest := httptest.NewRequest("POST", "http://0.0.0.0/", buff)
httpRequest.Header.Add("Content-Type", writer.FormDataContentType())

structRequest := &Request{}

err = Run(httpRequest, structRequest)
assert.NoError(t, err)

if !assert.NotNil(t, structRequest.Foo) {
return
}

fooContent, err := io.ReadAll(structRequest.Foo)
assert.NoError(t, err)

defer structRequest.Foo.Close()

assert.Equal(t, "foo content", string(fooContent))
}

0 comments on commit 58a04c0

Please sign in to comment.