sync from project
This commit is contained in:
206
magic/form.go
Normal file
206
magic/form.go
Normal file
@ -0,0 +1,206 @@
|
||||
package magic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
cachedStructFormFields = make(map[reflect.Type][]structFormFields)
|
||||
cachedUnsupportedFormFields = make(map[reflect.Type]structFormFields)
|
||||
)
|
||||
|
||||
type Form[T any] struct {
|
||||
Data T
|
||||
}
|
||||
|
||||
type structFormFields struct {
|
||||
PathKey string
|
||||
FieldKey string
|
||||
|
||||
Type reflect.Type
|
||||
MinValues, MaxValues int
|
||||
|
||||
Match *regexp.Regexp
|
||||
}
|
||||
|
||||
type UnsupportedFormType struct {
|
||||
inner reflect.Type
|
||||
}
|
||||
|
||||
func (err UnsupportedFormType) Error() string {
|
||||
return "unsupported type for Form: " + err.inner.String()
|
||||
}
|
||||
|
||||
type FormValueNotFitIn struct {
|
||||
Count int
|
||||
|
||||
field structFormFields
|
||||
}
|
||||
|
||||
func (err FormValueNotFitIn) Error() string {
|
||||
return fmt.Sprintf("Form %v is expected to has %v~%v values, got %v", err.field.PathKey, err.field.MinValues, err.field.MaxValues, err.Count)
|
||||
}
|
||||
|
||||
type InvalidFormValue struct {
|
||||
Match *regexp.Regexp
|
||||
Value string
|
||||
}
|
||||
|
||||
func (err InvalidFormValue) Error() string {
|
||||
return fmt.Sprintf("value not matched with regexp %v: %v", err.Match, err.Value)
|
||||
}
|
||||
|
||||
func findReflectFormFields(v reflect.Value) ([]structFormFields, error) {
|
||||
if cached, ok := cachedStructFormFields[v.Type()]; ok {
|
||||
return cached, nil
|
||||
}
|
||||
if cached, ok := cachedUnsupportedFormFields[v.Type()]; ok {
|
||||
return nil, UnsupportedPathValueType{inner: cached.Type}
|
||||
}
|
||||
|
||||
var fields []structFormFields
|
||||
if v.Kind() == reflect.Struct {
|
||||
t := v.Type()
|
||||
for idx := 0; idx < t.NumField(); idx++ {
|
||||
field := t.Field(idx)
|
||||
|
||||
pathKey := field.Name
|
||||
var match *regexp.Regexp
|
||||
|
||||
if tags, ok := field.Tag.Lookup("form"); ok {
|
||||
parts := strings.Split(strings.TrimSpace(tags), ",")
|
||||
if len(parts) == 0 {
|
||||
// do nothing
|
||||
} else {
|
||||
if parts[0] != "" {
|
||||
pathKey = parts[0]
|
||||
}
|
||||
|
||||
for _, part := range parts[1:] {
|
||||
part = strings.TrimSpace(part)
|
||||
if strings.HasPrefix(part, "match=") {
|
||||
part := strings.TrimPrefix(part, "match=")
|
||||
if unquoted, err := strconv.Unquote(part); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
part = unquoted
|
||||
}
|
||||
exp, err := regexp.Compile(part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
match = exp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldType := field.Type
|
||||
maxValues, minValues := 1, 1
|
||||
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
minValues = 0
|
||||
} else if fieldType.Kind() == reflect.Array {
|
||||
maxValues, minValues = fieldType.Len(), fieldType.Len()
|
||||
fieldType = fieldType.Elem()
|
||||
} else if fieldType.Kind() == reflect.Slice {
|
||||
fieldType = fieldType.Elem()
|
||||
maxValues, minValues = -1, 0
|
||||
}
|
||||
|
||||
if pathValueConvertTable[fieldType.Kind()] == nil {
|
||||
if _, ok := fieldType.MethodByName("FromString"); !ok {
|
||||
cachedUnsupportedPathValueFields[t] = field
|
||||
return nil, UnsupportedPathValueType{inner: field.Type}
|
||||
}
|
||||
}
|
||||
fields = append(fields, structFormFields{
|
||||
PathKey: pathKey,
|
||||
FieldKey: field.Name,
|
||||
|
||||
Type: fieldType,
|
||||
MaxValues: maxValues,
|
||||
MinValues: minValues,
|
||||
|
||||
Match: match,
|
||||
})
|
||||
|
||||
cachedStructFormFields[t] = fields
|
||||
}
|
||||
} else {
|
||||
return nil, UnsupportedFormType{inner: v.Type()}
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (form *Form[T]) FromRequest(r *http.Request) error {
|
||||
v := reflect.ValueOf(form).Elem().FieldByName("Data")
|
||||
fields, err := findReflectFormFields(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||
return err
|
||||
}
|
||||
q := r.PostForm
|
||||
|
||||
for _, field := range fields {
|
||||
values := q[field.PathKey]
|
||||
if len(values) < field.MinValues || (field.MaxValues > 0 && len(values) > field.MaxValues) {
|
||||
return FormValueNotFitIn{Count: len(values), field: field}
|
||||
}
|
||||
|
||||
structField := v.FieldByName(field.FieldKey)
|
||||
|
||||
if structField.Kind() == reflect.Slice && structField.Len() < len(values) {
|
||||
if structField.Cap() < len(values) {
|
||||
structField.SetCap(len(values))
|
||||
}
|
||||
structField.SetLen(len(values))
|
||||
}
|
||||
|
||||
for idx, value := range values {
|
||||
if structField.Kind() != reflect.Array && structField.Kind() != reflect.Slice {
|
||||
if idx != len(values)-1 {
|
||||
// only the last takes effect
|
||||
continue
|
||||
}
|
||||
}
|
||||
// check match regexp
|
||||
if field.Match != nil && !field.Match.MatchString(value) {
|
||||
return InvalidFormValue{Match: field.Match, Value: value}
|
||||
}
|
||||
|
||||
// convert to elem value
|
||||
convert := pathValueConvertTable[field.Type.Kind()]
|
||||
result, err := convert(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reflectValue := reflect.ValueOf(result)
|
||||
|
||||
if structField.Kind() != reflect.Array && structField.Kind() != reflect.Slice {
|
||||
if field.MinValues == 0 {
|
||||
newReflectValue := reflect.New(field.Type)
|
||||
newReflectValue.Elem().Set(reflectValue)
|
||||
reflectValue = newReflectValue
|
||||
}
|
||||
structField.Set(reflectValue)
|
||||
} else {
|
||||
structField.Index(idx).Set(reflectValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form Form[T]) TakeRequestBody() {}
|
Reference in New Issue
Block a user