mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
package web
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"reflect"
|
|
)
|
|
|
|
// Bind deserializes JSON payload from the request
|
|
func Bind(req *http.Request, v any) error {
|
|
if req.Body != nil {
|
|
m, _, err := mime.ParseMediaType(req.Header.Get("Content-type"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if m != "application/json" {
|
|
return errors.New("bad content type")
|
|
}
|
|
defer func() { _ = req.Body.Close() }()
|
|
err = json.NewDecoder(req.Body).Decode(v)
|
|
if err != nil && !errors.Is(err, io.EOF) {
|
|
return err
|
|
}
|
|
}
|
|
return validate(v)
|
|
}
|
|
|
|
type Validator interface {
|
|
Validate() error
|
|
}
|
|
|
|
func validate(obj any) error {
|
|
// First check if obj is nil, because we cannot validate those.
|
|
if obj == nil {
|
|
return nil
|
|
}
|
|
|
|
// Second, check if obj has a nil interface value.
|
|
// This is to prevent panics when obj is an instance of uninitialised struct pointer / interface.
|
|
t := reflect.TypeOf(obj)
|
|
v := reflect.ValueOf(obj)
|
|
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
return nil
|
|
}
|
|
|
|
// If type has a Validate() method - use that
|
|
if validator, ok := obj.(Validator); ok {
|
|
return validator.Validate()
|
|
}
|
|
|
|
// Otherwise, use reflection to match `binding:"Required"` struct field tags.
|
|
// Resolve all pointers and interfaces, until we get a concrete type.
|
|
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
|
|
}
|
|
}
|
|
default: // ignore
|
|
}
|
|
return nil
|
|
}
|