Binder

package revel

// A Binder translates between string parameters and Go data structures.
type Binder struct {
    // Bind takes the name and type of the desired parameter and constructs it
    // from one or more values from Params.
    //
    // Example
    //
    // Request:
    //   url?id=123&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=rob
    //
    // Action:
    //   Example.Action(id int, ol []int, ul []string, user User)
    //
    // Calls:
    //   Bind(params, "id", int): 123
    //   Bind(params, "ol", []int): {1, 2}
    //   Bind(params, "ul", []string): {"str", "array"}
    //   Bind(params, "user", User): User{Name:"rob"}
    //
    // Note that only exported struct fields may be bound.
    Bind func(params *Params, name string, typ reflect.Type) reflect.Value

    // Unbind serializes a given value to one or more URL parameters of the given
    // name.
    Unbind func(output map[string]string, name string, val interface{})
}

// An adapter for easily making one-key-value binders.
func ValueBinder(f func(value string, typ reflect.Type) reflect.Value) func(*Params, string, reflect.Type) reflect.Value {
    return func(params *Params, name string, typ reflect.Type) reflect.Value {
        vals, ok := params.Values[name]
        if !ok || len(vals) == 0 {
            return reflect.Zero(typ)
        }
        return f(vals[0], typ)
    }
}

const (
    DEFAULT_DATE_FORMAT     = "2006-01-02"
    DEFAULT_DATETIME_FORMAT = "2006-01-02 15:04"
)

var (
    // These are the lookups to find a Binder for any type of data.
    // The most specific binder found will be used (Type before Kind)
    TypeBinders = make(map[reflect.Type]Binder)
    KindBinders = make(map[reflect.Kind]Binder)

    // Applications can add custom time formats to this array, and they will be
    // automatically attempted when binding a time.Time.
    TimeFormats = []string{}

    DateFormat     string
    DateTimeFormat string

    IntBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            if len(val) == 0 {
                return reflect.Zero(typ)
            }
            intValue, err := strconv.ParseInt(val, 10, 64)
            if err != nil {
                WARN.Println(err)
                return reflect.Zero(typ)
            }
            pValue := reflect.New(typ)
            pValue.Elem().SetInt(intValue)
            return pValue.Elem()
        }),
        Unbind: func(output map[string]string, key string, val interface{}) {
            output[key] = fmt.Sprintf("%d", val)
        },
    }

    UintBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            if len(val) == 0 {
                return reflect.Zero(typ)
            }
            uintValue, err := strconv.ParseUint(val, 10, 64)
            if err != nil {
                WARN.Println(err)
                return reflect.Zero(typ)
            }
            pValue := reflect.New(typ)
            pValue.Elem().SetUint(uintValue)
            return pValue.Elem()
        }),
        Unbind: func(output map[string]string, key string, val interface{}) {
            output[key] = fmt.Sprintf("%d", val)
        },
    }

    FloatBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            if len(val) == 0 {
                return reflect.Zero(typ)
            }
            floatValue, err := strconv.ParseFloat(val, 64)
            if err != nil {
                WARN.Println(err)
                return reflect.Zero(typ)
            }
            pValue := reflect.New(typ)
            pValue.Elem().SetFloat(floatValue)
            return pValue.Elem()
        }),
        Unbind: func(output map[string]string, key string, val interface{}) {
            output[key] = fmt.Sprintf("%f", val)
        },
    }

    StringBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            return reflect.ValueOf(val)
        }),
        Unbind: func(output map[string]string, name string, val interface{}) {
            output[name] = val.(string)
        },
    }

    // Booleans support a couple different value formats:
    // "true" and "false"
    // "on" and "" (a checkbox)
    // "1" and "0" (why not)
    BoolBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            v := strings.TrimSpace(strings.ToLower(val))
            switch v {
            case "true", "on", "1":
                return reflect.ValueOf(true)
            }
            // Return false by default.
            return reflect.ValueOf(false)
        }),
        Unbind: func(output map[string]string, name string, val interface{}) {
            output[name] = fmt.Sprintf("%t", val)
        },
    }

    PointerBinder = Binder{
        Bind: func(params *Params, name string, typ reflect.Type) reflect.Value {
            return Bind(params, name, typ.Elem()).Addr()
        },
        Unbind: func(output map[string]string, name string, val interface{}) {
            Unbind(output, name, reflect.ValueOf(val).Elem().Interface())
        },
    }

    TimeBinder = Binder{
        Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
            for _, f := range TimeFormats {
                if r, err := time.Parse(f, val); err == nil {
                    return reflect.ValueOf(r)
                }
            }
            return reflect.Zero(typ)
        }),
        Unbind: func(output map[string]string, name string, val interface{}) {
            var (
                t       = val.(time.Time)
                format  = DateTimeFormat
                h, m, s = t.Clock()
            )
            if h == 0 && m == 0 && s == 0 {
                format = DateFormat
            }
            output[name] = t.Format(format)
        },
    }

    MapBinder = Binder{
        Bind:   bindMap,
        Unbind: unbindMap,
    }
)

// Bind takes the name and type of the desired parameter and constructs it
// from one or more values from Params.
// Returns the zero value of the type upon any sort of failure.
func Bind(params *Params, name string, typ reflect.Type) reflect.Value {
    if binder, found := binderForType(typ); found {
        return binder.Bind(params, name, typ)
    }
    return reflect.Zero(typ)
}

func BindValue(val string, typ reflect.Type) reflect.Value {
    return Bind(&Params{Values: map[string][]string{"": {val}}}, "", typ)
}

func BindFile(fileHeader *multipart.FileHeader, typ reflect.Type) reflect.Value {
    return Bind(&Params{Files: map[string][]*multipart.FileHeader{"": {fileHeader}}}, "", typ)
}

func Unbind(output map[string]string, name string, val interface{}) {
    if binder, found := binderForType(reflect.TypeOf(val)); found {
        if binder.Unbind != nil {
            binder.Unbind(output, name, val)
        } else {
            ERROR.Printf("revel/binder: can not unbind %s=%s", name, val)
        }
    }
}