Files
robinhood-agentic-mcp/internal/wire/list.go
T
s1d3sw1ped a6bf8632ce fix: parse MCP result payloads and drop place idempotency_key
Typed result structs replace empty envelopes. Equity place sends
ref_id only so live additionalProperties:false schemas accept the call.
2026-09-01 14:18:39 -05:00

77 lines
1.7 KiB
Go

package wire
import (
"encoding/json"
"net/url"
"strings"
)
// UnmarshalRows unpacks a list from an object (trying keys in order) or a JSON array.
// next is next_cursor, else the cursor query param of next, else next itself when it
// is not a URL.
func UnmarshalRows[T any](raw json.RawMessage, keys ...string) (rows []T, next string, err error) {
data := Unwrap(raw)
var obj map[string]json.RawMessage
if json.Unmarshal(data, &obj) != nil {
var list []T
if err := json.Unmarshal(data, &list); err != nil {
return nil, "", err
}
return list, "", nil
}
next = cursorFromMap(obj)
for _, k := range keys {
item, ok := obj[k]
if !ok || len(item) == 0 || string(item) == "null" {
continue
}
var list []T
if json.Unmarshal(item, &list) == nil {
return list, next, nil
}
var one T
if json.Unmarshal(item, &one) == nil {
return []T{one}, next, nil
}
}
return nil, next, nil
}
func cursorFromMap(obj map[string]json.RawMessage) string {
if c := stringField(obj, "next_cursor"); c != "" {
return c
}
return Cursor(stringField(obj, "next"), "")
}
func stringField(obj map[string]json.RawMessage, key string) string {
raw, ok := obj[key]
if !ok || len(raw) == 0 || string(raw) == "null" {
return ""
}
var s string
if json.Unmarshal(raw, &s) == nil {
return s
}
return ""
}
// Cursor prefers nextCursor; otherwise extracts cursor from a next URL.
func Cursor(next, nextCursor string) string {
if nextCursor != "" {
return nextCursor
}
next = strings.TrimSpace(next)
if next == "" {
return ""
}
u, err := url.Parse(next)
if err != nil || (u.Scheme == "" && u.Host == "" && !strings.Contains(next, "?")) {
return next
}
if v := u.Query().Get("cursor"); v != "" {
return v
}
return ""
}