initial commit
This commit is contained in:
178
magic/path.go
Normal file
178
magic/path.go
Normal file
@ -0,0 +1,178 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user