chore: better local dev with stainless script

This commit is contained in:
adamdottv
2025-07-03 11:49:15 -05:00
parent 1dffabcfda
commit 5a0910ea79
70 changed files with 11281 additions and 2 deletions

View File

@@ -0,0 +1,383 @@
package apiform
import (
"fmt"
"io"
"mime/multipart"
"net/textproto"
"path"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/sst/opencode-sdk-go/internal/param"
)
var encoders sync.Map // map[encoderEntry]encoderFunc
func Marshal(value interface{}, writer *multipart.Writer) error {
e := &encoder{dateFormat: time.RFC3339}
return e.marshal(value, writer)
}
func MarshalRoot(value interface{}, writer *multipart.Writer) error {
e := &encoder{root: true, dateFormat: time.RFC3339}
return e.marshal(value, writer)
}
type encoder struct {
dateFormat string
root bool
}
type encoderFunc func(key string, value reflect.Value, writer *multipart.Writer) error
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error {
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil
}
typ := val.Type()
enc := e.typeEncoder(typ)
return enc("", val, writer)
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value, writer *multipart.Writer) error {
wg.Wait()
return f(key, v, writer)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder()
}
if t.ConvertibleTo(reflect.TypeOf((*io.Reader)(nil)).Elem()) {
return e.newReaderTypeEncoder()
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.typeEncoder(inner)
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if !v.IsValid() || v.IsNil() {
return nil
}
return innerEncoder(key, v.Elem(), writer)
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Slice, reflect.Array:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
// Note that we could use `gjson` to encode these types but it would complicate our
// code more and this current code shouldn't cause any issues
case reflect.String:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, v.String())
}
case reflect.Bool:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if v.Bool() {
return writer.WriteField(key, "true")
}
return writer.WriteField(key, "false")
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatUint(v.Uint(), 10))
}
case reflect.Float32:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 32))
}
case reflect.Float64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
default:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return fmt.Errorf("unknown type received at primitive encoder: %s", t.String())
}
}
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if key != "" {
key = key + "."
}
for i := 0; i < v.Len(); i++ {
err := itemEncoder(key+strconv.Itoa(i), v.Index(i), writer)
if err != nil {
return err
}
}
return nil
}
}
func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) {
return e.newFieldTypeEncoder(t)
}
encoderFields := []encoderField{}
extraEncoder := (*encoderField)(nil)
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseFormStructTag(field)
if !ok {
continue
}
// We only want to support unexported field if they're tagged with
// `extras` because that field shouldn't be part of the public API. We
// also want to only keep the top level extras
if ptag.extras && len(index) == 0 {
extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx}
continue
}
if ptag.name == "-" {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
// Ensure deterministic output by sorting by lexicographic order
sort.Slice(encoderFields, func(i, j int) bool {
return encoderFields[i].tag.name < encoderFields[j].tag.name
})
return func(key string, value reflect.Value, writer *multipart.Writer) error {
if key != "" {
key = key + "."
}
for _, ef := range encoderFields {
field := value.FieldByIndex(ef.idx)
err := ef.fn(key+ef.tag.name, field, writer)
if err != nil {
return err
}
}
if extraEncoder != nil {
err := e.encodeMapEntries(key, value.FieldByIndex(extraEncoder.idx), writer)
if err != nil {
return err
}
}
return nil
}
}
func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.typeEncoder(f.Type)
return func(key string, value reflect.Value, writer *multipart.Writer) error {
present := value.FieldByName("Present")
if !present.Bool() {
return nil
}
null := value.FieldByName("Null")
if null.Bool() {
return nil
}
raw := value.FieldByName("Raw")
if !raw.IsNil() {
return e.typeEncoder(raw.Type())(key, raw, writer)
}
return enc(key, value.FieldByName("Value"), writer)
}
}
func (e *encoder) newTimeTypeEncoder() encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format))
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
value = value.Elem()
if !value.IsValid() {
return nil
}
return e.typeEncoder(value.Type())(key, value, writer)
}
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
func (e *encoder) newReaderTypeEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
reader := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader)
filename := "anonymous_file"
contentType := "application/octet-stream"
if named, ok := reader.(interface{ Filename() string }); ok {
filename = named.Filename()
} else if named, ok := reader.(interface{ Name() string }); ok {
filename = path.Base(named.Name())
}
if typed, ok := reader.(interface{ ContentType() string }); ok {
contentType = typed.ContentType()
}
// Below is taken almost 1-for-1 from [multipart.CreateFormFile]
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(key), escapeQuotes(filename)))
h.Set("Content-Type", contentType)
filewriter, err := writer.CreatePart(h)
if err != nil {
return err
}
_, err = io.Copy(filewriter, reader)
return err
}
}
// Given a []byte of json (may either be an empty object or an object that already contains entries)
// encode all of the entries in the map to the json byte array.
func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipart.Writer) error {
type mapPair struct {
key string
value reflect.Value
}
if key != "" {
key = key + "."
}
pairs := []mapPair{}
iter := v.MapRange()
for iter.Next() {
if iter.Key().Type().Kind() == reflect.String {
pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()})
} else {
return fmt.Errorf("cannot encode a map with a non string key")
}
}
// Ensure deterministic output
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].key < pairs[j].key
})
elementEncoder := e.typeEncoder(v.Type().Elem())
for _, p := range pairs {
err := elementEncoder(key+string(p.key), p.value, writer)
if err != nil {
return err
}
}
return nil
}
func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
return e.encodeMapEntries(key, value, writer)
}
}

View File

@@ -0,0 +1,5 @@
package apiform
type Marshaler interface {
MarshalMultipart() ([]byte, string, error)
}

View File

@@ -0,0 +1,440 @@
package apiform
import (
"bytes"
"mime/multipart"
"strings"
"testing"
"time"
)
func P[T any](v T) *T { return &v }
type Primitives struct {
A bool `form:"a"`
B int `form:"b"`
C uint `form:"c"`
D float64 `form:"d"`
E float32 `form:"e"`
F []int `form:"f"`
}
type PrimitivePointers struct {
A *bool `form:"a"`
B *int `form:"b"`
C *uint `form:"c"`
D *float64 `form:"d"`
E *float32 `form:"e"`
F *[]int `form:"f"`
}
type Slices struct {
Slice []Primitives `form:"slices"`
}
type DateTime struct {
Date time.Time `form:"date" format:"date"`
DateTime time.Time `form:"date-time" format:"date-time"`
}
type AdditionalProperties struct {
A bool `form:"a"`
Extras map[string]interface{} `form:"-,extras"`
}
type TypedAdditionalProperties struct {
A bool `form:"a"`
Extras map[string]int `form:"-,extras"`
}
type EmbeddedStructs struct {
AdditionalProperties
A *int `form:"number2"`
Extras map[string]interface{} `form:"-,extras"`
}
type Recursive struct {
Name string `form:"name"`
Child *Recursive `form:"child"`
}
type UnknownStruct struct {
Unknown interface{} `form:"unknown"`
}
type UnionStruct struct {
Union Union `form:"union" format:"date"`
}
type Union interface {
union()
}
type UnionInteger int64
func (UnionInteger) union() {}
type UnionStructA struct {
Type string `form:"type"`
A string `form:"a"`
B string `form:"b"`
}
func (UnionStructA) union() {}
type UnionStructB struct {
Type string `form:"type"`
A string `form:"a"`
}
func (UnionStructB) union() {}
type UnionTime time.Time
func (UnionTime) union() {}
type ReaderStruct struct {
}
var tests = map[string]struct {
buf string
val interface{}
}{
"map_string": {
`--xxx
Content-Disposition: form-data; name="foo"
bar
--xxx--
`,
map[string]string{"foo": "bar"},
},
"map_interface": {
`--xxx
Content-Disposition: form-data; name="a"
1
--xxx
Content-Disposition: form-data; name="b"
str
--xxx
Content-Disposition: form-data; name="c"
false
--xxx--
`,
map[string]interface{}{"a": float64(1), "b": "str", "c": false},
},
"primitive_struct": {
`--xxx
Content-Disposition: form-data; name="a"
false
--xxx
Content-Disposition: form-data; name="b"
237628372683
--xxx
Content-Disposition: form-data; name="c"
654
--xxx
Content-Disposition: form-data; name="d"
9999.43
--xxx
Content-Disposition: form-data; name="e"
43.76
--xxx
Content-Disposition: form-data; name="f.0"
1
--xxx
Content-Disposition: form-data; name="f.1"
2
--xxx
Content-Disposition: form-data; name="f.2"
3
--xxx
Content-Disposition: form-data; name="f.3"
4
--xxx--
`,
Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
},
"slices": {
`--xxx
Content-Disposition: form-data; name="slices.0.a"
false
--xxx
Content-Disposition: form-data; name="slices.0.b"
237628372683
--xxx
Content-Disposition: form-data; name="slices.0.c"
654
--xxx
Content-Disposition: form-data; name="slices.0.d"
9999.43
--xxx
Content-Disposition: form-data; name="slices.0.e"
43.76
--xxx
Content-Disposition: form-data; name="slices.0.f.0"
1
--xxx
Content-Disposition: form-data; name="slices.0.f.1"
2
--xxx
Content-Disposition: form-data; name="slices.0.f.2"
3
--xxx
Content-Disposition: form-data; name="slices.0.f.3"
4
--xxx--
`,
Slices{
Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}},
},
},
"primitive_pointer_struct": {
`--xxx
Content-Disposition: form-data; name="a"
false
--xxx
Content-Disposition: form-data; name="b"
237628372683
--xxx
Content-Disposition: form-data; name="c"
654
--xxx
Content-Disposition: form-data; name="d"
9999.43
--xxx
Content-Disposition: form-data; name="e"
43.76
--xxx
Content-Disposition: form-data; name="f.0"
1
--xxx
Content-Disposition: form-data; name="f.1"
2
--xxx
Content-Disposition: form-data; name="f.2"
3
--xxx
Content-Disposition: form-data; name="f.3"
4
--xxx
Content-Disposition: form-data; name="f.4"
5
--xxx--
`,
PrimitivePointers{
A: P(false),
B: P(237628372683),
C: P(uint(654)),
D: P(9999.43),
E: P(float32(43.76)),
F: &[]int{1, 2, 3, 4, 5},
},
},
"datetime_struct": {
`--xxx
Content-Disposition: form-data; name="date"
2006-01-02
--xxx
Content-Disposition: form-data; name="date-time"
2006-01-02T15:04:05Z
--xxx--
`,
DateTime{
Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC),
DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC),
},
},
"additional_properties": {
`--xxx
Content-Disposition: form-data; name="a"
true
--xxx
Content-Disposition: form-data; name="bar"
value
--xxx
Content-Disposition: form-data; name="foo"
true
--xxx--
`,
AdditionalProperties{
A: true,
Extras: map[string]interface{}{
"bar": "value",
"foo": true,
},
},
},
"recursive_struct": {
`--xxx
Content-Disposition: form-data; name="child.name"
Alex
--xxx
Content-Disposition: form-data; name="name"
Robert
--xxx--
`,
Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
},
"unknown_struct_number": {
`--xxx
Content-Disposition: form-data; name="unknown"
12
--xxx--
`,
UnknownStruct{
Unknown: 12.,
},
},
"unknown_struct_map": {
`--xxx
Content-Disposition: form-data; name="unknown.foo"
bar
--xxx--
`,
UnknownStruct{
Unknown: map[string]interface{}{
"foo": "bar",
},
},
},
"union_integer": {
`--xxx
Content-Disposition: form-data; name="union"
12
--xxx--
`,
UnionStruct{
Union: UnionInteger(12),
},
},
"union_struct_discriminated_a": {
`--xxx
Content-Disposition: form-data; name="union.a"
foo
--xxx
Content-Disposition: form-data; name="union.b"
bar
--xxx
Content-Disposition: form-data; name="union.type"
typeA
--xxx--
`,
UnionStruct{
Union: UnionStructA{
Type: "typeA",
A: "foo",
B: "bar",
},
},
},
"union_struct_discriminated_b": {
`--xxx
Content-Disposition: form-data; name="union.a"
foo
--xxx
Content-Disposition: form-data; name="union.type"
typeB
--xxx--
`,
UnionStruct{
Union: UnionStructB{
Type: "typeB",
A: "foo",
},
},
},
"union_struct_time": {
`--xxx
Content-Disposition: form-data; name="union"
2010-05-23
--xxx--
`,
UnionStruct{
Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)),
},
},
}
func TestEncode(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
writer.SetBoundary("xxx")
err := Marshal(test.val, writer)
if err != nil {
t.Errorf("serialization of %v failed with error %v", test.val, err)
}
err = writer.Close()
if err != nil {
t.Errorf("serialization of %v failed with error %v", test.val, err)
}
raw := buf.Bytes()
if string(raw) != strings.ReplaceAll(test.buf, "\n", "\r\n") {
t.Errorf("expected %+#v to serialize to '%s' but got '%s'", test.val, test.buf, string(raw))
}
})
}
}

View File

@@ -0,0 +1,48 @@
package apiform
import (
"reflect"
"strings"
)
const jsonStructTag = "json"
const formStructTag = "form"
const formatStructTag = "format"
type parsedStructTag struct {
name string
required bool
extras bool
metadata bool
}
func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(formStructTag)
if !ok {
raw, ok = field.Tag.Lookup(jsonStructTag)
}
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "required":
tag.required = true
case "extras":
tag.extras = true
case "metadata":
tag.metadata = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}