diff --git a/cmd/hook/main.go b/cmd/hook/main.go index e97529b..02d5e3f 100644 --- a/cmd/hook/main.go +++ b/cmd/hook/main.go @@ -2,18 +2,79 @@ package main import ( "vultras.su/core/bond" + "vultras.su/core/bond/urlenc" //"vultras.su/core/bond/methods" "vultras.su/core/bond/statuses" "fmt" - "io" - "net/url" + //"io" + //"net/url" "encoding/json" + "strconv" ) +type JsonInt64 int64 +func(ji *JsonInt) UnmarshalJSON(bts []byte) error { + k, err := strconv.ParseInt(string(bts), 10, 32) + if err != nil { + return err + } +} + +type JsonArrayMap[V any] map[int] V +func (jam *JsonArrayMap[V]) UnmarshalJSON(bts []byte) error { + *jam = make(JsonArrayMap[V]) + am := *jam + j := map[string] json.RawMessage{} + err := json.Unmarshal(bts, &j) + if err != nil { + return err + } + + v := new(V) + for jk, jv := range j { + // Getting the key from string. + k, err := strconv.ParseInt(jk, 10, 32) + if err != nil { + return err + } + + err = json.Unmarshal([]byte(jv), v) + if err != nil { + return err + } + + am[int(k)] = *v + } + + return nil +} + type GetNotesOptions struct { Id int `json:"id"` Name string `json:"name"` } +type WebhookRequest struct { + Leads Leads `json:"leads"` + Account Account `json:"account"` +} + +type Leads struct { + Status JsonArrayMap[Status]`json:"status"` + Add JsonArrayMap[Status] `json:"add"` +} + +type Status struct { + Id string `json:"id"` + StatusId string `json:"status_id"` + PipelineId string `json:"pipeline_id"` + OldStatusId string `json:"old_status_id"` + OldPipelineId string `json:"old_pipeline_id"` +} + +type Account struct { + Id string `json:"id"` + SubDomain string `json:"subdomain"` +} var root = bond.Root(bond.Path(). Def( @@ -21,7 +82,14 @@ Def( /*bond.Method().Def( methods.Post,*/ bond.Func(func(c *bond.Context){ - fmt.Printf("Content-Type: %q\n", c.ContentType()) + reciever := WebhookRequest{} + c.Scan(&reciever) + if c.ScanErr() != nil { + fmt.Printf("err: %s\n", c.ScanErr()) + } + fmt.Printf("%#v\n", reciever) + return + /*fmt.Printf("Content-Type: %q\n", c.ContentType()) body, err := io.ReadAll(c.R.Body) if err != nil { fmt.Printf("err:%s\n", err) @@ -45,19 +113,32 @@ Def( fmt.Printf("err:%s\n", err) return } - fmt.Printf("\nparsed: %v\n", string(js)) + fmt.Printf("\nparsed: %v\n", string(js))*/ c.SetStatus(statuses.OK) }), //), )) func main() { - srv := bond.Server{ + requestString := "leads[status][0][id]=2050297&" + + "leads[status][0][status_id]=35573056&" + + "leads[status][0][pipeline_id]=3643927&" + + "leads[status][0][old_status_id]=35572897&" + + "leads[status][0][old_pipeline_id]=3643927&" + + "account[id]=29085955&" + + "account[subdomain]=domain" + reciever := WebhookRequest{} + err := urlenc.Unmarshal([]byte(requestString), &reciever) + if err != nil { + panic(err) + } + fmt.Printf("%#v\n", reciever) + /*srv := bond.Server{ Addr: ":15080", Handler: root, } err := srv.ListenAndServe() if err != nil { panic(err) - } + }*/ } diff --git a/context.go b/context.go index 64d8e65..6ca1fff 100644 --- a/context.go +++ b/context.go @@ -7,6 +7,7 @@ import ( "net/url" "fmt" "vultras.su/core/bond/contents" + "vultras.su/core/bond/urlenc" ) type Context struct { @@ -61,15 +62,21 @@ func (c *Context) Close() { } // Scan the incoming value from body depending -// on the content type of the request. +// on the content type of the request into the +// structure or map[string] any. func (c *Context) Scan(v any) bool { if c.dec == nil { typ := c.ContentType() switch typ { case contents.Json: c.dec = json.NewDecoder(c.R.Body) - //case contents.UrlEncoded: - // return false + case contents.UrlEncoded: + body, _ := io.ReadAll(c.R.Body) + err := urlenc.Unmarshal(body, v) + if err != nil { + c.scanErr = err + } + return false default: c.scanErr = UnknownContentTypeErr return false diff --git a/parse.go b/parse.go index ab37a07..cf9dc05 100644 --- a/parse.go +++ b/parse.go @@ -1,147 +1,2 @@ package bond -import ( - "strings" - "fmt" - "net/url" -) - -func ParseStr(encodedString string, result map[string]interface{}) error { - // build nested map. - var build func(map[string]interface{}, []string, interface{}) error - - build = func(result map[string]interface{}, keys []string, value interface{}) error { - length := len(keys) - // trim '," - key := strings.Trim(keys[0], "'\"") - if length == 1 { - result[key] = value - return nil - } - - // The end is slice. like f[], f[a][] - if keys[1] == "" && length == 2 { - // todo nested slice - if key == "" { - return nil - } - val, ok := result[key] - if !ok { - result[key] = []interface{}{value} - return nil - } - children, ok := val.([]interface{}) - if !ok { - return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) - } - result[key] = append(children, value) - return nil - } - - // The end is slice + map. like f[][a] - if keys[1] == "" && length > 2 && keys[2] != "" { - val, ok := result[key] - if !ok { - result[key] = []interface{}{} - val = result[key] - } - children, ok := val.([]interface{}) - if !ok { - return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) - } - if l := len(children); l > 0 { - if child, ok := children[l-1].(map[string]interface{}); ok { - if _, ok := child[keys[2]]; !ok { - _ = build(child, keys[2:], value) - return nil - } - } - } - child := map[string]interface{}{} - _ = build(child, keys[2:], value) - result[key] = append(children, child) - - return nil - } - - // map. like f[a], f[a][b] - val, ok := result[key] - if !ok { - result[key] = map[string]interface{}{} - val = result[key] - } - children, ok := val.(map[string]interface{}) - if !ok { - return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val) - } - - return build(children, keys[1:], value) - } - - // split encodedString. - parts := strings.Split(encodedString, "&") - for _, part := range parts { - pos := strings.Index(part, "=") - if pos <= 0 { - continue - } - key, err := url.QueryUnescape(part[:pos]) - if err != nil { - return err - } - for key[0] == ' ' { - key = key[1:] - } - if key == "" || key[0] == '[' { - continue - } - value, err := url.QueryUnescape(part[pos+1:]) - if err != nil { - return err - } - - // split into multiple keys - var keys []string - left := 0 - for i, k := range key { - if k == '[' && left == 0 { - left = i - } else if k == ']' { - if left > 0 { - if len(keys) == 0 { - keys = append(keys, key[:left]) - } - keys = append(keys, key[left+1:i]) - left = 0 - if i+1 < len(key) && key[i+1] != '[' { - break - } - } - } - } - if len(keys) == 0 { - keys = append(keys, key) - } - // first key - first := "" - for i, chr := range keys[0] { - if chr == ' ' || chr == '.' || chr == '[' { - first += "_" - } else { - first += string(chr) - } - if chr == '[' { - first += keys[0][i+1:] - break - } - } - keys[0] = first - - // build nested map - if err := build(result, keys, value); err != nil { - return err - } - } - - return nil -} diff --git a/urlenc/scan.go b/urlenc/scan.go new file mode 100644 index 0000000..9eb899a --- /dev/null +++ b/urlenc/scan.go @@ -0,0 +1,173 @@ +package urlenc + +import ( + "net/url" + "encoding/json" + "strings" + "fmt" +) + +func Unmarshal(bts []byte, v any) error { + unesc, err := url.QueryUnescape(string(bts)) + if err != nil { + return err + } + + mp := map[string] any{} + err = parseStr(unesc, mp) + if err != nil { + return err + } + + js, err := json.Marshal(mp) + if err != nil { + return err + } + + err = json.Unmarshal(js, v) + if err != nil { + return err + } + + return nil +} + +func parseStr(encodedString string, result map[string]any) error { + // build nested map. + var build func(map[string]any, []string, any) error + + build = func(result map[string]any, keys []string, value any) error { + length := len(keys) + // trim '," + key := strings.Trim(keys[0], "'\"") + if length == 1 { + result[key] = value + return nil + } + + // The end is slice. like f[], f[a][] + if keys[1] == "" && length == 2 { + // todo nested slice + if key == "" { + return nil + } + val, ok := result[key] + if !ok { + result[key] = []any{value} + return nil + } + children, ok := val.([]any) + if !ok { + return fmt.Errorf("expected type '[]any' for key '%s', but got '%T'", key, val) + } + result[key] = append(children, value) + return nil + } + + // The end is slice + map. like f[][a] + if keys[1] == "" && length > 2 && keys[2] != "" { + val, ok := result[key] + if !ok { + result[key] = []any{} + val = result[key] + } + children, ok := val.([]any) + if !ok { + return fmt.Errorf("expected type '[]any' for key '%s', but got '%T'", key, val) + } + if l := len(children); l > 0 { + if child, ok := children[l-1].(map[string]any); ok { + if _, ok := child[keys[2]]; !ok { + _ = build(child, keys[2:], value) + return nil + } + } + } + child := map[string]any{} + _ = build(child, keys[2:], value) + result[key] = append(children, child) + + return nil + } + + // map. like f[a], f[a][b] + val, ok := result[key] + if !ok { + result[key] = map[string]any{} + val = result[key] + } + children, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("expected type 'map[string]any' for key '%s', but got '%T'", key, val) + } + + return build(children, keys[1:], value) + } + + // split encodedString. + parts := strings.Split(encodedString, "&") + for _, part := range parts { + pos := strings.Index(part, "=") + if pos <= 0 { + continue + } + key, err := url.QueryUnescape(part[:pos]) + if err != nil { + return err + } + for key[0] == ' ' { + key = key[1:] + } + if key == "" || key[0] == '[' { + continue + } + value, err := url.QueryUnescape(part[pos+1:]) + if err != nil { + return err + } + + // split into multiple keys + var keys []string + left := 0 + for i, k := range key { + if k == '[' && left == 0 { + left = i + } else if k == ']' { + if left > 0 { + if len(keys) == 0 { + keys = append(keys, key[:left]) + } + keys = append(keys, key[left+1:i]) + left = 0 + if i+1 < len(key) && key[i+1] != '[' { + break + } + } + } + } + if len(keys) == 0 { + keys = append(keys, key) + } + // first key + first := "" + for i, chr := range keys[0] { + if chr == ' ' || chr == '.' || chr == '[' { + first += "_" + } else { + first += string(chr) + } + if chr == '[' { + first += keys[0][i+1:] + break + } + } + keys[0] = first + + // build nested map + if err := build(result, keys, value); err != nil { + return err + } + } + + return nil +}