Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
paganotoni committed May 25, 2020
2 parents f54ca9e + b30b199 commit 1c74ae6
Show file tree
Hide file tree
Showing 32 changed files with 1,155 additions and 810 deletions.
4 changes: 2 additions & 2 deletions Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ RUN npm install -g --no-progress yarn \
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0

# Pulling docker binary from releases
RUN wget https://github.com/gobuffalo/buffalo/releases/download/v0.16.8/buffalo_0.16.8_Linux_x86_64.tar.gz \
&& tar -xzf buffalo_0.16.8_Linux_x86_64.tar.gz \
RUN wget https://github.com/gobuffalo/buffalo/releases/download/v0.16.9/buffalo_0.16.9_Linux_x86_64.tar.gz \
&& tar -xzf buffalo_0.16.9_Linux_x86_64.tar.gz \
&& mv buffalo $(go env GOPATH)/bin/buffalo

RUN go get github.com/gobuffalo/buffalo-pop/v2
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.slim.build
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ RUN npm i -g --no-progress yarn \
&& yarn config set yarn-offline-mirror-pruning true

# Pulling docker binary from releases
RUN wget https://github.com/gobuffalo/buffalo/releases/download/v0.16.8/buffalo_0.16.8_Linux_x86_64.tar.gz \
&& tar -xzf buffalo_0.16.8_Linux_x86_64.tar.gz \
RUN wget https://github.com/gobuffalo/buffalo/releases/download/vv0.16.9/buffalo_v0.16.9_Linux_x86_64.tar.gz \
&& tar -xzf buffalo_v0.16.9_Linux_x86_64.tar.gz \
&& mv buffalo $(go env GOPATH)/bin/buffalo

RUN go get github.com/gobuffalo/buffalo-pop/v2
Expand Down
172 changes: 31 additions & 141 deletions binding/binding.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,56 @@
package binding

import (
"encoding/json"
"encoding/xml"
"fmt"
"net/http"
"strings"
"sync"
"time"

"github.com/gobuffalo/buffalo/internal/httpx"
"github.com/gobuffalo/nulls"
"github.com/gobuffalo/buffalo/binding/decoders"
"github.com/monoculum/formam"
)

// Binder takes a request and binds it to an interface.
// If there is a problem it should return an error.
type Binder func(*http.Request, interface{}) error
var (
// MaxFileMemory can be used to set the maximum size, in bytes, for files to be
// stored in memory during uploaded for multipart requests.
// See https://golang.org/pkg/net/http/#Request.ParseMultipartForm for more
// information on how this impacts file uploads.
MaxFileMemory int64 = 5 * 1024 * 1024

// CustomTypeDecoder converts a custom type from the request insto its exact type.
type CustomTypeDecoder func([]string) (interface{}, error)

// binders is a map of the defined content-type related binders.
var binders = map[string]Binder{}
formDecoder = formam.NewDecoder(&formam.DecoderOptions{
TagName: "form",
IgnoreUnknownKeys: true,
})

var decoder *formam.Decoder
var lock = &sync.Mutex{}
var timeFormats = []string{
time.RFC3339,
"01/02/2006",
"2006-01-02",
"2006-01-02T15:04",
time.ANSIC,
time.UnixDate,
time.RubyDate,
time.RFC822,
time.RFC822Z,
time.RFC850,
time.RFC1123,
time.RFC1123Z,
time.RFC3339Nano,
time.Kitchen,
time.Stamp,
time.StampMilli,
time.StampMicro,
time.StampNano,
}
// BaseRequestBinder is an instance of the requeBinder, it comes with preconfigured
// content type binders for HTML, JSON, XML and Files, as well as custom types decoders
// for time.Time and nulls.Time
BaseRequestBinder = NewRequestBinder(
HTMLContentTypeBinder{
decoder: formDecoder,
},
JSONContentTypeBinder{},
XMLRequestTypeBinder{},
FileRequestTypeBinder{
decoder: formDecoder,
},
)
)

// RegisterTimeFormats allows to add custom time layouts that
// the binder will be able to use for decoding.
func RegisterTimeFormats(layouts ...string) {
timeFormats = append(layouts, timeFormats...)
decoders.RegisterTimeFormats(layouts...)
}

// RegisterCustomDecoder allows to define custom type decoders.
// RegisterCustomDecoder allows to define custom decoders for certain types
// In the request.
func RegisterCustomDecoder(fn CustomTypeDecoder, types []interface{}, fields []interface{}) {
rawFunc := (func([]string) (interface{}, error))(fn)
decoder.RegisterCustomType(rawFunc, types, fields)
formDecoder.RegisterCustomType(rawFunc, types, fields)
}

// Register maps a request Content-Type (application/json)
// to a Binder.
func Register(contentType string, fn Binder) {
lock.Lock()
defer lock.Unlock()

binders[strings.ToLower(contentType)] = fn
BaseRequestBinder.Register(contentType, fn)
}

// Exec will bind the interface to the request.Body. The type of binding
Expand All @@ -74,100 +59,5 @@ func Register(contentType string, fn Binder) {
// is "application/xml" it will use "xml.NewDecoder". The default
// binder is "https://github.com/monoculum/formam".
func Exec(req *http.Request, value interface{}) error {
if ba, ok := value.(Bindable); ok {
return ba.Bind(req)
}

ct := httpx.ContentType(req)
if ct == "" {
return fmt.Errorf("blank content type")
}
if b, ok := binders[ct]; ok {
return b(req, value)
}
return fmt.Errorf("could not find a binder for %s", ct)
}

func init() {
decoder = formam.NewDecoder(&formam.DecoderOptions{
TagName: "form",
IgnoreUnknownKeys: true,
})

decoder.RegisterCustomType(func(vals []string) (interface{}, error) {
return parseTime(vals)
}, []interface{}{time.Time{}}, nil)

decoder.RegisterCustomType(func(vals []string) (interface{}, error) {
var ti nulls.Time

t, err := parseTime(vals)
if err != nil {
return ti, err
}
ti.Time = t
ti.Valid = true

return ti, nil
}, []interface{}{nulls.Time{}}, nil)

sb := func(req *http.Request, i interface{}) error {
err := req.ParseForm()
if err != nil {
return err
}

if err := decoder.Decode(req.Form, i); err != nil {
return err
}
return nil
}

binders["application/html"] = sb
binders["text/html"] = sb
binders["application/x-www-form-urlencoded"] = sb
binders["html"] = sb
}

func init() {
jb := func(req *http.Request, value interface{}) error {
return json.NewDecoder(req.Body).Decode(value)
}

binders["application/json"] = jb
binders["text/json"] = jb
binders["json"] = jb
}

func init() {
xb := func(req *http.Request, value interface{}) error {
return xml.NewDecoder(req.Body).Decode(value)
}

binders["application/xml"] = xb
binders["text/xml"] = xb
binders["xml"] = xb
}

func parseTime(vals []string) (time.Time, error) {
var t time.Time
var err error

// don't try to parse empty time values, it will raise an error
if len(vals) == 0 || vals[0] == "" {
return t, nil
}

for _, layout := range timeFormats {
t, err = time.Parse(layout, vals[0])
if err == nil {
return t, nil
}
}

if err != nil {
return t, err
}

return t, nil
return BaseRequestBinder.Exec(req, value)
}
40 changes: 0 additions & 40 deletions binding/binding_custom_decoder_test.go

This file was deleted.

94 changes: 49 additions & 45 deletions binding/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,73 @@ package binding

import (
"net/http"
"net/url"
"testing"
"time"

"github.com/stretchr/testify/require"
)

type blogPost struct {
Tags []string
Dislikes int
Likes int32
}

func Test_Register(t *testing.T) {
r := require.New(t)
l := len(binders)

Register("foo/bar", func(*http.Request, interface{}) error {
return nil
})
r.Len(binders, l+1)
}

func TestParseTimeErrorParsing(t *testing.T) {
r := require.New(t)
_, err := parseTime([]string{"this is sparta"})
r.Error(err)
}

func TestParseTime(t *testing.T) {
r.NotNil(BaseRequestBinder.binders["foo/bar"])

r := require.New(t)
req, err := http.NewRequest("POST", "/", nil)
r.NoError(err)

testCases := []struct {
input string
expected time.Time
expectErr bool
}{
{
input: "2017-01-01",
expected: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),
expectErr: false,
},
{
input: "2018-07-13T15:34",
expected: time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),
expectErr: false,
},
{
input: "2018-20-10T30:15",
expected: time.Time{},
expectErr: true,
},
req.Header.Set("Content-Type", "foo/bar")
req.Form = url.Values{
"Tags": []string{"AAA"},
"Likes": []string{"12"},
"Dislikes": []string{"1000"},
}

for _, tc := range testCases {
tt, err := parseTime([]string{tc.input})
if !tc.expectErr {
r.NoError(err)
}
r.Equal(tc.expected, tt)
}
}
req.ParseForm()

var post blogPost
r.NoError(Exec(req, &post))

func TestParseTimeConflicting(t *testing.T) {
RegisterTimeFormats("2006-02-01")
r.Equal([]string(nil), post.Tags)
r.Equal(int32(0), post.Likes)
r.Equal(0, post.Dislikes)

}

func Test_RegisterCustomDecoder(t *testing.T) {
r := require.New(t)
tt, err := parseTime([]string{"2017-01-10"})

RegisterCustomDecoder(func(vals []string) (interface{}, error) {
return []string{"X"}, nil
}, []interface{}{[]string{}}, nil)

RegisterCustomDecoder(func(vals []string) (interface{}, error) {
return 0, nil
}, []interface{}{int(0)}, nil)

post := blogPost{}
req, err := http.NewRequest("POST", "/", nil)
r.NoError(err)
expected := time.Date(2017, time.October, 1, 0, 0, 0, 0, time.UTC)
r.Equal(expected, tt)

req.Header.Set("Content-Type", "application/html")
req.Form = url.Values{
"Tags": []string{"AAA"},
"Likes": []string{"12"},
"Dislikes": []string{"1000"},
}
req.ParseForm()

r.NoError(Exec(req, &post))
r.Equal([]string{"X"}, post.Tags)
r.Equal(int32(12), post.Likes)
r.Equal(0, post.Dislikes)
}
Loading

0 comments on commit 1c74ae6

Please sign in to comment.