summaryrefslogtreecommitdiffhomepage
path: root/packages/sdk/go/internal/apiform
diff options
context:
space:
mode:
Diffstat (limited to 'packages/sdk/go/internal/apiform')
-rw-r--r--packages/sdk/go/internal/apiform/encoder.go383
-rw-r--r--packages/sdk/go/internal/apiform/form.go5
-rw-r--r--packages/sdk/go/internal/apiform/form_test.go440
-rw-r--r--packages/sdk/go/internal/apiform/tag.go48
4 files changed, 0 insertions, 876 deletions
diff --git a/packages/sdk/go/internal/apiform/encoder.go b/packages/sdk/go/internal/apiform/encoder.go
deleted file mode 100644
index 243a1a123..000000000
--- a/packages/sdk/go/internal/apiform/encoder.go
+++ /dev/null
@@ -1,383 +0,0 @@
-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)
- }
-}
diff --git a/packages/sdk/go/internal/apiform/form.go b/packages/sdk/go/internal/apiform/form.go
deleted file mode 100644
index 5445116e9..000000000
--- a/packages/sdk/go/internal/apiform/form.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package apiform
-
-type Marshaler interface {
- MarshalMultipart() ([]byte, string, error)
-}
diff --git a/packages/sdk/go/internal/apiform/form_test.go b/packages/sdk/go/internal/apiform/form_test.go
deleted file mode 100644
index 39d1460c0..000000000
--- a/packages/sdk/go/internal/apiform/form_test.go
+++ /dev/null
@@ -1,440 +0,0 @@
-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))
- }
- })
- }
-}
diff --git a/packages/sdk/go/internal/apiform/tag.go b/packages/sdk/go/internal/apiform/tag.go
deleted file mode 100644
index b22e054fc..000000000
--- a/packages/sdk/go/internal/apiform/tag.go
+++ /dev/null
@@ -1,48 +0,0 @@
-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
-}