github.com/mayra-cabrera/buffalo@v0.9.4-0.20170814145312-66d2e7772f11/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/markbates/pop/nulls" 12 "github.com/monoculum/formam" 13 "github.com/pkg/errors" 14 ) 15 16 // Binder takes a request and binds it to an interface. 17 // If there is a problem it should return an error. 18 type Binder func(*http.Request, interface{}) error 19 20 // CustomTypeDecoder converts a custom type from the request insto its exact type. 21 type CustomTypeDecoder func([]string) (interface{}, error) 22 23 // binders is a map of the defined content-type related binders. 24 var binders = map[string]Binder{} 25 26 var decoder *formam.Decoder 27 var lock = &sync.Mutex{} 28 var timeFormats = []string{ 29 "2006-01-02T15:04:05Z07:00", 30 "01/02/2006", 31 "2006-01-02", 32 time.ANSIC, 33 time.UnixDate, 34 time.RubyDate, 35 time.RFC822, 36 time.RFC822Z, 37 time.RFC850, 38 time.RFC1123, 39 time.RFC1123Z, 40 time.RFC3339, 41 time.RFC3339Nano, 42 time.Kitchen, 43 time.Stamp, 44 time.StampMilli, 45 time.StampMicro, 46 time.StampNano, 47 } 48 49 // RegisterTimeFormats allows to add custom time layouts that 50 // the binder will be able to use for decoding. 51 func RegisterTimeFormats(layouts ...string) { 52 timeFormats = append(timeFormats, layouts...) 53 } 54 55 // RegisterCustomDecorder allows to define custom type decoders. 56 func RegisterCustomDecorder(fn CustomTypeDecoder, types []interface{}, fields []interface{}) { 57 rawFunc := (func([]string) (interface{}, error))(fn) 58 decoder.RegisterCustomType(rawFunc, types, fields) 59 } 60 61 // Register maps a request Content-Type (application/json) 62 // to a Binder. 63 func Register(contentType string, fn Binder) { 64 lock.Lock() 65 defer lock.Unlock() 66 67 binders[strings.ToLower(contentType)] = fn 68 } 69 70 // Exec will bind the interface to the request.Body. The type of binding 71 // is dependent on the "Content-Type" for the request. If the type 72 // is "application/json" it will use "json.NewDecoder". If the type 73 // is "application/xml" it will use "xml.NewDecoder". The default 74 // binder is "https://github.com/monoculum/formam". 75 func Exec(req *http.Request, value interface{}) error { 76 ct := strings.ToLower(req.Header.Get("Content-Type")) 77 if ct != "" { 78 cts := strings.Split(ct, ";") 79 c := cts[0] 80 if b, ok := binders[strings.TrimSpace(c)]; ok { 81 return b(req, value) 82 } 83 return errors.Errorf("could not find a binder for %s", c) 84 } 85 return errors.New("blank content type") 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["multipart/form-data"] = sb 127 binders["html"] = sb 128 } 129 130 func init() { 131 jb := func(req *http.Request, value interface{}) error { 132 return json.NewDecoder(req.Body).Decode(value) 133 } 134 135 binders["application/json"] = jb 136 binders["text/json"] = jb 137 binders["json"] = jb 138 } 139 140 func init() { 141 xb := func(req *http.Request, value interface{}) error { 142 return xml.NewDecoder(req.Body).Decode(value) 143 } 144 145 binders["application/xml"] = xb 146 binders["text/xml"] = xb 147 binders["xml"] = xb 148 } 149 150 func parseTime(vals []string) (time.Time, error) { 151 var t time.Time 152 var err error 153 154 // don't try to parse empty time values, it will raise an error 155 if len(vals) == 0 || vals[0] == "" { 156 return t, nil 157 } 158 159 for _, layout := range timeFormats { 160 t, err = time.Parse(layout, vals[0]) 161 if err == nil { 162 return t, nil 163 } 164 } 165 166 if err != nil { 167 return t, errors.WithStack(err) 168 } 169 170 return t, nil 171 }