Skip to content

Commit a033ad7

Browse files
joeybloggsjoeybloggs
authored andcommitted
Changed Unmarshal to Decode finished logic to populate struct + tests
1 parent f5aee57 commit a033ad7

File tree

3 files changed

+179
-0
lines changed

3 files changed

+179
-0
lines changed

context.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

12+
"github.com/go-playground/form"
1213
"github.com/gorilla/websocket"
1314
"golang.org/x/net/context"
1415
)
@@ -57,6 +58,7 @@ type Context interface {
5758
TextBytes(int, []byte) error
5859
Attachment(r io.Reader, filename string) (err error)
5960
Inline(r io.Reader, filename string) (err error)
61+
Decode(includeFormQueryParams bool, maxMemory int64, v interface{}) (err error)
6062
BaseContext() *Ctx
6163
}
6264

@@ -73,6 +75,7 @@ type Ctx struct {
7375
index int
7476
formParsed bool
7577
multipartFormParsed bool
78+
l *LARS
7679
}
7780

7881
var _ context.Context = &Ctx{}
@@ -82,6 +85,7 @@ func NewContext(l *LARS) *Ctx {
8285

8386
c := &Ctx{
8487
params: make(Params, l.mostParams),
88+
l: l,
8589
}
8690

8791
c.response = newResponse(nil, c)
@@ -412,6 +416,53 @@ func (c *Ctx) Inline(r io.Reader, filename string) (err error) {
412416
return
413417
}
414418

419+
// Decode takes the request and attempts to discover it's type
420+
// via http headers and then unmarshal into the provided struct.
421+
// Example if header was "application/json" would unmarshal using
422+
// json.NewDecoder(io.LimitReader(c.request.Body, maxMemory)).Decode(v).
423+
func (c *Ctx) Decode(includeFormQueryParams bool, maxMemory int64, v interface{}) (err error) {
424+
425+
c.l.formDecoderInit.Do(func() {
426+
c.l.formDecoder = form.NewDecoder()
427+
})
428+
429+
typ := c.request.Header.Get(ContentType)
430+
431+
if idx := strings.Index(typ, ";"); idx != -1 {
432+
typ = typ[:idx]
433+
}
434+
435+
switch typ {
436+
437+
case ApplicationJSON:
438+
err = json.NewDecoder(io.LimitReader(c.request.Body, maxMemory)).Decode(v)
439+
440+
case ApplicationXML:
441+
err = xml.NewDecoder(io.LimitReader(c.request.Body, maxMemory)).Decode(v)
442+
443+
case ApplicationForm:
444+
445+
if err = c.ParseForm(); err == nil {
446+
if includeFormQueryParams {
447+
err = c.l.formDecoder.Decode(v, c.request.Form)
448+
} else {
449+
err = c.l.formDecoder.Decode(v, c.request.PostForm)
450+
}
451+
}
452+
453+
case MultipartForm:
454+
455+
if err = c.ParseMultipartForm(maxMemory); err == nil {
456+
if includeFormQueryParams {
457+
err = c.l.formDecoder.Decode(v, c.request.Form)
458+
} else {
459+
err = c.l.formDecoder.Decode(v, c.request.MultipartForm.Value)
460+
}
461+
}
462+
}
463+
return
464+
}
465+
415466
// golang.org/x/net/context functions to comply with context.Context interface and keep context update on lars.Context object
416467

417468
// Context returns the request's context. To change the context, use

context_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package lars
22

33
import (
4+
"bytes"
45
"encoding/xml"
56
"io"
7+
"mime/multipart"
68
"net/http"
79
"net/http/httptest"
10+
"net/url"
811
"os"
912
"reflect"
13+
"strings"
1014
"testing"
1115
"time"
1216

@@ -26,6 +30,124 @@ import (
2630
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
2731
//
2832

33+
func TestDecode(t *testing.T) {
34+
35+
type TestStruct struct {
36+
ID int `form:"id"`
37+
Posted string
38+
MultiPartPosted string
39+
}
40+
41+
test := new(TestStruct)
42+
43+
l := New()
44+
l.Post("/decode/:id", func(c Context) {
45+
err := c.Decode(true, 16<<10, test)
46+
Equal(t, err, nil)
47+
})
48+
l.Post("/decode2/:id", func(c Context) {
49+
err := c.Decode(false, 16<<10, test)
50+
Equal(t, err, nil)
51+
})
52+
hf := l.Serve()
53+
54+
form := url.Values{}
55+
form.Add("Posted", "value")
56+
57+
r, _ := http.NewRequest(POST, "/decode/13", strings.NewReader(form.Encode()))
58+
r.Header.Set(ContentType, ApplicationForm)
59+
w := httptest.NewRecorder()
60+
61+
hf.ServeHTTP(w, r)
62+
63+
Equal(t, w.Code, http.StatusOK)
64+
Equal(t, test.ID, 13)
65+
Equal(t, test.Posted, "value")
66+
Equal(t, test.MultiPartPosted, "")
67+
68+
test = new(TestStruct)
69+
r, _ = http.NewRequest(POST, "/decode2/13", strings.NewReader(form.Encode()))
70+
r.Header.Set(ContentType, ApplicationForm)
71+
w = httptest.NewRecorder()
72+
73+
hf.ServeHTTP(w, r)
74+
75+
Equal(t, w.Code, http.StatusOK)
76+
Equal(t, test.ID, 0)
77+
Equal(t, test.Posted, "value")
78+
Equal(t, test.MultiPartPosted, "")
79+
80+
body := &bytes.Buffer{}
81+
writer := multipart.NewWriter(body)
82+
83+
err := writer.WriteField("MultiPartPosted", "value")
84+
Equal(t, err, nil)
85+
86+
// Don't forget to close the multipart writer.
87+
// If you don't close it, your request will be missing the terminating boundary.
88+
err = writer.Close()
89+
Equal(t, err, nil)
90+
91+
test = new(TestStruct)
92+
r, _ = http.NewRequest(POST, "/decode/13", body)
93+
r.Header.Set(ContentType, writer.FormDataContentType())
94+
w = httptest.NewRecorder()
95+
96+
hf.ServeHTTP(w, r)
97+
Equal(t, w.Code, http.StatusOK)
98+
Equal(t, test.ID, 13)
99+
Equal(t, test.Posted, "")
100+
Equal(t, test.MultiPartPosted, "value")
101+
102+
body = &bytes.Buffer{}
103+
writer = multipart.NewWriter(body)
104+
105+
err = writer.WriteField("MultiPartPosted", "value")
106+
Equal(t, err, nil)
107+
108+
// Don't forget to close the multipart writer.
109+
// If you don't close it, your request will be missing the terminating boundary.
110+
err = writer.Close()
111+
Equal(t, err, nil)
112+
113+
test = new(TestStruct)
114+
r, _ = http.NewRequest(POST, "/decode2/13", body)
115+
r.Header.Set(ContentType, writer.FormDataContentType())
116+
w = httptest.NewRecorder()
117+
118+
hf.ServeHTTP(w, r)
119+
Equal(t, w.Code, http.StatusOK)
120+
Equal(t, test.ID, 0)
121+
Equal(t, test.Posted, "")
122+
Equal(t, test.MultiPartPosted, "value")
123+
124+
jsonBody := `{"ID":13,"Posted":"value","MultiPartPosted":"value"}`
125+
test = new(TestStruct)
126+
r, _ = http.NewRequest(POST, "/decode/13", strings.NewReader(jsonBody))
127+
r.Header.Set(ContentType, ApplicationJSON)
128+
w = httptest.NewRecorder()
129+
130+
hf.ServeHTTP(w, r)
131+
132+
Equal(t, w.Code, http.StatusOK)
133+
Equal(t, test.ID, 13)
134+
Equal(t, test.Posted, "value")
135+
Equal(t, test.MultiPartPosted, "value")
136+
137+
xmlBody := `<TestStruct><ID>13</ID><Posted>value</Posted><MultiPartPosted>value</MultiPartPosted></TestStruct>`
138+
test = new(TestStruct)
139+
r, _ = http.NewRequest(POST, "/decode/13", strings.NewReader(xmlBody))
140+
r.Header.Set(ContentType, ApplicationXML)
141+
w = httptest.NewRecorder()
142+
143+
hf.ServeHTTP(w, r)
144+
145+
Equal(t, w.Code, http.StatusOK)
146+
Equal(t, test.ID, 13)
147+
Equal(t, test.Posted, "value")
148+
Equal(t, test.MultiPartPosted, "value")
149+
}
150+
29151
func TestStream(t *testing.T) {
30152
l := New()
31153

lars.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"reflect"
77
"strings"
88
"sync"
9+
10+
"github.com/go-playground/form"
911
)
1012

1113
// HTTP Constant Terms and Variables
@@ -150,6 +152,10 @@ type LARS struct {
150152
// if enabled automatically handles OPTION requests; manually configured OPTION
151153
// handlers take precidence. default true
152154
automaticallyHandleOPTIONS bool
155+
156+
// form decoder + once initialization
157+
formDecoder *form.Decoder
158+
formDecoderInit sync.Once
153159
}
154160

155161
// RouteMap contains a single routes full path

0 commit comments

Comments
 (0)