package input import ( "encoding" "fmt" "strconv" "time" ) // FieldBinder is an interface that everything that every type that wants to bind to an input field must implement. type FieldBinder interface { String() string // String returns the bound variable formatted in the users/current locale. SetString(string) error // SetString parses the string value in the users/current locale and sets the value of the bound variable. HTMLInputType() string // HTMLInputType returns the to be used type parameter for the HTML input element. } // FieldBindAny implements the FieldBinder interface for all basic types and the encoding.TextMarshaler, encoding.TextUnmarshaler interfaces. type FieldBindAny struct{ Value any } func (f FieldBindAny) String() string { switch v := f.Value.(type) { case *bool: return strconv.FormatBool(*v) case *string: return *v case *int: return strconv.FormatInt(int64(*v), 10) case *int8: return strconv.FormatInt(int64(*v), 10) case *int16: return strconv.FormatInt(int64(*v), 10) case *int32: return strconv.FormatInt(int64(*v), 10) case *int64: return strconv.FormatInt(int64(*v), 10) case *uint: return strconv.FormatUint(uint64(*v), 10) case *uint8: return strconv.FormatUint(uint64(*v), 10) case *uint16: return strconv.FormatUint(uint64(*v), 10) case *uint32: return strconv.FormatUint(uint64(*v), 10) case *uint64: return strconv.FormatUint(uint64(*v), 10) case *float32: return strconv.FormatFloat(float64(*v), 'f', -1, 32) // TODO: Format number in current user's locale case *float64: return strconv.FormatFloat(float64(*v), 'f', -1, 64) case *time.Time: return v.Local().Format("2006-01-02T15:04") case encoding.TextMarshaler: data, _ := v.MarshalText() // Ignore any errors, as we can't handle them here. return string(data) } return "" } func (f FieldBindAny) SetString(value string) error { switch v := f.Value.(type) { case *bool: val, err := strconv.ParseBool(value) if err != nil { return err } *v = val case *string: *v = value case *int: val, err := strconv.ParseInt(value, 10, 0) if err != nil { return err } *v = int(val) case *int8: val, err := strconv.ParseInt(value, 10, 8) if err != nil { return err } *v = int8(val) case *int16: val, err := strconv.ParseInt(value, 10, 16) if err != nil { return err } *v = int16(val) case *int32: val, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } *v = int32(val) case *int64: val, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } *v = int64(val) case *uint: val, err := strconv.ParseUint(value, 10, 0) if err != nil { return err } *v = uint(val) case *uint8: val, err := strconv.ParseUint(value, 10, 8) if err != nil { return err } *v = uint8(val) case *uint16: val, err := strconv.ParseUint(value, 10, 16) if err != nil { return err } *v = uint16(val) case *uint32: val, err := strconv.ParseUint(value, 10, 32) if err != nil { return err } *v = uint32(val) case *uint64: val, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } *v = uint64(val) case *float32: val, err := strconv.ParseFloat(value, 32) if err != nil { return err } *v = float32(val) case *float64: val, err := strconv.ParseFloat(value, 64) if err != nil { return err } *v = float64(val) case *time.Time: val, err := time.ParseInLocation("2006-01-02T15:04", value, time.Local) if err != nil { return err } *v = val case encoding.TextUnmarshaler: return v.UnmarshalText([]byte(value)) default: return fmt.Errorf("bound type %T is not supported", f.Value) } return nil } func (f FieldBindAny) HTMLInputType() string { switch f.Value.(type) { case *bool: return "text" // We will render the boolean as text, as this is an input field, not a checkbox. case *string: return "text" case *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64: return "number" case *float32, *float64: return "number" case *time.Time: return "datetime-local" case encoding.TextMarshaler, encoding.TextUnmarshaler: return "text" } return "text" }