mirror of https://github.com/grafana/grafana
Chore: Imperative request data binding (#39837)
* rename Bind to BindMiddleware * make things private * removed unused part of data bindings * provide json and form binding helpers * add example of binding migration in login api * implement validation * fix tests * remove debug output * put new bind api into macaron pacakge * revert bind api breaking changepull/40070/head
parent
7fd7c98540
commit
3131388084
@ -0,0 +1,74 @@ |
||||
package macaron |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"reflect" |
||||
) |
||||
|
||||
// Bind deserializes JSON payload from the request
|
||||
func Bind(req *http.Request, v interface{}) error { |
||||
if req.Body != nil { |
||||
defer req.Body.Close() |
||||
err := json.NewDecoder(req.Body).Decode(v) |
||||
if err != nil && err != io.EOF { |
||||
return err |
||||
} |
||||
} |
||||
return validate(v) |
||||
} |
||||
|
||||
type Validator interface { |
||||
Validate() error |
||||
} |
||||
|
||||
func validate(obj interface{}) error { |
||||
// If type has a Validate() method - use that
|
||||
if validator, ok := obj.(Validator); ok { |
||||
return validator.Validate() |
||||
} |
||||
// Otherwise, use relfection to match `binding:"Required"` struct field tags.
|
||||
// Resolve all pointers and interfaces, until we get a concrete type.
|
||||
t := reflect.TypeOf(obj) |
||||
v := reflect.ValueOf(obj) |
||||
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { |
||||
t = t.Elem() |
||||
v = v.Elem() |
||||
} |
||||
switch v.Kind() { |
||||
// For arrays and slices - iterate over each element and validate it recursively
|
||||
case reflect.Slice, reflect.Array: |
||||
for i := 0; i < v.Len(); i++ { |
||||
e := v.Index(i).Interface() |
||||
if err := validate(e); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
// For structs - iterate over each field, check for the "Required" constraint (Macaron legacy), then validate it recursively
|
||||
case reflect.Struct: |
||||
for i := 0; i < v.NumField(); i++ { |
||||
field := t.Field(i) |
||||
value := v.Field(i) |
||||
rule := field.Tag.Get("binding") |
||||
if !value.CanInterface() { |
||||
continue |
||||
} |
||||
if rule == "Required" { |
||||
zero := reflect.Zero(field.Type).Interface() |
||||
if value.Kind() == reflect.Slice { |
||||
if value.Len() == 0 { |
||||
return fmt.Errorf("required slice %s must not be empty", field.Name) |
||||
} |
||||
} else if reflect.DeepEqual(zero, value.Interface()) { |
||||
return fmt.Errorf("required value %s must not be empty", field.Name) |
||||
} |
||||
} |
||||
if err := validate(value.Interface()); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,101 @@ |
||||
package macaron |
||||
|
||||
import ( |
||||
"errors" |
||||
"testing" |
||||
) |
||||
|
||||
type StructWithInt struct { |
||||
A int `binding:"Required"` |
||||
} |
||||
|
||||
type StructWithPrimitives struct { |
||||
A int `binding:"Required"` |
||||
B string `binding:"Required"` |
||||
C bool `binding:"Required"` |
||||
D float64 `binding:"Required"` |
||||
} |
||||
|
||||
type StructWithPrivateFields struct { |
||||
A int `binding:"Required"` // must be validated
|
||||
b int `binding:"Required"` // will not be used
|
||||
} |
||||
|
||||
type StructWithInterface struct { |
||||
A interface{} `binding:"Required"` |
||||
} |
||||
type StructWithSliceInts struct { |
||||
A []int `binding:"Required"` |
||||
} |
||||
type StructWithSliceStructs struct { |
||||
A []StructWithInt `binding:"Required"` |
||||
} |
||||
type StructWithSliceInterfaces struct { |
||||
A []interface{} `binding:"Required"` |
||||
} |
||||
type StructWithStruct struct { |
||||
A StructWithInt `binding:"Required"` |
||||
} |
||||
type StructWithStructPointer struct { |
||||
A *StructWithInt `binding:"Required"` |
||||
} |
||||
type StructWithValidation struct { |
||||
A int |
||||
} |
||||
|
||||
func (sv StructWithValidation) Validate() error { |
||||
if sv.A < 10 { |
||||
return errors.New("too small") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func TestValidationSuccess(t *testing.T) { |
||||
for _, x := range []interface{}{ |
||||
42, |
||||
"foo", |
||||
struct{ A int }{}, |
||||
StructWithInt{42}, |
||||
StructWithPrimitives{42, "foo", true, 12.34}, |
||||
StructWithPrivateFields{12, 0}, |
||||
StructWithInterface{"foo"}, |
||||
StructWithSliceInts{[]int{1, 2, 3}}, |
||||
StructWithSliceInterfaces{[]interface{}{1, 2, 3}}, |
||||
StructWithSliceStructs{[]StructWithInt{{1}, {2}}}, |
||||
StructWithStruct{StructWithInt{3}}, |
||||
StructWithStructPointer{&StructWithInt{3}}, |
||||
StructWithValidation{42}, |
||||
} { |
||||
if err := validate(x); err != nil { |
||||
t.Error("Validation failed:", x, err) |
||||
} |
||||
} |
||||
} |
||||
func TestValidationFailure(t *testing.T) { |
||||
for i, x := range []interface{}{ |
||||
StructWithInt{0}, |
||||
StructWithPrimitives{0, "foo", true, 12.34}, |
||||
StructWithPrimitives{42, "", true, 12.34}, |
||||
StructWithPrimitives{42, "foo", false, 12.34}, |
||||
StructWithPrimitives{42, "foo", true, 0}, |
||||
StructWithPrivateFields{0, 1}, |
||||
StructWithInterface{}, |
||||
StructWithInterface{nil}, |
||||
StructWithSliceInts{}, |
||||
StructWithSliceInts{[]int{}}, |
||||
StructWithSliceStructs{[]StructWithInt{}}, |
||||
StructWithSliceStructs{[]StructWithInt{{0}, {2}}}, |
||||
StructWithSliceStructs{[]StructWithInt{{2}, {0}}}, |
||||
StructWithSliceInterfaces{[]interface{}{}}, |
||||
StructWithSliceInterfaces{nil}, |
||||
StructWithStruct{StructWithInt{}}, |
||||
StructWithStruct{StructWithInt{0}}, |
||||
StructWithStructPointer{}, |
||||
StructWithStructPointer{&StructWithInt{}}, |
||||
StructWithValidation{2}, |
||||
} { |
||||
if err := validate(x); err == nil { |
||||
t.Error("Validation should fail:", i, x) |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue