179 lines
3.9 KiB
Go
179 lines
3.9 KiB
Go
package magic
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
cachedStructPathValueFields = make(map[reflect.Type][]structPathValueFields)
|
|
cachedUnsupportedPathValueFields = make(map[reflect.Type]reflect.StructField)
|
|
)
|
|
|
|
type UnsupportedPathValueType struct {
|
|
inner reflect.Type
|
|
}
|
|
|
|
func (err UnsupportedPathValueType) Error() string {
|
|
return "unsupported type for path value: " + err.inner.String()
|
|
}
|
|
|
|
type structPathValueFields struct {
|
|
PathKey string
|
|
FieldKey string
|
|
|
|
Type reflect.Type
|
|
Optional bool
|
|
Match *regexp.Regexp
|
|
}
|
|
|
|
type PathValue[T any] struct {
|
|
Data T
|
|
}
|
|
|
|
type PathValueNotFound struct {
|
|
Key string
|
|
}
|
|
|
|
func (err PathValueNotFound) Error() string {
|
|
return "path value not found: " + err.Key
|
|
}
|
|
|
|
type InvalidPathValueType struct {
|
|
Kind reflect.Kind
|
|
Value string
|
|
}
|
|
|
|
func (err InvalidPathValueType) Error() string {
|
|
return fmt.Sprintf("invalid value for kind %v: %v", err.Kind, err.Value)
|
|
}
|
|
|
|
type InvalidPathValue struct {
|
|
Match *regexp.Regexp
|
|
Value string
|
|
}
|
|
|
|
func (err InvalidPathValue) Error() string {
|
|
return fmt.Sprintf("value not matched with regexp %v: %v", err.Match, err.Value)
|
|
}
|
|
|
|
func findReflectPathValueFields(v reflect.Value) ([]structPathValueFields, error) {
|
|
if cached, ok := cachedStructPathValueFields[v.Type()]; ok {
|
|
return cached, nil
|
|
}
|
|
if cached, ok := cachedUnsupportedPathValueFields[v.Type()]; ok {
|
|
return nil, UnsupportedPathValueType{inner: cached.Type}
|
|
}
|
|
|
|
var fields []structPathValueFields
|
|
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("pathvalue"); 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
|
|
optional := false
|
|
|
|
if fieldType.Kind() == reflect.Ptr {
|
|
fieldType = fieldType.Elem()
|
|
optional = true
|
|
}
|
|
|
|
if pathValueConvertTable[fieldType.Kind()] == nil {
|
|
if _, ok := fieldType.MethodByName("FromString"); !ok {
|
|
cachedUnsupportedPathValueFields[t] = field
|
|
return nil, UnsupportedPathValueType{inner: field.Type}
|
|
}
|
|
}
|
|
|
|
fields = append(fields, structPathValueFields{
|
|
PathKey: pathKey,
|
|
FieldKey: field.Name,
|
|
|
|
Type: fieldType,
|
|
Optional: optional,
|
|
|
|
Match: match,
|
|
})
|
|
}
|
|
cachedStructPathValueFields[t] = fields
|
|
} else {
|
|
return nil, UnsupportedPathValueType{inner: v.Type()}
|
|
}
|
|
|
|
return fields, nil
|
|
}
|
|
|
|
func (pathValue *PathValue[T]) FromRequest(r *http.Request) error {
|
|
v := reflect.ValueOf(pathValue).Elem().FieldByName("Data")
|
|
fields, err := findReflectPathValueFields(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, field := range fields {
|
|
str := r.PathValue(field.PathKey)
|
|
if str == "" {
|
|
if !field.Optional {
|
|
return PathValueNotFound{Key: field.PathKey}
|
|
}
|
|
continue
|
|
}
|
|
if field.Match != nil && !field.Match.MatchString(str) {
|
|
return InvalidPathValue{Match: field.Match, Value: str}
|
|
}
|
|
convert := pathValueConvertTable[field.Type.Kind()]
|
|
result, err := convert(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
reflectValue := reflect.ValueOf(result)
|
|
|
|
if field.Optional {
|
|
newReflectedValue := reflect.New(reflectValue.Type())
|
|
newReflectedValue.Elem().Set(reflect.ValueOf(result))
|
|
reflectValue = newReflectedValue
|
|
}
|
|
v.FieldByName(field.FieldKey).Set(reflectValue)
|
|
}
|
|
|
|
return nil
|
|
}
|