github.com/res-am/buffalo@v0.11.1/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/pop/nulls" 12 "github.com/gobuffalo/x/httpx" 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 "2006-01-02T03:04", 34 time.ANSIC, 35 time.UnixDate, 36 time.RubyDate, 37 time.RFC822, 38 time.RFC822Z, 39 time.RFC850, 40 time.RFC1123, 41 time.RFC1123Z, 42 time.RFC3339, 43 time.RFC3339Nano, 44 time.Kitchen, 45 time.Stamp, 46 time.StampMilli, 47 time.StampMicro, 48 time.StampNano, 49 } 50 51 // RegisterTimeFormats allows to add custom time layouts that 52 // the binder will be able to use for decoding. 53 func RegisterTimeFormats(layouts ...string) { 54 timeFormats = append(timeFormats, layouts...) 55 } 56 57 // RegisterCustomDecorder allows to define custom type decoders. 58 func RegisterCustomDecorder(fn CustomTypeDecoder, types []interface{}, fields []interface{}) { 59 rawFunc := (func([]string) (interface{}, error))(fn) 60 decoder.RegisterCustomType(rawFunc, types, fields) 61 } 62 63 // Register maps a request Content-Type (application/json) 64 // to a Binder. 65 func Register(contentType string, fn Binder) { 66 lock.Lock() 67 defer lock.Unlock() 68 69 binders[strings.ToLower(contentType)] = fn 70 } 71 72 // Exec will bind the interface to the request.Body. The type of binding 73 // is dependent on the "Content-Type" for the request. If the type 74 // is "application/json" it will use "json.NewDecoder". If the type 75 // is "application/xml" it will use "xml.NewDecoder". The default 76 // binder is "https://github.com/monoculum/formam". 77 func Exec(req *http.Request, value interface{}) error { 78 ct := httpx.ContentType(req) 79 if ct == "" { 80 return errors.New("blank content type") 81 } 82 if b, ok := binders[ct]; ok { 83 return b(req, value) 84 } 85 return errors.Errorf("could not find a binder for %s", ct) 86 } 87 88 func init() { 89 decoder = formam.NewDecoder(&formam.DecoderOptions{ 90 TagName: "form", 91 IgnoreUnknownKeys: true, 92 }) 93 94 decoder.RegisterCustomType(func(vals []string) (interface{}, error) { 95 return parseTime(vals) 96 }, []interface{}{time.Time{}}, nil) 97 98 decoder.RegisterCustomType(func(vals []string) (interface{}, error) { 99 var ti nulls.Time 100 101 t, err := parseTime(vals) 102 if err != nil { 103 return ti, errors.WithStack(err) 104 } 105 ti.Time = t 106 ti.Valid = true 107 108 return ti, nil 109 }, []interface{}{nulls.Time{}}, nil) 110 111 sb := func(req *http.Request, i interface{}) error { 112 err := req.ParseForm() 113 if err != nil { 114 return errors.WithStack(err) 115 } 116 117 if err := decoder.Decode(req.Form, i); err != nil { 118 return errors.WithStack(err) 119 } 120 return nil 121 } 122 123 binders["application/html"] = sb 124 binders["text/html"] = sb 125 binders["application/x-www-form-urlencoded"] = 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 }