2016-05-31 10:05:42 +02:00
// Package bindings source code from https://github.com/monoculum/formame.
2016-05-30 16:08:09 +02:00
package bindings
import (
"encoding"
"errors"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"github.com/kataras/iris/context"
)
const tagName = "form"
// A pathMap holds the values of a map with its key and values correspondent
type pathMap struct {
m reflect . Value
key string
value reflect . Value
path string
}
// a pathMaps holds the values for each key
type pathMaps [ ] * pathMap
// find find and get the value by the given key
func ( ma pathMaps ) find ( id reflect . Value , key string ) * pathMap {
for _ , v := range ma {
if v . m == id && v . key == key {
return v
}
}
return nil
}
// A decoder holds the values from form, the 'reflect' value of main struct
// and the 'reflect' value of current path
type decoder struct {
main reflect . Value
curr reflect . Value
value string
values [ ] string
path string
field string
index int
maps pathMaps
}
// Decode decodes the url.Values into a element that must be a pointer to a type provided by argument
func Decode ( vs url . Values , dst interface { } ) error {
main := reflect . ValueOf ( dst )
if main . Kind ( ) != reflect . Ptr {
return fmt . Errorf ( tagName + ": the value passed for decode is not a pointer but a %v" , main . Kind ( ) )
}
dec := & decoder { main : main . Elem ( ) }
// iterate over the form's values and decode it
for k , v := range vs {
dec . path = k
dec . field = k
dec . values = v
dec . value = v [ 0 ]
if dec . value != "" {
if err := dec . begin ( ) ; err != nil {
return err
}
}
}
// set values of each maps
for _ , v := range dec . maps {
key := v . m . Type ( ) . Key ( )
switch key . Kind ( ) {
case reflect . String :
// the key is a string
v . m . SetMapIndex ( reflect . ValueOf ( v . key ) , v . value )
default :
// must to implement the TextUnmarshaler interface for to can to decode the map's key
var val reflect . Value
if key . Kind ( ) == reflect . Ptr {
val = reflect . New ( key . Elem ( ) )
} else {
val = reflect . New ( key ) . Elem ( )
}
dec . value = v . key
if ok , err := dec . unmarshalText ( val ) ; ! ok {
return fmt . Errorf ( tagName + ": the key with %s type (%v) in the path %v should implements the TextUnmarshaler interface for to can decode it" , key , v . m . Type ( ) , v . path )
} else if err != nil {
return fmt . Errorf ( tagName + ": an error has occured in the UnmarshalText method for type %s: %s" , key , err )
}
v . m . SetMapIndex ( val , v . value )
}
}
dec . maps = make ( pathMaps , 0 )
return nil
}
// begin prepare the current path to walk through it
func ( dec * decoder ) begin ( ) ( err error ) {
dec . curr = dec . main
fields := strings . Split ( dec . field , "." )
for i , field := range fields {
b := strings . IndexAny ( field , "[" )
if b != - 1 {
// is a array
e := strings . IndexAny ( field , "]" )
if e == - 1 {
return errors . New ( tagName + ": bad syntax array" )
}
dec . field = field [ : b ]
if dec . index , err = strconv . Atoi ( field [ b + 1 : e ] ) ; err != nil {
return errors . New ( tagName + ": the index of array is not a number" )
}
if len ( fields ) == i + 1 {
return dec . end ( )
}
if err = dec . walk ( ) ; err != nil {
return
}
} else {
// not is a array
dec . field = field
dec . index = - 1
if len ( fields ) == i + 1 {
return dec . end ( )
}
if err = dec . walk ( ) ; err != nil {
return
}
}
}
return
}
// walk traverses the current path until to the last field
func ( dec * decoder ) walk ( ) error {
// check if is a struct or map
switch dec . curr . Kind ( ) {
case reflect . Struct :
if err := dec . findStructField ( ) ; err != nil {
return err
}
case reflect . Map :
dec . currentMap ( )
}
// check if the struct or map is a interface
if dec . curr . Kind ( ) == reflect . Interface {
dec . curr = dec . curr . Elem ( )
}
// check if the struct or map is a pointer
if dec . curr . Kind ( ) == reflect . Ptr {
if dec . curr . IsNil ( ) {
dec . curr . Set ( reflect . New ( dec . curr . Type ( ) . Elem ( ) ) )
}
dec . curr = dec . curr . Elem ( )
}
// finally, check if there are access to slice/array or not...
if dec . index != - 1 {
switch dec . curr . Kind ( ) {
case reflect . Slice , reflect . Array :
if dec . curr . Len ( ) <= dec . index {
dec . expandSlice ( dec . index + 1 )
}
dec . curr = dec . curr . Index ( dec . index )
default :
return fmt . Errorf ( tagName + ": the field \"%v\" in path \"%v\" has a index for array but it is not" , dec . field , dec . path )
}
}
return nil
}
// end finds the last field for decode its value correspondent
func ( dec * decoder ) end ( ) error {
if dec . curr . Kind ( ) == reflect . Struct {
if err := dec . findStructField ( ) ; err != nil {
return err
}
}
if dec . value == "" {
return nil
}
return dec . decode ( )
}
// decode sets the value in the last field found by end function
func ( dec * decoder ) decode ( ) error {
if ok , err := dec . unmarshalText ( dec . curr ) ; ok || err != nil {
return err
}
switch dec . curr . Kind ( ) {
case reflect . Map :
dec . currentMap ( )
return dec . decode ( )
case reflect . Slice , reflect . Array :
if dec . index == - 1 {
// not has index, so to decode all values in the slice/array
dec . expandSlice ( len ( dec . values ) )
tmp := dec . curr
for i , v := range dec . values {
dec . curr = tmp . Index ( i )
dec . value = v
if err := dec . decode ( ) ; err != nil {
return err
}
}
} else {
// has index, so to decode value by index indicated
if dec . curr . Len ( ) <= dec . index {
dec . expandSlice ( dec . index + 1 )
}
dec . curr = dec . curr . Index ( dec . index )
return dec . decode ( )
}
case reflect . String :
dec . curr . SetString ( dec . value )
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
if num , err := strconv . ParseInt ( dec . value , 10 , 64 ) ; err != nil {
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" should be a valid signed integer number" , dec . field , dec . path )
} else {
dec . curr . SetInt ( num )
}
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 , reflect . Uintptr :
if num , err := strconv . ParseUint ( dec . value , 10 , 64 ) ; err != nil {
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" should be a valid unsigned integer number" , dec . field , dec . path )
} else {
dec . curr . SetUint ( num )
}
case reflect . Float32 , reflect . Float64 :
if num , err := strconv . ParseFloat ( dec . value , dec . curr . Type ( ) . Bits ( ) ) ; err != nil {
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" should be a valid float number" , dec . field , dec . path )
} else {
dec . curr . SetFloat ( num )
}
case reflect . Bool :
switch dec . value {
case "true" , "on" , "1" :
dec . curr . SetBool ( true )
case "false" , "off" , "0" :
dec . curr . SetBool ( false )
default :
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" is not a valid boolean" , dec . field , dec . path )
}
case reflect . Interface :
dec . curr . Set ( reflect . ValueOf ( dec . value ) )
case reflect . Ptr :
dec . curr . Set ( reflect . New ( dec . curr . Type ( ) . Elem ( ) ) )
dec . curr = dec . curr . Elem ( )
return dec . decode ( )
case reflect . Struct :
switch dec . curr . Interface ( ) . ( type ) {
case time . Time :
t , err := time . Parse ( "2006-01-02" , dec . value )
if err != nil {
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" is not a valid datetime" , dec . field , dec . path )
}
dec . curr . Set ( reflect . ValueOf ( t ) )
case url . URL :
u , err := url . Parse ( dec . value )
if err != nil {
return fmt . Errorf ( tagName + ": the value of field \"%v\" in path \"%v\" is not a valid url" , dec . field , dec . path )
}
dec . curr . Set ( reflect . ValueOf ( * u ) )
default :
return fmt . Errorf ( tagName + ": not supported type for field \"%v\" in path \"%v\"" , dec . field , dec . path )
}
default :
return fmt . Errorf ( tagName + ": not supported type for field \"%v\" in path \"%v\"" , dec . field , dec . path )
}
return nil
}
// findField finds a field by its name, if it is not found,
// then retry the search examining the tag "form" of every field of struct
func ( dec * decoder ) findStructField ( ) error {
var anon reflect . Value
num := dec . curr . NumField ( )
for i := 0 ; i < num ; i ++ {
field := dec . curr . Type ( ) . Field ( i )
if field . Name == dec . field {
// check if the field's name is equal
dec . curr = dec . curr . Field ( i )
return nil
} else if field . Anonymous {
// if the field is a anonymous struct, then iterate over its fields
tmp := dec . curr
dec . curr = dec . curr . FieldByIndex ( field . Index )
if err := dec . findStructField ( ) ; err != nil {
dec . curr = tmp
continue
}
// field in anonymous struct is found,
// but first it should found the field in the rest of struct
// (a field with same name in the current struct should have preference over anonymous struct)
anon = dec . curr
dec . curr = tmp
} else if dec . field == field . Tag . Get ( tagName ) {
dec . curr = dec . curr . Field ( i )
return nil
}
}
if anon . IsValid ( ) {
dec . curr = anon
return nil
}
return fmt . Errorf ( tagName + ": not found the field \"%v\" in the path \"%v\"" , dec . field , dec . path )
}
// expandSlice expands the length and capacity of the current slice
func ( dec * decoder ) expandSlice ( length int ) {
n := reflect . MakeSlice ( dec . curr . Type ( ) , length , length )
reflect . Copy ( n , dec . curr )
dec . curr . Set ( n )
}
// currentMap gets in d.curr the map concrete for decode the current value
func ( dec * decoder ) currentMap ( ) {
n := dec . curr . Type ( )
if dec . curr . IsNil ( ) {
dec . curr . Set ( reflect . MakeMap ( n ) )
m := reflect . New ( n . Elem ( ) ) . Elem ( )
dec . maps = append ( dec . maps , & pathMap { dec . curr , dec . field , m , dec . path } )
dec . curr = m
} else if a := dec . maps . find ( dec . curr , dec . field ) ; a == nil {
m := reflect . New ( n . Elem ( ) ) . Elem ( )
dec . maps = append ( dec . maps , & pathMap { dec . curr , dec . field , m , dec . path } )
dec . curr = m
} else {
dec . curr = a . value
}
}
var (
timeType = reflect . TypeOf ( time . Time { } )
timePType = reflect . TypeOf ( & time . Time { } )
)
// unmarshalText returns a boolean and error. The boolean is true if the
// value implements TextUnmarshaler, and false if not.
func ( dec * decoder ) unmarshalText ( v reflect . Value ) ( bool , error ) {
// skip if the type is time.Time
n := v . Type ( )
if n . ConvertibleTo ( timeType ) || n . ConvertibleTo ( timePType ) {
return false , nil
}
// check if implements the interface
m , ok := v . Interface ( ) . ( encoding . TextUnmarshaler )
addr := v . CanAddr ( )
if ! ok && ! addr {
return false , nil
} else if addr {
return dec . unmarshalText ( v . Addr ( ) )
}
// return result
err := m . UnmarshalText ( [ ] byte ( dec . value ) )
return true , err
}
// BindForm binds the formObject with the form data
// it supports any kind of struct
func BindForm ( ctx context . IContext , formObject interface { } ) error {
reqCtx := ctx . GetRequestCtx ( )
// first check if we have multipart form
2016-06-01 11:36:52 +02:00
multipartForm , err := reqCtx . MultipartForm ( )
2016-05-30 16:08:09 +02:00
if err == nil {
//we have multipart form
2016-06-01 11:36:52 +02:00
return ErrReadBody . With ( Decode ( multipartForm . Value , formObject ) )
2016-05-30 16:08:09 +02:00
}
// if no multipart and post arguments ( means normal form)
2016-06-02 16:48:11 +02:00
if reqCtx . PostArgs ( ) . Len ( ) == 0 && reqCtx . QueryArgs ( ) . Len ( ) == 0 {
2016-06-01 11:36:52 +02:00
return ErrReadBody . With ( ErrNoForm . Return ( ) )
}
2016-05-30 16:08:09 +02:00
2016-06-01 11:36:52 +02:00
form := make ( map [ string ] [ ] string , reqCtx . PostArgs ( ) . Len ( ) + reqCtx . QueryArgs ( ) . Len ( ) )
2016-05-30 16:08:09 +02:00
2016-06-01 11:36:52 +02:00
reqCtx . PostArgs ( ) . VisitAll ( func ( k [ ] byte , v [ ] byte ) {
key := string ( k )
value := string ( v )
// for slices
if form [ key ] != nil {
form [ key ] = append ( form [ key ] , value )
} else {
form [ key ] = [ ] string { value }
}
} )
reqCtx . QueryArgs ( ) . VisitAll ( func ( k [ ] byte , v [ ] byte ) {
key := string ( k )
value := string ( v )
// for slices
if form [ key ] != nil {
form [ key ] = append ( form [ key ] , value )
} else {
form [ key ] = [ ] string { value }
}
} )
return ErrReadBody . With ( Decode ( form , formObject ) )
2016-05-30 16:08:09 +02:00
}