-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbind.go
162 lines (150 loc) · 4.05 KB
/
bind.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package pine
import (
"encoding/json"
"errors"
"reflect"
"strconv"
)
var (
ErrParse = errors.New("bind: cannot parse")
ErrConvert = errors.New("bind: cannot convert")
ErrType = errors.New("bind: unexpected type")
ErrPtr = errors.New("bind: destination must be a pointer")
ErrValidation = errors.New("bind: validation failed")
)
// BindJSON binds the request body to the given interface.
// You can use this to validate the request body without adding further logic
// to your handlers.
//
// Tested with nested JSON objects and arrays
// If you notice any errors, please open an issue on Github and I will fix it right away
func (c *Ctx) BindJSON(v interface{}) error {
err := json.NewDecoder(c.Request.Body).Decode(v)
if err != nil {
return ErrParse
}
return bindData(v)
}
// BindParam binds the specified parameter value of a request.
func (c *Ctx) BindParam(key string, v interface{}) error {
param := c.Params(key)
if param == "" {
return ErrValidation
}
return bind(param, v)
}
// BindQuery binds the specified query value of a request.
func (c *Ctx) BindQuery(key string, v interface{}) error {
param := c.Query(key)
if param == "" {
return ErrValidation
}
return bind(param, v)
}
// Internal helper function to validate the bind
// requires the input in this case is the key of the param or query
// and the destination is the value of the param or query
// you can also specify the type of the destination
func bind(input string, destination interface{}) error {
// reflect the type and value of the destination
typ := reflect.TypeOf(destination)
val := reflect.ValueOf(destination)
if typ.Kind() != reflect.Ptr {
return ErrPtr
}
// Dereference pointer type to assign value
val = reflect.Indirect(val)
switch val.Kind() {
case reflect.String:
val.SetString(input)
case reflect.Int, reflect.Int64:
parsed, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return ErrConvert
}
val.SetInt(parsed)
case reflect.Float64, reflect.Float32:
parsed, err := strconv.ParseFloat(input, 64)
if err != nil {
return ErrConvert
}
val.SetFloat(parsed)
case reflect.Bool:
parsed, err := strconv.ParseBool(input)
if err != nil {
return ErrConvert
}
val.SetBool(parsed)
default:
return ErrType
}
return nil
}
// Called to the bind of the JSON body
// A future revision of this will be implemented to handle forms and XML bodies
// but the logic will pretty much be the same
func bindData(destination interface{}) error {
v := reflect.ValueOf(destination)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we can check if the value is a struct or a slice
if v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if isZeroValue(field) {
return ErrValidation
}
}
}
if v.Kind() == reflect.Slice {
length := v.Len()
for i := 0; i < length; i++ {
if isZeroValue(v.Index(i)) {
return ErrValidation
}
}
}
return nil
}
// Internal helper function to check if the value is zero
// Returns true if the value is zero and hence handled as an error since
// the unpacked value is not set
func isZeroValue(val reflect.Value) bool {
switch val.Kind() {
case reflect.String:
return val.String() == ""
case reflect.Int, reflect.Int64:
return val.Int() == 0
case reflect.Float32, reflect.Float64:
return val.Float() == 0.0
case reflect.Bool:
return !val.Bool()
case reflect.Slice, reflect.Array:
// For slices and arrays, check each element
if val.Len() == 0 {
return true
}
for i := 0; i < val.Len(); i++ {
if isZeroValue(val.Index(i)) {
return true
}
}
return false
case reflect.Map:
// Maps should be non-nil and have at least one entry
return val.Len() == 0 || val.IsNil()
case reflect.Struct:
// For nested structs, recursively bind their fields
return bindData(val.Addr().Interface()) != nil
case reflect.Ptr:
// For pointers, check if it's nil or dereference it and check its value
if val.IsNil() {
return true
}
return isZeroValue(val.Elem())
default:
// For other types, treat as zero value
return false
}
}