2016-05-01 21:38:51 +02:00
package echo
import (
"bytes"
2018-09-28 19:41:13 +02:00
"encoding/json"
"encoding/xml"
"errors"
2016-05-01 21:38:51 +02:00
"io"
"mime/multipart"
"net/http"
2016-09-23 07:53:44 +02:00
"net/http/httptest"
2016-05-01 21:38:51 +02:00
"reflect"
2018-09-28 19:41:13 +02:00
"strconv"
2016-05-01 21:38:51 +02:00
"strings"
"testing"
2016-12-15 20:23:14 +02:00
"time"
2016-05-01 21:38:51 +02:00
"github.com/stretchr/testify/assert"
)
type (
2016-12-15 21:08:56 +02:00
bindTestStruct struct {
2016-05-01 21:38:51 +02:00
I int
2017-04-28 20:07:20 +02:00
PtrI * int
2016-05-01 21:38:51 +02:00
I8 int8
2017-04-28 20:07:20 +02:00
PtrI8 * int8
2016-05-01 21:38:51 +02:00
I16 int16
2017-04-28 20:07:20 +02:00
PtrI16 * int16
2016-05-01 21:38:51 +02:00
I32 int32
2017-04-28 20:07:20 +02:00
PtrI32 * int32
2016-05-01 21:38:51 +02:00
I64 int64
2017-04-28 20:07:20 +02:00
PtrI64 * int64
2016-05-01 21:38:51 +02:00
UI uint
2017-04-28 20:07:20 +02:00
PtrUI * uint
2016-05-01 21:38:51 +02:00
UI8 uint8
2017-04-28 20:07:20 +02:00
PtrUI8 * uint8
2016-05-01 21:38:51 +02:00
UI16 uint16
2017-04-28 20:07:20 +02:00
PtrUI16 * uint16
2016-05-01 21:38:51 +02:00
UI32 uint32
2017-04-28 20:07:20 +02:00
PtrUI32 * uint32
2016-05-01 21:38:51 +02:00
UI64 uint64
2017-04-28 20:07:20 +02:00
PtrUI64 * uint64
2016-05-01 21:38:51 +02:00
B bool
2017-04-28 20:07:20 +02:00
PtrB * bool
2016-05-01 21:38:51 +02:00
F32 float32
2017-04-28 20:07:20 +02:00
PtrF32 * float32
2016-05-01 21:38:51 +02:00
F64 float64
2017-04-28 20:07:20 +02:00
PtrF64 * float64
2016-05-01 21:38:51 +02:00
S string
2017-04-28 20:07:20 +02:00
PtrS * string
2016-05-01 21:38:51 +02:00
cantSet string
DoesntExist string
2019-06-09 18:39:54 +02:00
GoT time . Time
GoTptr * time . Time
2016-12-15 20:23:14 +02:00
T Timestamp
Tptr * Timestamp
2016-12-23 20:01:42 +02:00
SA StringArray
2016-05-01 21:38:51 +02:00
}
2020-01-08 23:40:52 +02:00
bindTestStructWithTags struct {
I int ` json:"I" form:"I" `
PtrI * int ` json:"PtrI" form:"PtrI" `
I8 int8 ` json:"I8" form:"I8" `
PtrI8 * int8 ` json:"PtrI8" form:"PtrI8" `
I16 int16 ` json:"I16" form:"I16" `
PtrI16 * int16 ` json:"PtrI16" form:"PtrI16" `
I32 int32 ` json:"I32" form:"I32" `
PtrI32 * int32 ` json:"PtrI32" form:"PtrI32" `
I64 int64 ` json:"I64" form:"I64" `
PtrI64 * int64 ` json:"PtrI64" form:"PtrI64" `
UI uint ` json:"UI" form:"UI" `
PtrUI * uint ` json:"PtrUI" form:"PtrUI" `
UI8 uint8 ` json:"UI8" form:"UI8" `
PtrUI8 * uint8 ` json:"PtrUI8" form:"PtrUI8" `
UI16 uint16 ` json:"UI16" form:"UI16" `
PtrUI16 * uint16 ` json:"PtrUI16" form:"PtrUI16" `
UI32 uint32 ` json:"UI32" form:"UI32" `
PtrUI32 * uint32 ` json:"PtrUI32" form:"PtrUI32" `
UI64 uint64 ` json:"UI64" form:"UI64" `
PtrUI64 * uint64 ` json:"PtrUI64" form:"PtrUI64" `
B bool ` json:"B" form:"B" `
PtrB * bool ` json:"PtrB" form:"PtrB" `
F32 float32 ` json:"F32" form:"F32" `
PtrF32 * float32 ` json:"PtrF32" form:"PtrF32" `
F64 float64 ` json:"F64" form:"F64" `
PtrF64 * float64 ` json:"PtrF64" form:"PtrF64" `
S string ` json:"S" form:"S" `
PtrS * string ` json:"PtrS" form:"PtrS" `
cantSet string
DoesntExist string ` json:"DoesntExist" form:"DoesntExist" `
GoT time . Time ` json:"GoT" form:"GoT" `
GoTptr * time . Time ` json:"GoTptr" form:"GoTptr" `
T Timestamp ` json:"T" form:"T" `
Tptr * Timestamp ` json:"Tptr" form:"Tptr" `
SA StringArray ` json:"SA" form:"SA" `
}
2016-12-23 20:01:42 +02:00
Timestamp time . Time
TA [ ] Timestamp
StringArray [ ] string
2017-01-16 09:13:46 +02:00
Struct struct {
Foo string
}
2016-05-01 21:38:51 +02:00
)
2016-12-15 20:23:14 +02:00
func ( t * Timestamp ) UnmarshalParam ( src string ) error {
ts , err := time . Parse ( time . RFC3339 , src )
* t = Timestamp ( ts )
return err
}
2016-12-23 20:01:42 +02:00
func ( a * StringArray ) UnmarshalParam ( src string ) error {
* a = StringArray ( strings . Split ( src , "," ) )
return nil
}
2017-01-16 09:13:46 +02:00
func ( s * Struct ) UnmarshalParam ( src string ) error {
* s = Struct {
Foo : src ,
}
return nil
}
2016-12-15 21:08:56 +02:00
func ( t bindTestStruct ) GetCantSet ( ) string {
2016-05-01 21:38:51 +02:00
return t . cantSet
}
var values = map [ string ] [ ] string {
"I" : { "0" } ,
2017-04-28 20:07:20 +02:00
"PtrI" : { "0" } ,
2016-05-01 21:38:51 +02:00
"I8" : { "8" } ,
2017-04-28 20:07:20 +02:00
"PtrI8" : { "8" } ,
2016-05-01 21:38:51 +02:00
"I16" : { "16" } ,
2017-04-28 20:07:20 +02:00
"PtrI16" : { "16" } ,
2016-05-01 21:38:51 +02:00
"I32" : { "32" } ,
2017-04-28 20:07:20 +02:00
"PtrI32" : { "32" } ,
2016-05-01 21:38:51 +02:00
"I64" : { "64" } ,
2017-04-28 20:07:20 +02:00
"PtrI64" : { "64" } ,
2016-05-01 21:38:51 +02:00
"UI" : { "0" } ,
2017-04-28 20:07:20 +02:00
"PtrUI" : { "0" } ,
2016-05-01 21:38:51 +02:00
"UI8" : { "8" } ,
2017-04-28 20:07:20 +02:00
"PtrUI8" : { "8" } ,
2016-05-01 21:38:51 +02:00
"UI16" : { "16" } ,
2017-04-28 20:07:20 +02:00
"PtrUI16" : { "16" } ,
2016-05-01 21:38:51 +02:00
"UI32" : { "32" } ,
2017-04-28 20:07:20 +02:00
"PtrUI32" : { "32" } ,
2016-05-01 21:38:51 +02:00
"UI64" : { "64" } ,
2017-04-28 20:07:20 +02:00
"PtrUI64" : { "64" } ,
2016-05-01 21:38:51 +02:00
"B" : { "true" } ,
2017-04-28 20:07:20 +02:00
"PtrB" : { "true" } ,
2016-05-01 21:38:51 +02:00
"F32" : { "32.5" } ,
2017-04-28 20:07:20 +02:00
"PtrF32" : { "32.5" } ,
2016-05-01 21:38:51 +02:00
"F64" : { "64.5" } ,
2017-04-28 20:07:20 +02:00
"PtrF64" : { "64.5" } ,
2016-05-01 21:38:51 +02:00
"S" : { "test" } ,
2017-04-28 20:07:20 +02:00
"PtrS" : { "test" } ,
2016-05-01 21:38:51 +02:00
"cantSet" : { "test" } ,
2016-12-15 20:23:14 +02:00
"T" : { "2016-12-06T19:09:05+01:00" } ,
"Tptr" : { "2016-12-06T19:09:05+01:00" } ,
2019-06-09 18:39:54 +02:00
"GoT" : { "2016-12-06T19:09:05+01:00" } ,
"GoTptr" : { "2016-12-06T19:09:05+01:00" } ,
2017-01-16 09:13:46 +02:00
"ST" : { "bar" } ,
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindJSON ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
testBindOkay ( assert , strings . NewReader ( userJSON ) , MIMEApplicationJSON )
testBindError ( assert , strings . NewReader ( invalidContent ) , MIMEApplicationJSON , & json . SyntaxError { } )
testBindError ( assert , strings . NewReader ( userJSONInvalidType ) , MIMEApplicationJSON , & json . UnmarshalTypeError { } )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindXML ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
testBindOkay ( assert , strings . NewReader ( userXML ) , MIMEApplicationXML )
testBindError ( assert , strings . NewReader ( invalidContent ) , MIMEApplicationXML , errors . New ( "" ) )
testBindError ( assert , strings . NewReader ( userXMLConvertNumberError ) , MIMEApplicationXML , & strconv . NumError { } )
testBindError ( assert , strings . NewReader ( userXMLUnsupportedTypeError ) , MIMEApplicationXML , & xml . SyntaxError { } )
testBindOkay ( assert , strings . NewReader ( userXML ) , MIMETextXML )
testBindError ( assert , strings . NewReader ( invalidContent ) , MIMETextXML , errors . New ( "" ) )
testBindError ( assert , strings . NewReader ( userXMLConvertNumberError ) , MIMETextXML , & strconv . NumError { } )
testBindError ( assert , strings . NewReader ( userXMLUnsupportedTypeError ) , MIMETextXML , & xml . SyntaxError { } )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindForm ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
testBindOkay ( assert , strings . NewReader ( userForm ) , MIMEApplicationForm )
2016-06-23 16:40:14 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodPost , "/" , strings . NewReader ( userForm ) )
2016-09-23 07:53:44 +02:00
rec := httptest . NewRecorder ( )
2016-06-23 16:40:14 +02:00
c := e . NewContext ( req , rec )
2016-09-23 07:53:44 +02:00
req . Header . Set ( HeaderContentType , MIMEApplicationForm )
2018-02-21 20:44:17 +02:00
err := c . Bind ( & [ ] struct { Field string } { } )
2018-10-14 09:18:44 +02:00
assert . Error ( err )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindQueryParams ( t * testing . T ) {
2016-06-24 04:43:05 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodGet , "/?id=1&name=Jon+Snow" , nil )
2016-09-23 07:53:44 +02:00
rec := httptest . NewRecorder ( )
2016-06-24 04:43:05 +02:00
c := e . NewContext ( req , rec )
u := new ( user )
err := c . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "Jon Snow" , u . Name )
}
}
2018-05-30 23:50:24 +02:00
func TestBindQueryParamsCaseInsensitive ( t * testing . T ) {
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodGet , "/?ID=1&NAME=Jon+Snow" , nil )
2018-05-30 23:50:24 +02:00
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
u := new ( user )
err := c . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "Jon Snow" , u . Name )
}
}
2018-05-31 00:09:39 +02:00
func TestBindQueryParamsCaseSensitivePrioritized ( t * testing . T ) {
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodGet , "/?id=1&ID=2&NAME=Jon+Snow&name=Jon+Doe" , nil )
2018-05-31 00:09:39 +02:00
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
u := new ( user )
err := c . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "Jon Doe" , u . Name )
}
}
2016-12-15 21:08:56 +02:00
func TestBindUnmarshalParam ( t * testing . T ) {
2016-12-15 20:23:14 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodGet , "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz" , nil )
2016-12-15 20:23:14 +02:00
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
2016-12-15 21:08:56 +02:00
result := struct {
2016-12-23 20:01:42 +02:00
T Timestamp ` query:"ts" `
TA [ ] Timestamp ` query:"ta" `
SA StringArray ` query:"sa" `
2017-01-16 09:13:46 +02:00
ST Struct
2016-12-15 21:08:56 +02:00
} { }
2016-12-15 20:23:14 +02:00
err := c . Bind ( & result )
2016-12-23 20:01:42 +02:00
ts := Timestamp ( time . Date ( 2016 , 12 , 6 , 19 , 9 , 5 , 0 , time . UTC ) )
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
if assert . NoError ( err ) {
// assert.Equal( Timestamp(reflect.TypeOf(&Timestamp{}), time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
assert . Equal ( ts , result . T )
assert . Equal ( StringArray ( [ ] string { "one" , "two" , "three" } ) , result . SA )
assert . Equal ( [ ] Timestamp { ts , ts } , result . TA )
assert . Equal ( Struct { "baz" } , result . ST )
2016-12-15 20:23:14 +02:00
}
}
2019-06-09 18:39:54 +02:00
func TestBindUnmarshalText ( t * testing . T ) {
e := New ( )
req := httptest . NewRequest ( GET , "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz" , nil )
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
result := struct {
T time . Time ` query:"ts" `
TA [ ] time . Time ` query:"ta" `
SA StringArray ` query:"sa" `
ST Struct
} { }
err := c . Bind ( & result )
ts := time . Date ( 2016 , 12 , 6 , 19 , 9 , 5 , 0 , time . UTC )
if assert . NoError ( t , err ) {
// assert.Equal(t, Timestamp(reflect.TypeOf(&Timestamp{}), time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
assert . Equal ( t , ts , result . T )
assert . Equal ( t , StringArray ( [ ] string { "one" , "two" , "three" } ) , result . SA )
assert . Equal ( t , [ ] time . Time { ts , ts } , result . TA )
assert . Equal ( t , Struct { "baz" } , result . ST )
}
}
2016-12-15 21:08:56 +02:00
func TestBindUnmarshalParamPtr ( t * testing . T ) {
2016-12-15 20:23:14 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodGet , "/?ts=2016-12-06T19:09:05Z" , nil )
2016-12-15 20:23:14 +02:00
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
2016-12-15 21:08:56 +02:00
result := struct {
2016-12-15 20:23:14 +02:00
Tptr * Timestamp ` query:"ts" `
2016-12-15 21:08:56 +02:00
} { }
2016-12-15 20:23:14 +02:00
err := c . Bind ( & result )
if assert . NoError ( t , err ) {
assert . Equal ( t , Timestamp ( time . Date ( 2016 , 12 , 6 , 19 , 9 , 5 , 0 , time . UTC ) ) , * result . Tptr )
}
}
2019-06-09 18:39:54 +02:00
func TestBindUnmarshalTextPtr ( t * testing . T ) {
e := New ( )
req := httptest . NewRequest ( GET , "/?ts=2016-12-06T19:09:05Z" , nil )
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
result := struct {
Tptr * time . Time ` query:"ts" `
} { }
err := c . Bind ( & result )
if assert . NoError ( t , err ) {
assert . Equal ( t , time . Date ( 2016 , 12 , 6 , 19 , 9 , 5 , 0 , time . UTC ) , * result . Tptr )
}
}
2016-12-15 21:08:56 +02:00
func TestBindMultipartForm ( t * testing . T ) {
2016-05-01 21:38:51 +02:00
body := new ( bytes . Buffer )
mw := multipart . NewWriter ( body )
mw . WriteField ( "id" , "1" )
mw . WriteField ( "name" , "Jon Snow" )
mw . Close ( )
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
testBindOkay ( assert , body , mw . FormDataContentType ( ) )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindUnsupportedMediaType ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
testBindError ( assert , strings . NewReader ( invalidContent ) , MIMEApplicationJSON , & json . SyntaxError { } )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindbindData ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
2016-12-15 21:08:56 +02:00
ts := new ( bindTestStruct )
2016-12-10 18:38:27 +02:00
b := new ( DefaultBinder )
b . bindData ( ts , values , "form" )
2018-10-14 09:18:44 +02:00
assertBindTestStruct ( assert , ts )
2016-05-01 21:38:51 +02:00
}
2019-06-21 15:12:55 +02:00
func TestBindParam ( t * testing . T ) {
e := New ( )
req := httptest . NewRequest ( GET , "/" , nil )
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
c . SetPath ( "/users/:id/:name" )
c . SetParamNames ( "id" , "name" )
c . SetParamValues ( "1" , "Jon Snow" )
u := new ( user )
err := c . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "Jon Snow" , u . Name )
}
// Second test for the absence of a param
c2 := e . NewContext ( req , rec )
c2 . SetPath ( "/users/:id" )
c2 . SetParamNames ( "id" )
c2 . SetParamValues ( "1" )
u = new ( user )
err = c2 . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "" , u . Name )
}
// Bind something with param and post data payload
body := bytes . NewBufferString ( ` { "name": "Jon Snow" } ` )
e2 := New ( )
req2 := httptest . NewRequest ( POST , "/" , body )
req2 . Header . Set ( HeaderContentType , MIMEApplicationJSON )
rec2 := httptest . NewRecorder ( )
c3 := e2 . NewContext ( req2 , rec2 )
c3 . SetPath ( "/users/:id" )
c3 . SetParamNames ( "id" )
c3 . SetParamValues ( "1" )
u = new ( user )
err = c3 . Bind ( u )
if assert . NoError ( t , err ) {
assert . Equal ( t , 1 , u . ID )
assert . Equal ( t , "Jon Snow" , u . Name )
}
}
2018-05-01 11:18:55 +02:00
func TestBindUnmarshalTypeError ( t * testing . T ) {
body := bytes . NewBufferString ( ` { "id": "text" } ` )
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodPost , "/" , body )
2018-05-01 11:18:55 +02:00
req . Header . Set ( HeaderContentType , MIMEApplicationJSON )
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
u := new ( user )
err := c . Bind ( u )
2018-09-28 19:41:13 +02:00
he := & HTTPError { Code : http . StatusBadRequest , Message : "Unmarshal type error: expected=int, got=string, field=id, offset=14" , Internal : err . ( * HTTPError ) . Internal }
2018-05-01 11:18:55 +02:00
assert . Equal ( t , he , err )
}
2016-12-15 21:08:56 +02:00
func TestBindSetWithProperType ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
2016-12-15 21:08:56 +02:00
ts := new ( bindTestStruct )
2016-05-01 21:38:51 +02:00
typ := reflect . TypeOf ( ts ) . Elem ( )
val := reflect . ValueOf ( ts ) . Elem ( )
for i := 0 ; i < typ . NumField ( ) ; i ++ {
typeField := typ . Field ( i )
structField := val . Field ( i )
if ! structField . CanSet ( ) {
continue
}
if len ( values [ typeField . Name ] ) == 0 {
continue
}
val := values [ typeField . Name ] [ 0 ]
err := setWithProperType ( typeField . Type . Kind ( ) , val , structField )
2018-10-14 09:18:44 +02:00
assert . NoError ( err )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
assertBindTestStruct ( assert , ts )
2016-05-01 21:38:51 +02:00
type foo struct {
Bar bytes . Buffer
}
v := & foo { }
typ = reflect . TypeOf ( v ) . Elem ( )
val = reflect . ValueOf ( v ) . Elem ( )
2018-10-14 09:18:44 +02:00
assert . Error ( setWithProperType ( typ . Field ( 0 ) . Type . Kind ( ) , "5" , val . Field ( 0 ) ) )
2016-05-01 21:38:51 +02:00
}
2016-12-15 21:08:56 +02:00
func TestBindSetFields ( t * testing . T ) {
2018-10-14 09:18:44 +02:00
assert := assert . New ( t )
2016-12-15 21:08:56 +02:00
ts := new ( bindTestStruct )
2016-05-01 21:38:51 +02:00
val := reflect . ValueOf ( ts ) . Elem ( )
// Int
2018-10-14 09:18:44 +02:00
if assert . NoError ( setIntField ( "5" , 0 , val . FieldByName ( "I" ) ) ) {
assert . Equal ( 5 , ts . I )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
if assert . NoError ( setIntField ( "" , 0 , val . FieldByName ( "I" ) ) ) {
assert . Equal ( 0 , ts . I )
2016-05-01 21:38:51 +02:00
}
// Uint
2018-10-14 09:18:44 +02:00
if assert . NoError ( setUintField ( "10" , 0 , val . FieldByName ( "UI" ) ) ) {
assert . Equal ( uint ( 10 ) , ts . UI )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
if assert . NoError ( setUintField ( "" , 0 , val . FieldByName ( "UI" ) ) ) {
assert . Equal ( uint ( 0 ) , ts . UI )
2016-05-01 21:38:51 +02:00
}
// Float
2018-10-14 09:18:44 +02:00
if assert . NoError ( setFloatField ( "15.5" , 0 , val . FieldByName ( "F32" ) ) ) {
assert . Equal ( float32 ( 15.5 ) , ts . F32 )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
if assert . NoError ( setFloatField ( "" , 0 , val . FieldByName ( "F32" ) ) ) {
assert . Equal ( float32 ( 0.0 ) , ts . F32 )
2016-05-01 21:38:51 +02:00
}
// Bool
2018-10-14 09:18:44 +02:00
if assert . NoError ( setBoolField ( "true" , val . FieldByName ( "B" ) ) ) {
assert . Equal ( true , ts . B )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
if assert . NoError ( setBoolField ( "" , val . FieldByName ( "B" ) ) ) {
assert . Equal ( false , ts . B )
2016-05-01 21:38:51 +02:00
}
2016-12-15 20:23:14 +02:00
2016-12-23 20:01:42 +02:00
ok , err := unmarshalFieldNonPtr ( "2016-12-06T19:09:05Z" , val . FieldByName ( "T" ) )
2018-10-14 09:18:44 +02:00
if assert . NoError ( err ) {
assert . Equal ( ok , true )
assert . Equal ( Timestamp ( time . Date ( 2016 , 12 , 6 , 19 , 9 , 5 , 0 , time . UTC ) ) , ts . T )
2016-12-15 20:23:14 +02:00
}
2016-05-01 21:38:51 +02:00
}
2020-01-08 23:40:52 +02:00
func BenchmarkBindbindData ( b * testing . B ) {
b . ReportAllocs ( )
assert := assert . New ( b )
ts := new ( bindTestStruct )
binder := new ( DefaultBinder )
var err error
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
err = binder . bindData ( ts , values , "form" )
}
assert . NoError ( err )
assertBindTestStruct ( assert , ts )
}
func BenchmarkBindbindDataWithTags ( b * testing . B ) {
b . ReportAllocs ( )
assert := assert . New ( b )
ts := new ( bindTestStructWithTags )
binder := new ( DefaultBinder )
var err error
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
err = binder . bindData ( ts , values , "form" )
}
assert . NoError ( err )
assertBindTestStruct ( assert , ( * bindTestStruct ) ( ts ) )
}
2018-10-14 09:18:44 +02:00
func assertBindTestStruct ( a * assert . Assertions , ts * bindTestStruct ) {
a . Equal ( 0 , ts . I )
a . Equal ( int8 ( 8 ) , ts . I8 )
a . Equal ( int16 ( 16 ) , ts . I16 )
a . Equal ( int32 ( 32 ) , ts . I32 )
a . Equal ( int64 ( 64 ) , ts . I64 )
a . Equal ( uint ( 0 ) , ts . UI )
a . Equal ( uint8 ( 8 ) , ts . UI8 )
a . Equal ( uint16 ( 16 ) , ts . UI16 )
a . Equal ( uint32 ( 32 ) , ts . UI32 )
a . Equal ( uint64 ( 64 ) , ts . UI64 )
a . Equal ( true , ts . B )
a . Equal ( float32 ( 32.5 ) , ts . F32 )
a . Equal ( float64 ( 64.5 ) , ts . F64 )
a . Equal ( "test" , ts . S )
a . Equal ( "" , ts . GetCantSet ( ) )
2016-05-01 21:38:51 +02:00
}
2018-10-14 09:18:44 +02:00
func testBindOkay ( assert * assert . Assertions , r io . Reader , ctype string ) {
2016-05-01 21:38:51 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodPost , "/" , r )
2016-09-23 07:53:44 +02:00
rec := httptest . NewRecorder ( )
2016-05-01 21:38:51 +02:00
c := e . NewContext ( req , rec )
2016-09-23 07:53:44 +02:00
req . Header . Set ( HeaderContentType , ctype )
2016-05-01 21:38:51 +02:00
u := new ( user )
err := c . Bind ( u )
2018-10-14 09:18:44 +02:00
if assert . NoError ( err ) {
assert . Equal ( 1 , u . ID )
assert . Equal ( "Jon Snow" , u . Name )
2016-05-01 21:38:51 +02:00
}
}
2018-10-14 09:18:44 +02:00
func testBindError ( assert * assert . Assertions , r io . Reader , ctype string , expectedInternal error ) {
2016-05-01 21:38:51 +02:00
e := New ( )
2018-10-14 17:16:58 +02:00
req := httptest . NewRequest ( http . MethodPost , "/" , r )
2016-09-23 07:53:44 +02:00
rec := httptest . NewRecorder ( )
2016-05-01 21:38:51 +02:00
c := e . NewContext ( req , rec )
2016-09-23 07:53:44 +02:00
req . Header . Set ( HeaderContentType , ctype )
2016-05-01 21:38:51 +02:00
u := new ( user )
err := c . Bind ( u )
switch {
2017-02-28 22:04:29 +02:00
case strings . HasPrefix ( ctype , MIMEApplicationJSON ) , strings . HasPrefix ( ctype , MIMEApplicationXML ) , strings . HasPrefix ( ctype , MIMETextXML ) ,
2016-05-01 21:38:51 +02:00
strings . HasPrefix ( ctype , MIMEApplicationForm ) , strings . HasPrefix ( ctype , MIMEMultipartForm ) :
2018-10-14 09:18:44 +02:00
if assert . IsType ( new ( HTTPError ) , err ) {
assert . Equal ( http . StatusBadRequest , err . ( * HTTPError ) . Code )
assert . IsType ( expectedInternal , err . ( * HTTPError ) . Internal )
2016-05-01 21:38:51 +02:00
}
default :
2018-10-14 09:18:44 +02:00
if assert . IsType ( new ( HTTPError ) , err ) {
assert . Equal ( ErrUnsupportedMediaType , err )
assert . IsType ( expectedInternal , err . ( * HTTPError ) . Internal )
2016-05-01 21:38:51 +02:00
}
}
}
2020-11-12 12:28:45 +02:00
func TestDefaultBinder_BindToStructFromMixedSources ( t * testing . T ) {
// tests to check binding behaviour when multiple sources path params, query params and request body are in use
// binding is done in steps and one source could overwrite previous source binded data
// these tests are to document this behaviour and detect further possible regressions when bind implementation is changed
type Node struct {
ID int ` json:"id" `
Node string ` json:"node" `
}
var testCases = [ ] struct {
name string
givenURL string
givenContent io . Reader
givenMethod string
whenBindTarget interface { }
whenNoPathParams bool
expect interface { }
expectError string
} {
{
name : "ok, POST bind to struct with: path param + query param + empty body" ,
givenMethod : http . MethodPost ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` { "id": 1} ` ) ,
expect : & Node { ID : 1 , Node : "xxx" } , // in current implementation query params has higher priority than path params
} ,
{
name : "ok, POST bind to struct with: path param + empty body" ,
givenMethod : http . MethodPost ,
givenURL : "/api/real_node/endpoint" ,
givenContent : strings . NewReader ( ` { "id": 1} ` ) ,
expect : & Node { ID : 1 , Node : "real_node" } ,
} ,
{
name : "ok, POST bind to struct with path + query + body = body has priority" ,
givenMethod : http . MethodPost ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` { "id": 1, "node": "zzz"} ` ) ,
expect : & Node { ID : 1 , Node : "zzz" } , // field value from content has higher priority
} ,
{
name : "nok, POST body bind failure" ,
givenMethod : http . MethodPost ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` { ` ) ,
expect : & Node { ID : 0 , Node : "xxx" } , // query binding has already modified bind target
expectError : "code=400, message=unexpected EOF, internal=unexpected EOF" ,
} ,
{
name : "nok, GET body bind failure - trying to bind json array to struct" ,
givenMethod : http . MethodGet ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` [ { "id": 1}] ` ) ,
expect : & Node { ID : 0 , Node : "xxx" } , // query binding has already modified bind target
expectError : "code=400, message=Unmarshal type error: expected=echo.Node, got=array, field=, offset=1, internal=json: cannot unmarshal array into Go value of type echo.Node" ,
} ,
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
name : "nok, GET query params bind failure - trying to bind json array to slice" ,
givenMethod : http . MethodGet ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` [ { "id": 1}] ` ) ,
whenNoPathParams : true ,
whenBindTarget : & [ ] Node { } ,
expect : & [ ] Node { } ,
expectError : "code=400, message=binding element must be a struct, internal=binding element must be a struct" ,
} ,
{ // binding path params interferes with body. b.BindBody() should be used to bind only body to slice
name : "nok, GET path params bind failure - trying to bind json array to slice" ,
givenMethod : http . MethodGet ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenContent : strings . NewReader ( ` [ { "id": 1}] ` ) ,
whenBindTarget : & [ ] Node { } ,
expect : & [ ] Node { } ,
expectError : "code=400, message=binding element must be a struct, internal=binding element must be a struct" ,
} ,
{
name : "ok, GET body bind json array to slice" ,
givenMethod : http . MethodGet ,
givenURL : "/api/real_node/endpoint" ,
givenContent : strings . NewReader ( ` [ { "id": 1}] ` ) ,
whenNoPathParams : true ,
whenBindTarget : & [ ] Node { } ,
expect : & [ ] Node { { ID : 1 , Node : "" } } ,
expectError : "" ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
e := New ( )
// assume route we are testing is "/api/:node/endpoint?some_query_params=here"
req := httptest . NewRequest ( tc . givenMethod , tc . givenURL , tc . givenContent )
req . Header . Set ( HeaderContentType , MIMEApplicationJSON )
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
if ! tc . whenNoPathParams {
c . SetParamNames ( "node" )
c . SetParamValues ( "real_node" )
}
var bindTarget interface { }
if tc . whenBindTarget != nil {
bindTarget = tc . whenBindTarget
} else {
bindTarget = & Node { }
}
b := new ( DefaultBinder )
err := b . Bind ( bindTarget , c )
if tc . expectError != "" {
assert . EqualError ( t , err , tc . expectError )
} else {
assert . NoError ( t , err )
}
assert . Equal ( t , tc . expect , bindTarget )
} )
}
}
func TestDefaultBinder_BindBody ( t * testing . T ) {
// tests to check binding behaviour when multiple sources path params, query params and request body are in use
// generally when binding from request body - URL and path params are ignored - unless form is being binded.
// these tests are to document this behaviour and detect further possible regressions when bind implementation is changed
type Node struct {
ID int ` json:"id" xml:"id" `
Node string ` json:"node" xml:"node" `
}
type Nodes struct {
Nodes [ ] Node ` xml:"node" form:"node" `
}
var testCases = [ ] struct {
name string
givenURL string
givenContent io . Reader
givenMethod string
givenContentType string
whenNoPathParams bool
whenBindTarget interface { }
expect interface { }
expectError string
} {
{
name : "ok, JSON POST bind to struct with: path + query + empty field in body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` { "id": 1} ` ) ,
expect : & Node { ID : 1 , Node : "" } , // path params or query params should not interfere with body
} ,
{
name : "ok, JSON POST bind to struct with: path + query + body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` { "id": 1, "node": "zzz"} ` ) ,
expect : & Node { ID : 1 , Node : "zzz" } , // field value from content has higher priority
} ,
{
name : "ok, JSON POST body bind json array to slice (has matching path/query params)" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` [ { "id": 1}] ` ) ,
whenNoPathParams : true ,
whenBindTarget : & [ ] Node { } ,
expect : & [ ] Node { { ID : 1 , Node : "" } } ,
expectError : "" ,
} ,
{ // rare case as GET is not usually used to send request body
name : "ok, JSON GET bind to struct with: path + query + empty field in body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodGet ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` { "id": 1} ` ) ,
expect : & Node { ID : 1 , Node : "" } , // path params or query params should not interfere with body
} ,
{ // rare case as GET is not usually used to send request body
name : "ok, JSON GET bind to struct with: path + query + body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodGet ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` { "id": 1, "node": "zzz"} ` ) ,
expect : & Node { ID : 1 , Node : "zzz" } , // field value from content has higher priority
} ,
{
name : "nok, JSON POST body bind failure" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationJSON ,
givenContent : strings . NewReader ( ` { ` ) ,
expect : & Node { ID : 0 , Node : "" } ,
expectError : "code=400, message=unexpected EOF, internal=unexpected EOF" ,
} ,
{
name : "ok, XML POST bind to struct with: path + query + empty body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationXML ,
givenContent : strings . NewReader ( ` <node><id>1</id><node>yyy</node></node> ` ) ,
expect : & Node { ID : 1 , Node : "yyy" } ,
} ,
{
name : "ok, XML POST bind array to slice with: path + query + body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationXML ,
givenContent : strings . NewReader ( ` <nodes><node><id>1</id><node>yyy</node></node></nodes> ` ) ,
whenBindTarget : & Nodes { } ,
expect : & Nodes { Nodes : [ ] Node { { ID : 1 , Node : "yyy" } } } ,
} ,
{
name : "nok, XML POST bind failure" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationXML ,
givenContent : strings . NewReader ( ` <node>< ` ) ,
expect : & Node { ID : 0 , Node : "" } ,
expectError : "code=400, message=Syntax error: line=1, error=XML syntax error on line 1: unexpected EOF, internal=XML syntax error on line 1: unexpected EOF" ,
} ,
{
name : "ok, FORM POST bind to struct with: path + query + empty body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationForm ,
givenContent : strings . NewReader ( ` id=1&node=yyy ` ) ,
expect : & Node { ID : 1 , Node : "yyy" } ,
} ,
{
// NB: form values are taken from BOTH body and query for POST/PUT/PATCH by standard library implementation
// See: https://golang.org/pkg/net/http/#Request.ParseForm
name : "ok, FORM POST bind to struct with: path + query + empty field in body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMEApplicationForm ,
givenContent : strings . NewReader ( ` id=1 ` ) ,
expect : & Node { ID : 1 , Node : "xxx" } ,
} ,
{
// NB: form values are taken from query by standard library implementation
// See: https://golang.org/pkg/net/http/#Request.ParseForm
name : "ok, FORM GET bind to struct with: path + query + empty field in body" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodGet ,
givenContentType : MIMEApplicationForm ,
givenContent : strings . NewReader ( ` id=1 ` ) ,
expect : & Node { ID : 0 , Node : "xxx" } , // 'xxx' is taken from URL and body is not used with GET by implementation
} ,
{
name : "nok, unsupported content type" ,
givenURL : "/api/real_node/endpoint?node=xxx" ,
givenMethod : http . MethodPost ,
givenContentType : MIMETextPlain ,
givenContent : strings . NewReader ( ` <html></html> ` ) ,
expect : & Node { ID : 0 , Node : "" } ,
expectError : "code=415, message=Unsupported Media Type" ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
e := New ( )
// assume route we are testing is "/api/:node/endpoint?some_query_params=here"
req := httptest . NewRequest ( tc . givenMethod , tc . givenURL , tc . givenContent )
switch tc . givenContentType {
case MIMEApplicationXML :
req . Header . Set ( HeaderContentType , MIMEApplicationXML )
case MIMEApplicationForm :
req . Header . Set ( HeaderContentType , MIMEApplicationForm )
case MIMEApplicationJSON :
req . Header . Set ( HeaderContentType , MIMEApplicationJSON )
}
rec := httptest . NewRecorder ( )
c := e . NewContext ( req , rec )
if ! tc . whenNoPathParams {
c . SetParamNames ( "node" )
c . SetParamValues ( "real_node" )
}
var bindTarget interface { }
if tc . whenBindTarget != nil {
bindTarget = tc . whenBindTarget
} else {
bindTarget = & Node { }
}
b := new ( DefaultBinder )
err := b . BindBody ( c , bindTarget )
if tc . expectError != "" {
assert . EqualError ( t , err , tc . expectError )
} else {
assert . NoError ( t , err )
}
assert . Equal ( t , tc . expect , bindTarget )
} )
}
}