diff --git a/dag/fiber_api.go b/dag/fiber_api.go index 443b56a..534c4c4 100644 --- a/dag/fiber_api.go +++ b/dag/fiber_api.go @@ -1,12 +1,10 @@ package dag import ( - "bytes" "context" "encoding/json" "fmt" "net/http" - "net/url" "os" "strings" @@ -14,6 +12,7 @@ import ( "github.com/oarkflow/errors" "github.com/oarkflow/mq" + "github.com/oarkflow/mq/utils" "github.com/oarkflow/mq/consts" "github.com/oarkflow/mq/jsonparser" @@ -216,9 +215,12 @@ func parseRequest(c *fiber.Ctx) (context.Context, []byte, error) { if body == nil { return ctx, nil, errors.New("empty form body") } - formData := ParseFormToMap(body, &userContext.Query) - if formData != nil { - return ctx, nil, fmt.Errorf("failed to parse form data: %v", formData) + val, err := utils.DecodeForm(body) + if err != nil { + return ctx, nil, fmt.Errorf("failed to parse form data: %v", err.Error()) + } + for key, v := range val { + userContext.Query[key] = v } result = userContext.Query default: @@ -232,52 +234,3 @@ func parseRequest(c *fiber.Ctx) (context.Context, []byte, error) { return ctx, bt, nil } - -// ParseFormToMap parses form-encoded data into the provided map -func ParseFormToMap(body []byte, data *map[string]any) error { - if data == nil { - return errors.New("data map is nil") - } - if len(body) == 0 { - return errors.New("empty form body") - } - start := 0 - for i := 0; i <= len(body); i++ { - if i == len(body) || body[i] == '&' { - if err := processPair(body[start:i], data); err != nil { - return err - } - start = i + 1 - } - } - return nil -} - -// processPair processes a key-value pair and inserts it into the map -func processPair(pair []byte, data *map[string]any) error { - if len(pair) == 0 { - return nil // Ignore empty pairs - } - eqIndex := bytes.IndexByte(pair, '=') - if eqIndex == -1 { - return errors.New("malformed key-value pair") - } - - // Extract key and value - key := pair[:eqIndex] - value := pair[eqIndex+1:] - - // Decode key and value (zero-allocation alternatives can replace this) - decodedKey, err := url.QueryUnescape(string(key)) - if err != nil { - return err - } - decodedValue, err := url.QueryUnescape(string(value)) - if err != nil { - return err - } - - // Insert into the map - (*data)[decodedKey] = decodedValue - return nil -} diff --git a/examples/parse.go b/examples/parse.go new file mode 100644 index 0000000..16e42f8 --- /dev/null +++ b/examples/parse.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + + "github.com/oarkflow/mq/utils" +) + +func main() { + queryString := []byte("fields[0][method]=GET&fields[0][path]=/user/:id&fields[0][handlerMsg]=User Profile&fields[1][method]=POST&fields[1][path]=/user/create&fields[1][handlerMsg]=Create User") + fmt.Println(utils.DecodeForm(queryString)) +} diff --git a/utils/form.go b/utils/form.go new file mode 100644 index 0000000..3897e13 --- /dev/null +++ b/utils/form.go @@ -0,0 +1,120 @@ +package utils + +import ( + "fmt" + "net/url" + "strings" +) + +type form struct { + dest map[string]any + raw string + pathCache map[string]int +} + +func newForm(raw string) *form { + f := new(form) + f.raw = raw + f.pathCache = make(map[string]int) + return f +} + +func (f *form) reset() { + f.dest = make(map[string]any) +} + +func (f *form) decode() (map[string]any, error) { + f.reset() + vals := make(map[string]any) + for _, v := range strings.Split(f.raw, "&") { + if v == "" { + continue + } + index := strings.Index(v, "=") + if index > 0 { + key := v[:index] + val := v[index+1:] + + f.insertValue(&vals, key, val) + } else { + f.insertValue(&vals, v, "") + } + } + return f.parseArray(vals), nil +} + +func (f *form) insertValue(destP *map[string]any, key string, val string) { + key, _ = url.PathUnescape(key) + var path []string + if strings.Contains(key, "[") || strings.Contains(key, "]") { + var current string + for _, c := range key { + switch c { + case '[': + if len(current) > 0 { + path = append(path, current) + current = "" + } + case ']': + path = append(path, current) + current = "" + continue + default: + current += string(c) + } + } + if len(current) > 0 { + path = append(path, current) + } + } else { + path = append(path, key) + } + dest := *destP + for i, k := range path { + if i == len(path)-1 { + break + } + if k == "" { + c := strings.Join(path, ",") + k = fmt.Sprint(f.pathCache[c]) + f.pathCache[c] = f.pathCache[c] + 1 + } + if _, ok := dest[k].(map[string]any); !ok { + dest[k] = make(map[string]any) + } + dest = dest[k].(map[string]any) + } + p := path[len(path)-1] + if p == "" { + p = fmt.Sprint(len(dest)) + } + val, _ = url.QueryUnescape(val) + dest[p] = val +} + +func (f *form) parseArrayItem(dest map[string]any) any { + var arr []any + for i := 0; i < len(dest); i++ { + item, ok := dest[fmt.Sprint(i)] + if !ok { + return dest + } + arr = append(arr, item) + } + return arr +} + +func (f *form) parseArray(dest map[string]any) map[string]any { + for k, v := range dest { + mv, ok := v.(map[string]any) + if ok { + f.parseArray(mv) + dest[k] = f.parseArrayItem(mv) + } + } + return dest +} + +func DecodeForm(src []byte) (map[string]any, error) { + return newForm(FromByte(src)).decode() +}