Permalink
340 lines (310 sloc)
10.6 KB
| package echo | |
| import ( | |
| "encoding" | |
| "encoding/xml" | |
| "errors" | |
| "fmt" | |
| "net/http" | |
| "reflect" | |
| "strconv" | |
| "strings" | |
| ) | |
| type ( | |
| // Binder is the interface that wraps the Bind method. | |
| Binder interface { | |
| Bind(i interface{}, c Context) error | |
| } | |
| // DefaultBinder is the default implementation of the Binder interface. | |
| DefaultBinder struct{} | |
| // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. | |
| // Types that don't implement this, but do implement encoding.TextUnmarshaler | |
| // will use that interface instead. | |
| BindUnmarshaler interface { | |
| // UnmarshalParam decodes and assigns a value from an form or query param. | |
| UnmarshalParam(param string) error | |
| } | |
| ) | |
| // BindPathParams binds path params to bindable object | |
| func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error { | |
| names := c.ParamNames() | |
| values := c.ParamValues() | |
| params := map[string][]string{} | |
| for i, name := range names { | |
| params[name] = []string{values[i]} | |
| } | |
| if err := b.bindData(i, params, "param"); err != nil { | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| return nil | |
| } | |
| // BindQueryParams binds query params to bindable object | |
| func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error { | |
| if err := b.bindData(i, c.QueryParams(), "query"); err != nil { | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| return nil | |
| } | |
| // BindBody binds request body contents to bindable object | |
| // NB: then binding forms take note that this implementation uses standard library form parsing | |
| // which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm | |
| // See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm | |
| // See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm | |
| func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { | |
| req := c.Request() | |
| if req.ContentLength == 0 { | |
| return | |
| } | |
| ctype := req.Header.Get(HeaderContentType) | |
| switch { | |
| case strings.HasPrefix(ctype, MIMEApplicationJSON): | |
| if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil { | |
| switch err.(type) { | |
| case *HTTPError: | |
| return err | |
| default: | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| } | |
| case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML): | |
| if err = xml.NewDecoder(req.Body).Decode(i); err != nil { | |
| if ute, ok := err.(*xml.UnsupportedTypeError); ok { | |
| return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err) | |
| } else if se, ok := err.(*xml.SyntaxError); ok { | |
| return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err) | |
| } | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm): | |
| params, err := c.FormParams() | |
| if err != nil { | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| if err = b.bindData(i, params, "form"); err != nil { | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| default: | |
| return ErrUnsupportedMediaType | |
| } | |
| return nil | |
| } | |
| // BindHeaders binds HTTP headers to a bindable object | |
| func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error { | |
| if err := b.bindData(i, c.Request().Header, "header"); err != nil { | |
| return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) | |
| } | |
| return nil | |
| } | |
| // Bind implements the `Binder#Bind` function. | |
| // Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous | |
| // step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams. | |
| func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { | |
| if err := b.BindPathParams(c, i); err != nil { | |
| return err | |
| } | |
| // Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH) | |
| // Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that. | |
| // i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding. | |
| // This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body | |
| if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete { | |
| if err = b.BindQueryParams(c, i); err != nil { | |
| return err | |
| } | |
| } | |
| return b.BindBody(c, i) | |
| } | |
| // bindData will bind data ONLY fields in destination struct that have EXPLICIT tag | |
| func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error { | |
| if destination == nil || len(data) == 0 { | |
| return nil | |
| } | |
| typ := reflect.TypeOf(destination).Elem() | |
| val := reflect.ValueOf(destination).Elem() | |
| // Map | |
| if typ.Kind() == reflect.Map { | |
| for k, v := range data { | |
| val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) | |
| } | |
| return nil | |
| } | |
| // !struct | |
| if typ.Kind() != reflect.Struct { | |
| if tag == "param" || tag == "query" || tag == "header" { | |
| // incompatible type, data is probably to be found in the body | |
| return nil | |
| } | |
| return errors.New("binding element must be a struct") | |
| } | |
| for i := 0; i < typ.NumField(); i++ { | |
| typeField := typ.Field(i) | |
| structField := val.Field(i) | |
| if typeField.Anonymous { | |
| if structField.Kind() == reflect.Ptr { | |
| structField = structField.Elem() | |
| } | |
| } | |
| if !structField.CanSet() { | |
| continue | |
| } | |
| structFieldKind := structField.Kind() | |
| inputFieldName := typeField.Tag.Get(tag) | |
| if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" { | |
| // if anonymous struct with query/param/form tags, report an error | |
| return errors.New("query/param/form tags are not allowed with anonymous struct field") | |
| } | |
| if inputFieldName == "" { | |
| // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags). | |
| // structs that implement BindUnmarshaler are binded only when they have explicit tag | |
| if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct { | |
| if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil { | |
| return err | |
| } | |
| } | |
| // does not have explicit tag and is not an ordinary struct - so move to next field | |
| continue | |
| } | |
| inputValue, exists := data[inputFieldName] | |
| if !exists { | |
| // Go json.Unmarshal supports case insensitive binding. However the | |
| // url params are bound case sensitive which is inconsistent. To | |
| // fix this we must check all of the map values in a | |
| // case-insensitive search. | |
| for k, v := range data { | |
| if strings.EqualFold(k, inputFieldName) { | |
| inputValue = v | |
| exists = true | |
| break | |
| } | |
| } | |
| } | |
| if !exists { | |
| continue | |
| } | |
| // Call this first, in case we're dealing with an alias to an array type | |
| if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok { | |
| if err != nil { | |
| return err | |
| } | |
| continue | |
| } | |
| numElems := len(inputValue) | |
| if structFieldKind == reflect.Slice && numElems > 0 { | |
| sliceOf := structField.Type().Elem().Kind() | |
| slice := reflect.MakeSlice(structField.Type(), numElems, numElems) | |
| for j := 0; j < numElems; j++ { | |
| if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil { | |
| return err | |
| } | |
| } | |
| val.Field(i).Set(slice) | |
| } else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { | |
| // But also call it here, in case we're dealing with an array of BindUnmarshalers | |
| if ok, err := unmarshalField(valueKind, val, structField); ok { | |
| return err | |
| } | |
| switch valueKind { | |
| case reflect.Ptr: | |
| return setWithProperType(structField.Elem().Kind(), val, structField.Elem()) | |
| case reflect.Int: | |
| return setIntField(val, 0, structField) | |
| case reflect.Int8: | |
| return setIntField(val, 8, structField) | |
| case reflect.Int16: | |
| return setIntField(val, 16, structField) | |
| case reflect.Int32: | |
| return setIntField(val, 32, structField) | |
| case reflect.Int64: | |
| return setIntField(val, 64, structField) | |
| case reflect.Uint: | |
| return setUintField(val, 0, structField) | |
| case reflect.Uint8: | |
| return setUintField(val, 8, structField) | |
| case reflect.Uint16: | |
| return setUintField(val, 16, structField) | |
| case reflect.Uint32: | |
| return setUintField(val, 32, structField) | |
| case reflect.Uint64: | |
| return setUintField(val, 64, structField) | |
| case reflect.Bool: | |
| return setBoolField(val, structField) | |
| case reflect.Float32: | |
| return setFloatField(val, 32, structField) | |
| case reflect.Float64: | |
| return setFloatField(val, 64, structField) | |
| case reflect.String: | |
| structField.SetString(val) | |
| default: | |
| return errors.New("unknown type") | |
| } | |
| return nil | |
| } | |
| func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) { | |
| switch valueKind { | |
| case reflect.Ptr: | |
| return unmarshalFieldPtr(val, field) | |
| default: | |
| return unmarshalFieldNonPtr(val, field) | |
| } | |
| } | |
| func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { | |
| fieldIValue := field.Addr().Interface() | |
| if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok { | |
| return true, unmarshaler.UnmarshalParam(value) | |
| } | |
| if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok { | |
| return true, unmarshaler.UnmarshalText([]byte(value)) | |
| } | |
| return false, nil | |
| } | |
| func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) { | |
| if field.IsNil() { | |
| // Initialize the pointer to a nil value | |
| field.Set(reflect.New(field.Type().Elem())) | |
| } | |
| return unmarshalFieldNonPtr(value, field.Elem()) | |
| } | |
| func setIntField(value string, bitSize int, field reflect.Value) error { | |
| if value == "" { | |
| value = "0" | |
| } | |
| intVal, err := strconv.ParseInt(value, 10, bitSize) | |
| if err == nil { | |
| field.SetInt(intVal) | |
| } | |
| return err | |
| } | |
| func setUintField(value string, bitSize int, field reflect.Value) error { | |
| if value == "" { | |
| value = "0" | |
| } | |
| uintVal, err := strconv.ParseUint(value, 10, bitSize) | |
| if err == nil { | |
| field.SetUint(uintVal) | |
| } | |
| return err | |
| } | |
| func setBoolField(value string, field reflect.Value) error { | |
| if value == "" { | |
| value = "false" | |
| } | |
| boolVal, err := strconv.ParseBool(value) | |
| if err == nil { | |
| field.SetBool(boolVal) | |
| } | |
| return err | |
| } | |
| func setFloatField(value string, bitSize int, field reflect.Value) error { | |
| if value == "" { | |
| value = "0.0" | |
| } | |
| floatVal, err := strconv.ParseFloat(value, bitSize) | |
| if err == nil { | |
| field.SetFloat(floatVal) | |
| } | |
| return err | |
| } |