feat: add module and decimal wire helpers
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
decimal "github.com/alpacahq/alpacadecimal"
|
||||
)
|
||||
|
||||
// Unwrap returns the inner JSON of an optional {"data": ...} envelope.
|
||||
func Unwrap(raw json.RawMessage) json.RawMessage {
|
||||
if len(raw) == 0 {
|
||||
return raw
|
||||
}
|
||||
var wrap struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
if json.Unmarshal(raw, &wrap) == nil && len(wrap.Data) > 0 {
|
||||
return wrap.Data
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// Dec parses a required money/size/price value.
|
||||
func Dec(v any) (decimal.Decimal, error) {
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
return decimal.Zero, nil
|
||||
case float64:
|
||||
return decimal.NewFromFloat(x), nil
|
||||
case json.Number:
|
||||
d, err := decimal.NewFromString(string(x))
|
||||
if err != nil {
|
||||
return decimal.Zero, fmt.Errorf("parse decimal: %v", v)
|
||||
}
|
||||
return d, nil
|
||||
case string:
|
||||
if x == "" {
|
||||
return decimal.Zero, nil
|
||||
}
|
||||
d, err := decimal.NewFromString(x)
|
||||
if err != nil {
|
||||
return decimal.Zero, fmt.Errorf("parse decimal: %v", v)
|
||||
}
|
||||
return d, nil
|
||||
default:
|
||||
return decimal.Zero, fmt.Errorf("parse decimal: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// DecOpt parses an optional money/size/price value.
|
||||
func DecOpt(v any) (*decimal.Decimal, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if s, ok := v.(string); ok && s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
d, err := Dec(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// Encode is Decimal.String for Robinhood string wire fields.
|
||||
func Encode(d decimal.Decimal) string {
|
||||
return d.String()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package wire_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
decimal "github.com/alpacahq/alpacadecimal"
|
||||
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
|
||||
)
|
||||
|
||||
func TestUnwrap_dataEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := json.RawMessage(`{"data":{"cash":"1000"}}`)
|
||||
got := wire.Unwrap(in)
|
||||
if string(got) != `{"cash":"1000"}` {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDec_table(t *testing.T) {
|
||||
t.Parallel()
|
||||
zero := decimal.Zero
|
||||
tests := []struct {
|
||||
name string
|
||||
in any
|
||||
want decimal.Decimal
|
||||
wantErr bool
|
||||
}{
|
||||
{"number", float64(99.6), decimal.RequireFromString("99.6"), false},
|
||||
{"string", "99.60", decimal.RequireFromString("99.60"), false},
|
||||
{"zeroNum", float64(0), zero, false},
|
||||
{"zeroStr", "0", zero, false},
|
||||
{"emptyStr", "", zero, false},
|
||||
{"nil", nil, zero, false},
|
||||
{"bad", "n/a", zero, true},
|
||||
{"obj", map[string]any{"x": 1}, zero, true},
|
||||
{"bool", true, zero, true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := wire.Dec(tc.in)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("want error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.Equal(tc.want) {
|
||||
t.Fatalf("got %s want %s", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecOpt_nullIsNil(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := wire.DecOpt(nil)
|
||||
if err != nil || got != nil {
|
||||
t.Fatalf("got %v err %v", got, err)
|
||||
}
|
||||
got, err = wire.DecOpt("")
|
||||
if err != nil || got != nil {
|
||||
t.Fatalf("empty string: %v %v", got, err)
|
||||
}
|
||||
got, err = wire.DecOpt("0")
|
||||
if err != nil || got == nil || !got.IsZero() {
|
||||
t.Fatalf("zero: %v %v", got, err)
|
||||
}
|
||||
_, err = wire.DecOpt("n/a")
|
||||
if err == nil {
|
||||
t.Fatal("unparseable must error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := decimal.RequireFromString("99.6")
|
||||
if wire.Encode(d) != d.String() {
|
||||
t.Fatalf("%q", wire.Encode(d))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user