github.com/cheikhshift/buffalo@v0.9.5/binding/binding.go (about) 1 package binding 2 3 import ( 4 "encoding/json" 5 "encoding/xml" 6 "net/http" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/gobuffalo/x/httpx" 12 "github.com/markbates/pop/nulls" 13 "github.com/monoculum/formam" 14 "github.com/pkg/errors" 15 ) 16 17 // Binder takes a request and binds it to an interface. 18 // If there is a problem it should return an error. 19 type Binder func(*http.Request, interface{}) error 20 21 // CustomTypeDecoder converts a custom type from the request insto its exact type. 22 type CustomTypeDecoder func([]string) (interface{}, error) 23 24 // binders is a map of the defined content-type related binders. 25 var binders = map[string]Binder{} 26 27 var decoder *formam.Decoder 28 var lock = &sync.Mutex{} 29 var timeFormats = []string{ 30 "2006-01-02T15:04:05Z07:00", 31 "01/02/2006", 32 "2006-01-02", 33 time.ANSIC, 34 time.UnixDate, 35 time.RubyDate, 36 time.RFC822, 37 time.RFC822Z, 38 time.RFC850, 39 time.RFC1123, 40 time.RFC1123Z, 41 time.RFC3339, 42 time.RFC3339Nano, 43 time.Kitchen, 44 time.Stamp, 45 time.StampMilli, 46 time.StampMicro, 47 time.StampNano, 48 } 49 50 // RegisterTimeFormats allows to add custom time layouts that 51 // the binder will be able to use for decoding. 52 func RegisterTimeFormats(layouts ...string) { 53 timeFormats = append(timeFormats, layouts...) 54 } 55 56 // RegisterCustomDecorder allows to define custom type decoders. 57 func RegisterCustomDecorder(fn CustomTypeDecoder, types []interface{}, fields []interface{}) { 58 rawFunc := (func([]string) (interface{}, error))(fn) 59 decoder.RegisterCustomType(rawFunc, types, fields) 60 } 61 62 // Register maps a request Content-Type (application/json) 63 // to a Binder. 64 func Register(contentType string, fn Binder) { 65 lock.Lock() 66 defer lock.Unlock() 67 68 binders[strings.ToLower(contentType)] = fn 69 } 70 71 // Exec will bind the interface to the request.Body. The type of binding 72 // is dependent on the "Content-Type" for the request. If the type 73 // is "application/json" it will use "json.NewDecoder". If the type 74 // is "application/xml" it will use "xml.NewDecoder". The default 75 // binder is "https://github.com/monoculum/formam". 76 func Exec(req *http.Request, value interface{}) error { 77 ct := httpx.ContentType(req) 78 if ct == "" { 79 return errors.New("blank content type") 80 } 81 if b, ok := binders[ct]; ok { 82 return b(req, value) 83 } 84 return errors.Errorf("could not find a binder for %s", ct) 85 } 86 87 func init() { 88 decoder = formam.NewDecoder(&formam.DecoderOptions{ 89 TagName: "form", 90 IgnoreUnknownKeys: true, 91 }) 92 93 decoder.RegisterCustomType(func(vals []string) (interface{}, error) { 94 return parseTime(vals) 95 }, []interface{}{time.Time{}}, nil) 96 97 decoder.RegisterCustomType(func(vals []string) (interface{}, error) { 98 var ti nulls.Time 99 100 t, err := parseTime(vals) 101 if err != nil { 102 return ti, errors.WithStack(err) 103 } 104 ti.Time = t 105 ti.Valid = true 106 107 return ti, nil 108 }, []interface{}{nulls.Time{}}, nil) 109 110 sb := func(req *http.Request, i interface{}) error { 111 err := req.ParseForm() 112 if err != nil { 113 return errors.WithStack(err) 114 } 115 116 if err := decoder.Decode(req.Form, i); err != nil { 117 return errors.WithStack(err) 118 } 119 return nil 120 } 121 122 binders["application/html"] = sb 123 binders["text/html"] = sb 124 binders["application/x-www-form-urlencoded"] = sb 125 binders["multipart/form-data"] = sb 126 binders["html"] = sb 127 } 128 129 func init() { 130 jb := func(req *http.Request, value interface{}) error { 131 return json.NewDecoder(req.Body).Decode(value) 132 } 133 134 binders["application/json"] = jb 135 binders["text/json"] = jb 136 binders["json"] = jb 137 } 138 139 func init() { 140 xb := func(req *http.Request, value interface{}) error { 141 return xml.NewDecoder(req.Body).Decode(value) 142 } 143 144 binders["application/xml"] = xb 145 binders["text/xml"] = xb 146 binders["xml"] = xb 147 } 148 149 func parseTime(vals []string) (time.Time, error) { 150 var t time.Time 151 var err error 152 153 // don't try to parse empty time values, it will raise an error 154 if len(vals) == 0 || vals[0] == "" { 155 return t, nil 156 } 157 158 for _, layout := range timeFormats { 159 t, err = time.Parse(layout, vals[0]) 160 if err == nil { 161 return t, nil 162 } 163 } 164 165 if err != nil { 166 return t, errors.WithStack(err) 167 } 168 169 return t, nil 170 }