diff options
Diffstat (limited to 'packages/sdk/go/internal')
22 files changed, 0 insertions, 4650 deletions
diff --git a/packages/sdk/go/internal/apierror/apierror.go b/packages/sdk/go/internal/apierror/apierror.go deleted file mode 100644 index 24307fc36..000000000 --- a/packages/sdk/go/internal/apierror/apierror.go +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package apierror - -import ( - "fmt" - "net/http" - "net/http/httputil" - - "github.com/sst/opencode-sdk-go/internal/apijson" -) - -// Error represents an error that originates from the API, i.e. when a request is -// made and the API returns a response with a HTTP status code. Other errors are -// not wrapped by this SDK. -type Error struct { - JSON errorJSON `json:"-"` - StatusCode int - Request *http.Request - Response *http.Response -} - -// errorJSON contains the JSON metadata for the struct [Error] -type errorJSON struct { - raw string - ExtraFields map[string]apijson.Field -} - -func (r *Error) UnmarshalJSON(data []byte) (err error) { - return apijson.UnmarshalRoot(data, r) -} - -func (r errorJSON) RawJSON() string { - return r.raw -} - -func (r *Error) Error() string { - // Attempt to re-populate the response body - return fmt.Sprintf("%s \"%s\": %d %s %s", r.Request.Method, r.Request.URL, r.Response.StatusCode, http.StatusText(r.Response.StatusCode), r.JSON.RawJSON()) -} - -func (r *Error) DumpRequest(body bool) []byte { - if r.Request.GetBody != nil { - r.Request.Body, _ = r.Request.GetBody() - } - out, _ := httputil.DumpRequestOut(r.Request, body) - return out -} - -func (r *Error) DumpResponse(body bool) []byte { - out, _ := httputil.DumpResponse(r.Response, body) - return out -} 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 -} diff --git a/packages/sdk/go/internal/apijson/decoder.go b/packages/sdk/go/internal/apijson/decoder.go deleted file mode 100644 index bf01bf6b4..000000000 --- a/packages/sdk/go/internal/apijson/decoder.go +++ /dev/null @@ -1,670 +0,0 @@ -package apijson - -import ( - "encoding/json" - "errors" - "fmt" - "reflect" - "strconv" - "sync" - "time" - "unsafe" - - "github.com/tidwall/gjson" -) - -// decoders is a synchronized map with roughly the following type: -// map[reflect.Type]decoderFunc -var decoders sync.Map - -// Unmarshal is similar to [encoding/json.Unmarshal] and parses the JSON-encoded -// data and stores it in the given pointer. -func Unmarshal(raw []byte, to any) error { - d := &decoderBuilder{dateFormat: time.RFC3339} - return d.unmarshal(raw, to) -} - -// UnmarshalRoot is like Unmarshal, but doesn't try to call MarshalJSON on the -// root element. Useful if a struct's UnmarshalJSON is overrode to use the -// behavior of this encoder versus the standard library. -func UnmarshalRoot(raw []byte, to any) error { - d := &decoderBuilder{dateFormat: time.RFC3339, root: true} - return d.unmarshal(raw, to) -} - -// decoderBuilder contains the 'compile-time' state of the decoder. -type decoderBuilder struct { - // Whether or not this is the first element and called by [UnmarshalRoot], see - // the documentation there to see why this is necessary. - root bool - // The dateFormat (a format string for [time.Format]) which is chosen by the - // last struct tag that was seen. - dateFormat string -} - -// decoderState contains the 'run-time' state of the decoder. -type decoderState struct { - strict bool - exactness exactness -} - -// Exactness refers to how close to the type the result was if deserialization -// was successful. This is useful in deserializing unions, where you want to try -// each entry, first with strict, then with looser validation, without actually -// having to do a lot of redundant work by marshalling twice (or maybe even more -// times). -type exactness int8 - -const ( - // Some values had to fudged a bit, for example by converting a string to an - // int, or an enum with extra values. - loose exactness = iota - // There are some extra arguments, but other wise it matches the union. - extras - // Exactly right. - exact -) - -type decoderFunc func(node gjson.Result, value reflect.Value, state *decoderState) error - -type decoderField struct { - tag parsedStructTag - fn decoderFunc - idx []int - goname string -} - -type decoderEntry struct { - reflect.Type - dateFormat string - root bool -} - -func (d *decoderBuilder) unmarshal(raw []byte, to any) error { - value := reflect.ValueOf(to).Elem() - result := gjson.ParseBytes(raw) - if !value.IsValid() { - return fmt.Errorf("apijson: cannot marshal into invalid value") - } - return d.typeDecoder(value.Type())(result, value, &decoderState{strict: false, exactness: exact}) -} - -func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { - entry := decoderEntry{ - Type: t, - dateFormat: d.dateFormat, - root: d.root, - } - - if fi, ok := decoders.Load(entry); ok { - return fi.(decoderFunc) - } - - // 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 decoderFunc - ) - wg.Add(1) - fi, loaded := decoders.LoadOrStore(entry, decoderFunc(func(node gjson.Result, v reflect.Value, state *decoderState) error { - wg.Wait() - return f(node, v, state) - })) - if loaded { - return fi.(decoderFunc) - } - - // Compute the real decoder and replace the indirect func with it. - f = d.newTypeDecoder(t) - wg.Done() - decoders.Store(entry, f) - return f -} - -func indirectUnmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error { - return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) -} - -func unmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error { - if v.Kind() == reflect.Pointer && v.CanSet() { - v.Set(reflect.New(v.Type().Elem())) - } - return v.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) -} - -func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - return d.newTimeTypeDecoder(t) - } - if !d.root && t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { - return unmarshalerDecoder - } - if !d.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { - if _, ok := unionVariants[t]; !ok { - return indirectUnmarshalerDecoder - } - } - d.root = false - - if _, ok := unionRegistry[t]; ok { - return d.newUnionDecoder(t) - } - - switch t.Kind() { - case reflect.Pointer: - inner := t.Elem() - innerDecoder := d.typeDecoder(inner) - - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - if !v.IsValid() { - return fmt.Errorf("apijson: unexpected invalid reflection value %+#v", v) - } - - newValue := reflect.New(inner).Elem() - err := innerDecoder(n, newValue, state) - if err != nil { - return err - } - - v.Set(newValue.Addr()) - return nil - } - case reflect.Struct: - return d.newStructTypeDecoder(t) - case reflect.Array: - fallthrough - case reflect.Slice: - return d.newArrayTypeDecoder(t) - case reflect.Map: - return d.newMapDecoder(t) - case reflect.Interface: - return func(node gjson.Result, value reflect.Value, state *decoderState) error { - if !value.IsValid() { - return fmt.Errorf("apijson: unexpected invalid value %+#v", value) - } - if node.Value() != nil && value.CanSet() { - value.Set(reflect.ValueOf(node.Value())) - } - return nil - } - default: - return d.newPrimitiveTypeDecoder(t) - } -} - -// newUnionDecoder returns a decoderFunc that deserializes into a union using an -// algorithm roughly similar to Pydantic's [smart algorithm]. -// -// Conceptually this is equivalent to choosing the best schema based on how 'exact' -// the deserialization is for each of the schemas. -// -// If there is a tie in the level of exactness, then the tie is broken -// left-to-right. -// -// [smart algorithm]: https://docs.pydantic.dev/latest/concepts/unions/#smart-mode -func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc { - unionEntry, ok := unionRegistry[t] - if !ok { - panic("apijson: couldn't find union of type " + t.String() + " in union registry") - } - decoders := []decoderFunc{} - for _, variant := range unionEntry.variants { - decoder := d.typeDecoder(variant.Type) - decoders = append(decoders, decoder) - } - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - // If there is a discriminator match, circumvent the exactness logic entirely - for idx, variant := range unionEntry.variants { - decoder := decoders[idx] - if variant.TypeFilter != n.Type { - continue - } - - if len(unionEntry.discriminatorKey) != 0 { - discriminatorValue := n.Get(EscapeSJSONKey(unionEntry.discriminatorKey)).Value() - if discriminatorValue == variant.DiscriminatorValue { - inner := reflect.New(variant.Type).Elem() - err := decoder(n, inner, state) - v.Set(inner) - return err - } - } - } - - // Set bestExactness to worse than loose - bestExactness := loose - 1 - for idx, variant := range unionEntry.variants { - decoder := decoders[idx] - if variant.TypeFilter != n.Type { - continue - } - sub := decoderState{strict: state.strict, exactness: exact} - inner := reflect.New(variant.Type).Elem() - err := decoder(n, inner, &sub) - if err != nil { - continue - } - if sub.exactness == exact { - v.Set(inner) - return nil - } - if sub.exactness > bestExactness { - v.Set(inner) - bestExactness = sub.exactness - } - } - - if bestExactness < loose { - return errors.New("apijson: was not able to coerce type as union") - } - - if guardStrict(state, bestExactness != exact) { - return errors.New("apijson: was not able to coerce type as union strictly") - } - - return nil - } -} - -func (d *decoderBuilder) newMapDecoder(t reflect.Type) decoderFunc { - keyType := t.Key() - itemType := t.Elem() - itemDecoder := d.typeDecoder(itemType) - - return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { - mapValue := reflect.MakeMapWithSize(t, len(node.Map())) - - node.ForEach(func(key, value gjson.Result) bool { - // It's fine for us to just use `ValueOf` here because the key types will - // always be primitive types so we don't need to decode it using the standard pattern - keyValue := reflect.ValueOf(key.Value()) - if !keyValue.IsValid() { - if err == nil { - err = fmt.Errorf("apijson: received invalid key type %v", keyValue.String()) - } - return false - } - if keyValue.Type() != keyType { - if err == nil { - err = fmt.Errorf("apijson: expected key type %v but got %v", keyType, keyValue.Type()) - } - return false - } - - itemValue := reflect.New(itemType).Elem() - itemerr := itemDecoder(value, itemValue, state) - if itemerr != nil { - if err == nil { - err = itemerr - } - return false - } - - mapValue.SetMapIndex(keyValue, itemValue) - return true - }) - - if err != nil { - return err - } - value.Set(mapValue) - return nil - } -} - -func (d *decoderBuilder) newArrayTypeDecoder(t reflect.Type) decoderFunc { - itemDecoder := d.typeDecoder(t.Elem()) - - return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { - if !node.IsArray() { - return fmt.Errorf("apijson: could not deserialize to an array") - } - - arrayNode := node.Array() - - arrayValue := reflect.MakeSlice(reflect.SliceOf(t.Elem()), len(arrayNode), len(arrayNode)) - for i, itemNode := range arrayNode { - err = itemDecoder(itemNode, arrayValue.Index(i), state) - if err != nil { - return err - } - } - - value.Set(arrayValue) - return nil - } -} - -func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { - // map of json field name to struct field decoders - decoderFields := map[string]decoderField{} - anonymousDecoders := []decoderField{} - extraDecoder := (*decoderField)(nil) - inlineDecoder := (*decoderField)(nil) - - for i := 0; i < t.NumField(); i++ { - idx := []int{i} - field := t.FieldByIndex(idx) - if !field.IsExported() { - continue - } - // If this is an embedded struct, traverse one level deeper to extract - // the fields and get their encoders as well. - if field.Anonymous { - anonymousDecoders = append(anonymousDecoders, decoderField{ - fn: d.typeDecoder(field.Type), - idx: idx[:], - }) - continue - } - // If json tag is not present, then we skip, which is intentionally - // different behavior from the stdlib. - ptag, ok := parseJSONStructTag(field) - if !ok { - continue - } - // We only want to support unexported fields if they're tagged with - // `extras` because that field shouldn't be part of the public API. - if ptag.extras { - extraDecoder = &decoderField{ptag, d.typeDecoder(field.Type.Elem()), idx, field.Name} - continue - } - if ptag.inline { - inlineDecoder = &decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} - continue - } - if ptag.metadata { - continue - } - - oldFormat := d.dateFormat - dateFormat, ok := parseFormatStructTag(field) - if ok { - switch dateFormat { - case "date-time": - d.dateFormat = time.RFC3339 - case "date": - d.dateFormat = "2006-01-02" - } - } - decoderFields[ptag.name] = decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} - d.dateFormat = oldFormat - } - - return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { - if field := value.FieldByName("JSON"); field.IsValid() { - if raw := field.FieldByName("raw"); raw.IsValid() { - setUnexportedField(raw, node.Raw) - } - } - - for _, decoder := range anonymousDecoders { - // ignore errors - decoder.fn(node, value.FieldByIndex(decoder.idx), state) - } - - if inlineDecoder != nil { - var meta Field - dest := value.FieldByIndex(inlineDecoder.idx) - isValid := false - if dest.IsValid() && node.Type != gjson.Null { - err = inlineDecoder.fn(node, dest, state) - if err == nil { - isValid = true - } - } - - if node.Type == gjson.Null { - meta = Field{ - raw: node.Raw, - status: null, - } - } else if !isValid { - meta = Field{ - raw: node.Raw, - status: invalid, - } - } else if isValid { - meta = Field{ - raw: node.Raw, - status: valid, - } - } - if metadata := getSubField(value, inlineDecoder.idx, inlineDecoder.goname); metadata.IsValid() { - metadata.Set(reflect.ValueOf(meta)) - } - return err - } - - typedExtraType := reflect.Type(nil) - typedExtraFields := reflect.Value{} - if extraDecoder != nil { - typedExtraType = value.FieldByIndex(extraDecoder.idx).Type() - typedExtraFields = reflect.MakeMap(typedExtraType) - } - untypedExtraFields := map[string]Field{} - - for fieldName, itemNode := range node.Map() { - df, explicit := decoderFields[fieldName] - var ( - dest reflect.Value - fn decoderFunc - meta Field - ) - if explicit { - fn = df.fn - dest = value.FieldByIndex(df.idx) - } - if !explicit && extraDecoder != nil { - dest = reflect.New(typedExtraType.Elem()).Elem() - fn = extraDecoder.fn - } - - isValid := false - if dest.IsValid() && itemNode.Type != gjson.Null { - err = fn(itemNode, dest, state) - if err == nil { - isValid = true - } - } - - if itemNode.Type == gjson.Null { - meta = Field{ - raw: itemNode.Raw, - status: null, - } - } else if !isValid { - meta = Field{ - raw: itemNode.Raw, - status: invalid, - } - } else if isValid { - meta = Field{ - raw: itemNode.Raw, - status: valid, - } - } - - if explicit { - if metadata := getSubField(value, df.idx, df.goname); metadata.IsValid() { - metadata.Set(reflect.ValueOf(meta)) - } - } - if !explicit { - untypedExtraFields[fieldName] = meta - } - if !explicit && extraDecoder != nil { - typedExtraFields.SetMapIndex(reflect.ValueOf(fieldName), dest) - } - } - - if extraDecoder != nil && typedExtraFields.Len() > 0 { - value.FieldByIndex(extraDecoder.idx).Set(typedExtraFields) - } - - // Set exactness to 'extras' if there are untyped, extra fields. - if len(untypedExtraFields) > 0 && state.exactness > extras { - state.exactness = extras - } - - if metadata := getSubField(value, []int{-1}, "ExtraFields"); metadata.IsValid() && len(untypedExtraFields) > 0 { - metadata.Set(reflect.ValueOf(untypedExtraFields)) - } - return nil - } -} - -func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { - switch t.Kind() { - case reflect.String: - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - v.SetString(n.String()) - if guardStrict(state, n.Type != gjson.String) { - return fmt.Errorf("apijson: failed to parse string strictly") - } - // Everything that is not an object can be loosely stringified. - if n.Type == gjson.JSON { - return fmt.Errorf("apijson: failed to parse string") - } - if guardUnknown(state, v) { - return fmt.Errorf("apijson: failed string enum validation") - } - return nil - } - case reflect.Bool: - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - v.SetBool(n.Bool()) - if guardStrict(state, n.Type != gjson.True && n.Type != gjson.False) { - return fmt.Errorf("apijson: failed to parse bool strictly") - } - // Numbers and strings that are either 'true' or 'false' can be loosely - // deserialized as bool. - if n.Type == gjson.String && (n.Raw != "true" && n.Raw != "false") || n.Type == gjson.JSON { - return fmt.Errorf("apijson: failed to parse bool") - } - if guardUnknown(state, v) { - return fmt.Errorf("apijson: failed bool enum validation") - } - return nil - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - v.SetInt(n.Int()) - if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num))) { - return fmt.Errorf("apijson: failed to parse int strictly") - } - // Numbers, booleans, and strings that maybe look like numbers can be - // loosely deserialized as numbers. - if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { - return fmt.Errorf("apijson: failed to parse int") - } - if guardUnknown(state, v) { - return fmt.Errorf("apijson: failed int enum validation") - } - return nil - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - v.SetUint(n.Uint()) - if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num)) || n.Num < 0) { - return fmt.Errorf("apijson: failed to parse uint strictly") - } - // Numbers, booleans, and strings that maybe look like numbers can be - // loosely deserialized as uint. - if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { - return fmt.Errorf("apijson: failed to parse uint") - } - if guardUnknown(state, v) { - return fmt.Errorf("apijson: failed uint enum validation") - } - return nil - } - case reflect.Float32, reflect.Float64: - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - v.SetFloat(n.Float()) - if guardStrict(state, n.Type != gjson.Number) { - return fmt.Errorf("apijson: failed to parse float strictly") - } - // Numbers, booleans, and strings that maybe look like numbers can be - // loosely deserialized as floats. - if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { - return fmt.Errorf("apijson: failed to parse float") - } - if guardUnknown(state, v) { - return fmt.Errorf("apijson: failed float enum validation") - } - return nil - } - default: - return func(node gjson.Result, v reflect.Value, state *decoderState) error { - return fmt.Errorf("unknown type received at primitive decoder: %s", t.String()) - } - } -} - -func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc { - format := d.dateFormat - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - parsed, err := time.Parse(format, n.Str) - if err == nil { - v.Set(reflect.ValueOf(parsed).Convert(t)) - return nil - } - - if guardStrict(state, true) { - return err - } - - layouts := []string{ - "2006-01-02", - "2006-01-02T15:04:05Z07:00", - "2006-01-02T15:04:05Z0700", - "2006-01-02T15:04:05", - "2006-01-02 15:04:05Z07:00", - "2006-01-02 15:04:05Z0700", - "2006-01-02 15:04:05", - } - - for _, layout := range layouts { - parsed, err := time.Parse(layout, n.Str) - if err == nil { - v.Set(reflect.ValueOf(parsed).Convert(t)) - return nil - } - } - - return fmt.Errorf("unable to leniently parse date-time string: %s", n.Str) - } -} - -func setUnexportedField(field reflect.Value, value interface{}) { - reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(value)) -} - -func guardStrict(state *decoderState, cond bool) bool { - if !cond { - return false - } - - if state.strict { - return true - } - - state.exactness = loose - return false -} - -func canParseAsNumber(str string) bool { - _, err := strconv.ParseFloat(str, 64) - return err == nil -} - -func guardUnknown(state *decoderState, v reflect.Value) bool { - if have, ok := v.Interface().(interface{ IsKnown() bool }); guardStrict(state, ok && !have.IsKnown()) { - return true - } - return false -} diff --git a/packages/sdk/go/internal/apijson/encoder.go b/packages/sdk/go/internal/apijson/encoder.go deleted file mode 100644 index 66b319b66..000000000 --- a/packages/sdk/go/internal/apijson/encoder.go +++ /dev/null @@ -1,398 +0,0 @@ -package apijson - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/tidwall/sjson" - - "github.com/sst/opencode-sdk-go/internal/param" -) - -var encoders sync.Map // map[encoderEntry]encoderFunc - -// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have -// special characters that sjson interprets as a path. -var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace - -func Marshal(value interface{}) ([]byte, error) { - e := &encoder{dateFormat: time.RFC3339} - return e.marshal(value) -} - -func MarshalRoot(value interface{}) ([]byte, error) { - e := &encoder{root: true, dateFormat: time.RFC3339} - return e.marshal(value) -} - -type encoder struct { - dateFormat string - root bool -} - -type encoderFunc func(value reflect.Value) ([]byte, 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{}) ([]byte, error) { - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil, nil - } - typ := val.Type() - enc := e.typeEncoder(typ) - return enc(val) -} - -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(v reflect.Value) ([]byte, error) { - wg.Wait() - return f(v) - })) - 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 marshalerEncoder(v reflect.Value) ([]byte, error) { - return v.Interface().(json.Marshaler).MarshalJSON() -} - -func indirectMarshalerEncoder(v reflect.Value) ([]byte, error) { - return v.Addr().Interface().(json.Marshaler).MarshalJSON() -} - -func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - return e.newTimeTypeEncoder() - } - if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { - return marshalerEncoder - } - if !e.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { - return indirectMarshalerEncoder - } - e.root = false - switch t.Kind() { - case reflect.Pointer: - inner := t.Elem() - - innerEncoder := e.typeEncoder(inner) - return func(v reflect.Value) ([]byte, error) { - if !v.IsValid() || v.IsNil() { - return nil, nil - } - return innerEncoder(v.Elem()) - } - case reflect.Struct: - return e.newStructTypeEncoder(t) - case reflect.Array: - fallthrough - case reflect.Slice: - 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(v reflect.Value) ([]byte, error) { - return json.Marshal(v.Interface()) - } - case reflect.Bool: - return func(v reflect.Value) ([]byte, error) { - if v.Bool() { - return []byte("true"), nil - } - return []byte("false"), nil - } - case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - return func(v reflect.Value) ([]byte, error) { - return []byte(strconv.FormatInt(v.Int(), 10)), nil - } - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return func(v reflect.Value) ([]byte, error) { - return []byte(strconv.FormatUint(v.Uint(), 10)), nil - } - case reflect.Float32: - return func(v reflect.Value) ([]byte, error) { - return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 32)), nil - } - case reflect.Float64: - return func(v reflect.Value) ([]byte, error) { - return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 64)), nil - } - default: - return func(v reflect.Value) ([]byte, error) { - return nil, 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(value reflect.Value) ([]byte, error) { - json := []byte("[]") - for i := 0; i < value.Len(); i++ { - var value, err = itemEncoder(value.Index(i)) - if err != nil { - return nil, err - } - if value == nil { - // Assume that empty items should be inserted as `null` so that the output array - // will be the same length as the input array - value = []byte("null") - } - - json, err = sjson.SetRawBytes(json, "-1", value) - if err != nil { - return nil, err - } - } - - return json, 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 := parseJSONStructTag(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(value reflect.Value) (json []byte, err error) { - json = []byte("{}") - - for _, ef := range encoderFields { - field := value.FieldByIndex(ef.idx) - encoded, err := ef.fn(field) - if err != nil { - return nil, err - } - if encoded == nil { - continue - } - json, err = sjson.SetRawBytes(json, EscapeSJSONKey(ef.tag.name), encoded) - if err != nil { - return nil, err - } - } - - if extraEncoder != nil { - json, err = e.encodeMapEntries(json, value.FieldByIndex(extraEncoder.idx)) - if err != nil { - return nil, err - } - } - return - } -} - -func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { - f, _ := t.FieldByName("Value") - enc := e.typeEncoder(f.Type) - - return func(value reflect.Value) (json []byte, err error) { - present := value.FieldByName("Present") - if !present.Bool() { - return nil, nil - } - null := value.FieldByName("Null") - if null.Bool() { - return []byte("null"), nil - } - raw := value.FieldByName("Raw") - if !raw.IsNil() { - return e.typeEncoder(raw.Type())(raw) - } - return enc(value.FieldByName("Value")) - } -} - -func (e *encoder) newTimeTypeEncoder() encoderFunc { - format := e.dateFormat - return func(value reflect.Value) (json []byte, err error) { - return []byte(`"` + value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format) + `"`), nil - } -} - -func (e encoder) newInterfaceEncoder() encoderFunc { - return func(value reflect.Value) ([]byte, error) { - value = value.Elem() - if !value.IsValid() { - return nil, nil - } - return e.typeEncoder(value.Type())(value) - } -} - -// 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(json []byte, v reflect.Value) ([]byte, error) { - type mapPair struct { - key []byte - value reflect.Value - } - - pairs := []mapPair{} - keyEncoder := e.typeEncoder(v.Type().Key()) - - iter := v.MapRange() - for iter.Next() { - var encodedKeyString string - if iter.Key().Type().Kind() == reflect.String { - encodedKeyString = iter.Key().String() - } else { - var err error - encodedKeyBytes, err := keyEncoder(iter.Key()) - if err != nil { - return nil, err - } - encodedKeyString = string(encodedKeyBytes) - } - encodedKey := []byte(encodedKeyString) - pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()}) - } - - // Ensure deterministic output - sort.Slice(pairs, func(i, j int) bool { - return bytes.Compare(pairs[i].key, pairs[j].key) < 0 - }) - - elementEncoder := e.typeEncoder(v.Type().Elem()) - for _, p := range pairs { - encodedValue, err := elementEncoder(p.value) - if err != nil { - return nil, err - } - if len(encodedValue) == 0 { - continue - } - json, err = sjson.SetRawBytes(json, EscapeSJSONKey(string(p.key)), encodedValue) - if err != nil { - return nil, err - } - } - - return json, nil -} - -func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { - return func(value reflect.Value) ([]byte, error) { - json := []byte("{}") - var err error - json, err = e.encodeMapEntries(json, value) - if err != nil { - return nil, err - } - return json, nil - } -} diff --git a/packages/sdk/go/internal/apijson/field.go b/packages/sdk/go/internal/apijson/field.go deleted file mode 100644 index 3ef207c58..000000000 --- a/packages/sdk/go/internal/apijson/field.go +++ /dev/null @@ -1,41 +0,0 @@ -package apijson - -import "reflect" - -type status uint8 - -const ( - missing status = iota - null - invalid - valid -) - -type Field struct { - raw string - status status -} - -// Returns true if the field is explicitly `null` _or_ if it is not present at all (ie, missing). -// To check if the field's key is present in the JSON with an explicit null value, -// you must check `f.IsNull() && !f.IsMissing()`. -func (j Field) IsNull() bool { return j.status <= null } -func (j Field) IsMissing() bool { return j.status == missing } -func (j Field) IsInvalid() bool { return j.status == invalid } -func (j Field) Raw() string { return j.raw } - -func getSubField(root reflect.Value, index []int, name string) reflect.Value { - strct := root.FieldByIndex(index[:len(index)-1]) - if !strct.IsValid() { - panic("couldn't find encapsulating struct for field " + name) - } - meta := strct.FieldByName("JSON") - if !meta.IsValid() { - return reflect.Value{} - } - field := meta.FieldByName(name) - if !field.IsValid() { - return reflect.Value{} - } - return field -} diff --git a/packages/sdk/go/internal/apijson/field_test.go b/packages/sdk/go/internal/apijson/field_test.go deleted file mode 100644 index 2e170c76f..000000000 --- a/packages/sdk/go/internal/apijson/field_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package apijson - -import ( - "testing" - "time" - - "github.com/sst/opencode-sdk-go/internal/param" -) - -type Struct struct { - A string `json:"a"` - B int64 `json:"b"` -} - -type FieldStruct struct { - A param.Field[string] `json:"a"` - B param.Field[int64] `json:"b"` - C param.Field[Struct] `json:"c"` - D param.Field[time.Time] `json:"d" format:"date"` - E param.Field[time.Time] `json:"e" format:"date-time"` - F param.Field[int64] `json:"f"` -} - -func TestFieldMarshal(t *testing.T) { - tests := map[string]struct { - value interface{} - expected string - }{ - "null_string": {param.Field[string]{Present: true, Null: true}, "null"}, - "null_int": {param.Field[int]{Present: true, Null: true}, "null"}, - "null_int64": {param.Field[int64]{Present: true, Null: true}, "null"}, - "null_struct": {param.Field[Struct]{Present: true, Null: true}, "null"}, - - "string": {param.Field[string]{Present: true, Value: "string"}, `"string"`}, - "int": {param.Field[int]{Present: true, Value: 123}, "123"}, - "int64": {param.Field[int64]{Present: true, Value: int64(123456789123456789)}, "123456789123456789"}, - "struct": {param.Field[Struct]{Present: true, Value: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, - - "string_raw": {param.Field[int]{Present: true, Raw: "string"}, `"string"`}, - "int_raw": {param.Field[int]{Present: true, Raw: 123}, "123"}, - "int64_raw": {param.Field[int]{Present: true, Raw: int64(123456789123456789)}, "123456789123456789"}, - "struct_raw": {param.Field[int]{Present: true, Raw: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, - - "param_struct": { - FieldStruct{ - A: param.Field[string]{Present: true, Value: "hello"}, - B: param.Field[int64]{Present: true, Value: int64(12)}, - D: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, - E: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, - }, - `{"a":"hello","b":12,"d":"2023-03-18","e":"2023-03-18T14:47:38Z"}`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - b, err := Marshal(test.value) - if err != nil { - t.Fatalf("didn't expect error %v", err) - } - if string(b) != test.expected { - t.Fatalf("expected %s, received %s", test.expected, string(b)) - } - }) - } -} diff --git a/packages/sdk/go/internal/apijson/json_test.go b/packages/sdk/go/internal/apijson/json_test.go deleted file mode 100644 index d4c628359..000000000 --- a/packages/sdk/go/internal/apijson/json_test.go +++ /dev/null @@ -1,617 +0,0 @@ -package apijson - -import ( - "reflect" - "strings" - "testing" - "time" - - "github.com/tidwall/gjson" -) - -func P[T any](v T) *T { return &v } - -type Primitives struct { - A bool `json:"a"` - B int `json:"b"` - C uint `json:"c"` - D float64 `json:"d"` - E float32 `json:"e"` - F []int `json:"f"` -} - -type PrimitivePointers struct { - A *bool `json:"a"` - B *int `json:"b"` - C *uint `json:"c"` - D *float64 `json:"d"` - E *float32 `json:"e"` - F *[]int `json:"f"` -} - -type Slices struct { - Slice []Primitives `json:"slices"` -} - -type DateTime struct { - Date time.Time `json:"date" format:"date"` - DateTime time.Time `json:"date-time" format:"date-time"` -} - -type AdditionalProperties struct { - A bool `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` -} - -type TypedAdditionalProperties struct { - A bool `json:"a"` - ExtraFields map[string]int `json:"-,extras"` -} - -type EmbeddedStruct struct { - A bool `json:"a"` - B string `json:"b"` - - JSON EmbeddedStructJSON -} - -type EmbeddedStructJSON struct { - A Field - B Field - ExtraFields map[string]Field - raw string -} - -type EmbeddedStructs struct { - EmbeddedStruct - A *int `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` - - JSON EmbeddedStructsJSON -} - -type EmbeddedStructsJSON struct { - A Field - ExtraFields map[string]Field - raw string -} - -type Recursive struct { - Name string `json:"name"` - Child *Recursive `json:"child"` -} - -type JSONFieldStruct struct { - A bool `json:"a"` - B int64 `json:"b"` - C string `json:"c"` - D string `json:"d"` - ExtraFields map[string]int64 `json:"-,extras"` - JSON JSONFieldStructJSON `json:"-,metadata"` -} - -type JSONFieldStructJSON struct { - A Field - B Field - C Field - D Field - ExtraFields map[string]Field - raw string -} - -type UnknownStruct struct { - Unknown interface{} `json:"unknown"` -} - -type UnionStruct struct { - Union Union `json:"union" format:"date"` -} - -type Union interface { - union() -} - -type Inline struct { - InlineField Primitives `json:"-,inline"` - JSON InlineJSON `json:"-,metadata"` -} - -type InlineArray struct { - InlineField []string `json:"-,inline"` - JSON InlineJSON `json:"-,metadata"` -} - -type InlineJSON struct { - InlineField Field - raw string -} - -type UnionInteger int64 - -func (UnionInteger) union() {} - -type UnionStructA struct { - Type string `json:"type"` - A string `json:"a"` - B string `json:"b"` -} - -func (UnionStructA) union() {} - -type UnionStructB struct { - Type string `json:"type"` - A string `json:"a"` -} - -func (UnionStructB) union() {} - -type UnionTime time.Time - -func (UnionTime) union() {} - -func init() { - RegisterUnion(reflect.TypeOf((*Union)(nil)).Elem(), "type", - UnionVariant{ - TypeFilter: gjson.String, - Type: reflect.TypeOf(UnionTime{}), - }, - UnionVariant{ - TypeFilter: gjson.Number, - Type: reflect.TypeOf(UnionInteger(0)), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - DiscriminatorValue: "typeA", - Type: reflect.TypeOf(UnionStructA{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - DiscriminatorValue: "typeB", - Type: reflect.TypeOf(UnionStructB{}), - }, - ) -} - -type ComplexUnionStruct struct { - Union ComplexUnion `json:"union"` -} - -type ComplexUnion interface { - complexUnion() -} - -type ComplexUnionA struct { - Boo string `json:"boo"` - Foo bool `json:"foo"` -} - -func (ComplexUnionA) complexUnion() {} - -type ComplexUnionB struct { - Boo bool `json:"boo"` - Foo string `json:"foo"` -} - -func (ComplexUnionB) complexUnion() {} - -type ComplexUnionC struct { - Boo int64 `json:"boo"` -} - -func (ComplexUnionC) complexUnion() {} - -type ComplexUnionTypeA struct { - Baz int64 `json:"baz"` - Type TypeA `json:"type"` -} - -func (ComplexUnionTypeA) complexUnion() {} - -type TypeA string - -func (t TypeA) IsKnown() bool { - return t == "a" -} - -type ComplexUnionTypeB struct { - Baz int64 `json:"baz"` - Type TypeB `json:"type"` -} - -type TypeB string - -func (t TypeB) IsKnown() bool { - return t == "b" -} - -type UnmarshalStruct struct { - Foo string `json:"foo"` - prop bool `json:"-"` -} - -func (r *UnmarshalStruct) UnmarshalJSON(json []byte) error { - r.prop = true - return UnmarshalRoot(json, r) -} - -func (ComplexUnionTypeB) complexUnion() {} - -func init() { - RegisterUnion(reflect.TypeOf((*ComplexUnion)(nil)).Elem(), "", - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(ComplexUnionA{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(ComplexUnionB{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(ComplexUnionC{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(ComplexUnionTypeA{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(ComplexUnionTypeB{}), - }, - ) -} - -type MarshallingUnionStruct struct { - Union MarshallingUnion -} - -func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) { - *r = MarshallingUnionStruct{} - err = UnmarshalRoot(data, &r.Union) - return -} - -func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) { - return MarshalRoot(r.Union) -} - -type MarshallingUnion interface { - marshallingUnion() -} - -type MarshallingUnionA struct { - Boo string `json:"boo"` -} - -func (MarshallingUnionA) marshallingUnion() {} - -func (r *MarshallingUnionA) UnmarshalJSON(data []byte) (err error) { - return UnmarshalRoot(data, r) -} - -type MarshallingUnionB struct { - Foo string `json:"foo"` -} - -func (MarshallingUnionB) marshallingUnion() {} - -func (r *MarshallingUnionB) UnmarshalJSON(data []byte) (err error) { - return UnmarshalRoot(data, r) -} - -func init() { - RegisterUnion( - reflect.TypeOf((*MarshallingUnion)(nil)).Elem(), - "", - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(MarshallingUnionA{}), - }, - UnionVariant{ - TypeFilter: gjson.JSON, - Type: reflect.TypeOf(MarshallingUnionB{}), - }, - ) -} - -var tests = map[string]struct { - buf string - val interface{} -}{ - "true": {"true", true}, - "false": {"false", false}, - "int": {"1", 1}, - "int_bigger": {"12324", 12324}, - "int_string_coerce": {`"65"`, 65}, - "int_boolean_coerce": {"true", 1}, - "int64": {"1", int64(1)}, - "int64_huge": {"123456789123456789", int64(123456789123456789)}, - "uint": {"1", uint(1)}, - "uint_bigger": {"12324", uint(12324)}, - "uint_coerce": {`"65"`, uint(65)}, - "float_1.54": {"1.54", float32(1.54)}, - "float_1.89": {"1.89", float64(1.89)}, - "string": {`"str"`, "str"}, - "string_int_coerce": {`12`, "12"}, - "array_string": {`["foo","bar"]`, []string{"foo", "bar"}}, - "array_int": {`[1,2]`, []int{1, 2}}, - "array_int_coerce": {`["1",2]`, []int{1, 2}}, - - "ptr_true": {"true", P(true)}, - "ptr_false": {"false", P(false)}, - "ptr_int": {"1", P(1)}, - "ptr_int_bigger": {"12324", P(12324)}, - "ptr_int_string_coerce": {`"65"`, P(65)}, - "ptr_int_boolean_coerce": {"true", P(1)}, - "ptr_int64": {"1", P(int64(1))}, - "ptr_int64_huge": {"123456789123456789", P(int64(123456789123456789))}, - "ptr_uint": {"1", P(uint(1))}, - "ptr_uint_bigger": {"12324", P(uint(12324))}, - "ptr_uint_coerce": {`"65"`, P(uint(65))}, - "ptr_float_1.54": {"1.54", P(float32(1.54))}, - "ptr_float_1.89": {"1.89", P(float64(1.89))}, - - "date_time": {`"2007-03-01T13:00:00Z"`, time.Date(2007, time.March, 1, 13, 0, 0, 0, time.UTC)}, - "date_time_nano_coerce": {`"2007-03-01T13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, - - "date_time_missing_t_coerce": {`"2007-03-01 13:03:05Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, - "date_time_missing_timezone_coerce": {`"2007-03-01T13:03:05"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, - // note: using -1200 to minimize probability of conflicting with the local timezone of the test runner - // see https://en.wikipedia.org/wiki/UTC%E2%88%9212:00 - "date_time_missing_timezone_colon_coerce": {`"2007-03-01T13:03:05-1200"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.FixedZone("", -12*60*60))}, - "date_time_nano_missing_t_coerce": {`"2007-03-01 13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, - - "map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}}, - "map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f@g?h":"bar"}`, map[string]string{":a.b.c*:d*-1e.f@g?h": "bar"}}, - "map_interface": {`{"a":1,"b":"str","c":false}`, map[string]interface{}{"a": float64(1), "b": "str", "c": false}}, - - "primitive_struct": { - `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, - Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - }, - - "slices": { - `{"slices":[{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}]}`, - 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": { - `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4,5]}`, - 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": { - `{"date":"2006-01-02","date-time":"2006-01-02T15:04:05Z"}`, - 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": { - `{"a":true,"bar":"value","foo":true}`, - AdditionalProperties{ - A: true, - ExtraFields: map[string]interface{}{ - "bar": "value", - "foo": true, - }, - }, - }, - - "embedded_struct": { - `{"a":1,"b":"bar"}`, - EmbeddedStructs{ - EmbeddedStruct: EmbeddedStruct{ - A: true, - B: "bar", - JSON: EmbeddedStructJSON{ - A: Field{raw: `1`, status: valid}, - B: Field{raw: `"bar"`, status: valid}, - raw: `{"a":1,"b":"bar"}`, - }, - }, - A: P(1), - ExtraFields: map[string]interface{}{"b": "bar"}, - JSON: EmbeddedStructsJSON{ - A: Field{raw: `1`, status: valid}, - ExtraFields: map[string]Field{ - "b": {raw: `"bar"`, status: valid}, - }, - raw: `{"a":1,"b":"bar"}`, - }, - }, - }, - - "recursive_struct": { - `{"child":{"name":"Alex"},"name":"Robert"}`, - Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, - }, - - "metadata_coerce": { - `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, - JSONFieldStruct{ - A: false, - B: 12, - C: "", - JSON: JSONFieldStructJSON{ - raw: `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, - A: Field{raw: `"12"`, status: invalid}, - B: Field{raw: `"12"`, status: valid}, - C: Field{raw: "null", status: null}, - D: Field{raw: "", status: missing}, - ExtraFields: map[string]Field{ - "extra_typed": { - raw: "12", - status: valid, - }, - "extra_untyped": { - raw: `{"foo":"bar"}`, - status: invalid, - }, - }, - }, - ExtraFields: map[string]int64{ - "extra_typed": 12, - "extra_untyped": 0, - }, - }, - }, - - "unknown_struct_number": { - `{"unknown":12}`, - UnknownStruct{ - Unknown: 12., - }, - }, - - "unknown_struct_map": { - `{"unknown":{"foo":"bar"}}`, - UnknownStruct{ - Unknown: map[string]interface{}{ - "foo": "bar", - }, - }, - }, - - "union_integer": { - `{"union":12}`, - UnionStruct{ - Union: UnionInteger(12), - }, - }, - - "union_struct_discriminated_a": { - `{"union":{"a":"foo","b":"bar","type":"typeA"}}`, - UnionStruct{ - Union: UnionStructA{ - Type: "typeA", - A: "foo", - B: "bar", - }, - }, - }, - - "union_struct_discriminated_b": { - `{"union":{"a":"foo","type":"typeB"}}`, - UnionStruct{ - Union: UnionStructB{ - Type: "typeB", - A: "foo", - }, - }, - }, - - "union_struct_time": { - `{"union":"2010-05-23"}`, - UnionStruct{ - Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), - }, - }, - - "complex_union_a": { - `{"union":{"boo":"12","foo":true}}`, - ComplexUnionStruct{Union: ComplexUnionA{Boo: "12", Foo: true}}, - }, - - "complex_union_b": { - `{"union":{"boo":true,"foo":"12"}}`, - ComplexUnionStruct{Union: ComplexUnionB{Boo: true, Foo: "12"}}, - }, - - "complex_union_c": { - `{"union":{"boo":12}}`, - ComplexUnionStruct{Union: ComplexUnionC{Boo: 12}}, - }, - - "complex_union_type_a": { - `{"union":{"baz":12,"type":"a"}}`, - ComplexUnionStruct{Union: ComplexUnionTypeA{Baz: 12, Type: TypeA("a")}}, - }, - - "complex_union_type_b": { - `{"union":{"baz":12,"type":"b"}}`, - ComplexUnionStruct{Union: ComplexUnionTypeB{Baz: 12, Type: TypeB("b")}}, - }, - - "marshalling_union_a": { - `{"boo":"hello"}`, - MarshallingUnionStruct{Union: MarshallingUnionA{Boo: "hello"}}, - }, - "marshalling_union_b": { - `{"foo":"hi"}`, - MarshallingUnionStruct{Union: MarshallingUnionB{Foo: "hi"}}, - }, - - "unmarshal": { - `{"foo":"hello"}`, - &UnmarshalStruct{Foo: "hello", prop: true}, - }, - - "array_of_unmarshal": { - `[{"foo":"hello"}]`, - []UnmarshalStruct{{Foo: "hello", prop: true}}, - }, - - "inline_coerce": { - `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, - Inline{ - InlineField: Primitives{A: false, B: 237628372683, C: 0x28e, D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - JSON: InlineJSON{ - InlineField: Field{raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", status: 3}, - raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", - }, - }, - }, - - "inline_array_coerce": { - `["Hello","foo","bar"]`, - InlineArray{ - InlineField: []string{"Hello", "foo", "bar"}, - JSON: InlineJSON{ - InlineField: Field{raw: `["Hello","foo","bar"]`, status: 3}, - raw: `["Hello","foo","bar"]`, - }, - }, - }, -} - -func TestDecode(t *testing.T) { - for name, test := range tests { - t.Run(name, func(t *testing.T) { - result := reflect.New(reflect.TypeOf(test.val)) - if err := Unmarshal([]byte(test.buf), result.Interface()); err != nil { - t.Fatalf("deserialization of %v failed with error %v", result, err) - } - if !reflect.DeepEqual(result.Elem().Interface(), test.val) { - t.Fatalf("expected '%s' to deserialize to \n%#v\nbut got\n%#v", test.buf, test.val, result.Elem().Interface()) - } - }) - } -} - -func TestEncode(t *testing.T) { - for name, test := range tests { - if strings.HasSuffix(name, "_coerce") { - continue - } - t.Run(name, func(t *testing.T) { - raw, err := Marshal(test.val) - if err != nil { - t.Fatalf("serialization of %v failed with error %v", test.val, err) - } - if string(raw) != test.buf { - t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.buf, string(raw)) - } - }) - } -} diff --git a/packages/sdk/go/internal/apijson/port.go b/packages/sdk/go/internal/apijson/port.go deleted file mode 100644 index 502ab7780..000000000 --- a/packages/sdk/go/internal/apijson/port.go +++ /dev/null @@ -1,120 +0,0 @@ -package apijson - -import ( - "fmt" - "reflect" -) - -// Port copies over values from one struct to another struct. -func Port(from any, to any) error { - toVal := reflect.ValueOf(to) - fromVal := reflect.ValueOf(from) - - if toVal.Kind() != reflect.Ptr || toVal.IsNil() { - return fmt.Errorf("destination must be a non-nil pointer") - } - - for toVal.Kind() == reflect.Ptr { - toVal = toVal.Elem() - } - toType := toVal.Type() - - for fromVal.Kind() == reflect.Ptr { - fromVal = fromVal.Elem() - } - fromType := fromVal.Type() - - if toType.Kind() != reflect.Struct { - return fmt.Errorf("destination must be a non-nil pointer to a struct (%v %v)", toType, toType.Kind()) - } - - values := map[string]reflect.Value{} - fields := map[string]reflect.Value{} - - fromJSON := fromVal.FieldByName("JSON") - toJSON := toVal.FieldByName("JSON") - - // Iterate through the fields of v and load all the "normal" fields in the struct to the map of - // string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j. - var getFields func(t reflect.Type, v reflect.Value) - getFields = func(t reflect.Type, v reflect.Value) { - j := v.FieldByName("JSON") - - // Recurse into anonymous fields first, since the fields on the object should win over the fields in the - // embedded object. - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - if field.Anonymous { - getFields(field.Type, v.Field(i)) - continue - } - } - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - ptag, ok := parseJSONStructTag(field) - if !ok || ptag.name == "-" { - continue - } - values[ptag.name] = v.Field(i) - if j.IsValid() { - fields[ptag.name] = j.FieldByName(field.Name) - } - } - } - getFields(fromType, fromVal) - - // Use the values from the previous step to populate the 'to' struct. - for i := 0; i < toType.NumField(); i++ { - field := toType.Field(i) - ptag, ok := parseJSONStructTag(field) - if !ok { - continue - } - if ptag.name == "-" { - continue - } - if value, ok := values[ptag.name]; ok { - delete(values, ptag.name) - if field.Type.Kind() == reflect.Interface { - toVal.Field(i).Set(value) - } else { - switch value.Kind() { - case reflect.String: - toVal.Field(i).SetString(value.String()) - case reflect.Bool: - toVal.Field(i).SetBool(value.Bool()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - toVal.Field(i).SetInt(value.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - toVal.Field(i).SetUint(value.Uint()) - case reflect.Float32, reflect.Float64: - toVal.Field(i).SetFloat(value.Float()) - default: - toVal.Field(i).Set(value) - } - } - } - - if fromJSONField, ok := fields[ptag.name]; ok { - if toJSONField := toJSON.FieldByName(field.Name); toJSONField.IsValid() { - toJSONField.Set(fromJSONField) - } - } - } - - // Finally, copy over the .JSON.raw and .JSON.ExtraFields - if toJSON.IsValid() { - if raw := toJSON.FieldByName("raw"); raw.IsValid() { - setUnexportedField(raw, fromJSON.Interface().(interface{ RawJSON() string }).RawJSON()) - } - - if toExtraFields := toJSON.FieldByName("ExtraFields"); toExtraFields.IsValid() { - if fromExtraFields := fromJSON.FieldByName("ExtraFields"); fromExtraFields.IsValid() { - setUnexportedField(toExtraFields, fromExtraFields.Interface()) - } - } - } - - return nil -} diff --git a/packages/sdk/go/internal/apijson/port_test.go b/packages/sdk/go/internal/apijson/port_test.go deleted file mode 100644 index 115405335..000000000 --- a/packages/sdk/go/internal/apijson/port_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package apijson - -import ( - "reflect" - "testing" -) - -type Metadata struct { - CreatedAt string `json:"created_at"` -} - -// Card is the "combined" type of CardVisa and CardMastercard -type Card struct { - Processor CardProcessor `json:"processor"` - Data any `json:"data"` - IsFoo bool `json:"is_foo"` - IsBar bool `json:"is_bar"` - Metadata Metadata `json:"metadata"` - Value interface{} `json:"value"` - - JSON cardJSON -} - -type cardJSON struct { - Processor Field - Data Field - IsFoo Field - IsBar Field - Metadata Field - Value Field - ExtraFields map[string]Field - raw string -} - -func (r cardJSON) RawJSON() string { return r.raw } - -type CardProcessor string - -// CardVisa -type CardVisa struct { - Processor CardVisaProcessor `json:"processor"` - Data CardVisaData `json:"data"` - IsFoo bool `json:"is_foo"` - Metadata Metadata `json:"metadata"` - Value string `json:"value"` - - JSON cardVisaJSON -} - -type cardVisaJSON struct { - Processor Field - Data Field - IsFoo Field - Metadata Field - Value Field - ExtraFields map[string]Field - raw string -} - -func (r cardVisaJSON) RawJSON() string { return r.raw } - -type CardVisaProcessor string - -type CardVisaData struct { - Foo string `json:"foo"` -} - -// CardMastercard -type CardMastercard struct { - Processor CardMastercardProcessor `json:"processor"` - Data CardMastercardData `json:"data"` - IsBar bool `json:"is_bar"` - Metadata Metadata `json:"metadata"` - Value bool `json:"value"` - - JSON cardMastercardJSON -} - -type cardMastercardJSON struct { - Processor Field - Data Field - IsBar Field - Metadata Field - Value Field - ExtraFields map[string]Field - raw string -} - -func (r cardMastercardJSON) RawJSON() string { return r.raw } - -type CardMastercardProcessor string - -type CardMastercardData struct { - Bar int64 `json:"bar"` -} - -type CommonFields struct { - Metadata Metadata `json:"metadata"` - Value string `json:"value"` - - JSON commonFieldsJSON -} - -type commonFieldsJSON struct { - Metadata Field - Value Field - ExtraFields map[string]Field - raw string -} - -type CardEmbedded struct { - CommonFields - Processor CardVisaProcessor `json:"processor"` - Data CardVisaData `json:"data"` - IsFoo bool `json:"is_foo"` - - JSON cardEmbeddedJSON -} - -type cardEmbeddedJSON struct { - Processor Field - Data Field - IsFoo Field - ExtraFields map[string]Field - raw string -} - -func (r cardEmbeddedJSON) RawJSON() string { return r.raw } - -var portTests = map[string]struct { - from any - to any -}{ - "visa to card": { - CardVisa{ - Processor: "visa", - IsFoo: true, - Data: CardVisaData{ - Foo: "foo", - }, - Metadata: Metadata{ - CreatedAt: "Mar 29 2024", - }, - Value: "value", - JSON: cardVisaJSON{ - raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`, - Processor: Field{raw: `"visa"`, status: valid}, - IsFoo: Field{raw: `true`, status: valid}, - Data: Field{raw: `{"foo":"foo"}`, status: valid}, - Value: Field{raw: `"value"`, status: valid}, - ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}}, - }, - }, - Card{ - Processor: "visa", - IsFoo: true, - IsBar: false, - Data: CardVisaData{ - Foo: "foo", - }, - Metadata: Metadata{ - CreatedAt: "Mar 29 2024", - }, - Value: "value", - JSON: cardJSON{ - raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`, - Processor: Field{raw: `"visa"`, status: valid}, - IsFoo: Field{raw: `true`, status: valid}, - Data: Field{raw: `{"foo":"foo"}`, status: valid}, - Value: Field{raw: `"value"`, status: valid}, - ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}}, - }, - }, - }, - "mastercard to card": { - CardMastercard{ - Processor: "mastercard", - IsBar: true, - Data: CardMastercardData{ - Bar: 13, - }, - Value: false, - }, - Card{ - Processor: "mastercard", - IsFoo: false, - IsBar: true, - Data: CardMastercardData{ - Bar: 13, - }, - Value: false, - }, - }, - "embedded to card": { - CardEmbedded{ - CommonFields: CommonFields{ - Metadata: Metadata{ - CreatedAt: "Mar 29 2024", - }, - Value: "embedded_value", - JSON: commonFieldsJSON{ - Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: valid}, - Value: Field{raw: `"embedded_value"`, status: valid}, - raw: `should not matter`, - }, - }, - Processor: "visa", - IsFoo: true, - Data: CardVisaData{ - Foo: "embedded_foo", - }, - JSON: cardEmbeddedJSON{ - raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, - Processor: Field{raw: `"visa"`, status: valid}, - IsFoo: Field{raw: `true`, status: valid}, - Data: Field{raw: `{"foo":"embedded_foo"}`, status: valid}, - }, - }, - Card{ - Processor: "visa", - IsFoo: true, - IsBar: false, - Data: CardVisaData{ - Foo: "embedded_foo", - }, - Metadata: Metadata{ - CreatedAt: "Mar 29 2024", - }, - Value: "embedded_value", - JSON: cardJSON{ - raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, - Processor: Field{raw: `"visa"`, status: 0x3}, - IsFoo: Field{raw: "true", status: 0x3}, - Data: Field{raw: `{"foo":"embedded_foo"}`, status: 0x3}, - Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: 0x3}, - Value: Field{raw: `"embedded_value"`, status: 0x3}, - }, - }, - }, -} - -func TestPort(t *testing.T) { - for name, test := range portTests { - t.Run(name, func(t *testing.T) { - toVal := reflect.New(reflect.TypeOf(test.to)) - - err := Port(test.from, toVal.Interface()) - if err != nil { - t.Fatalf("port of %v failed with error %v", test.from, err) - } - - if !reflect.DeepEqual(toVal.Elem().Interface(), test.to) { - t.Fatalf("expected:\n%+#v\n\nto port to:\n%+#v\n\nbut got:\n%+#v", test.from, test.to, toVal.Elem().Interface()) - } - }) - } -} diff --git a/packages/sdk/go/internal/apijson/registry.go b/packages/sdk/go/internal/apijson/registry.go deleted file mode 100644 index 119cc5ff8..000000000 --- a/packages/sdk/go/internal/apijson/registry.go +++ /dev/null @@ -1,41 +0,0 @@ -package apijson - -import ( - "reflect" - - "github.com/tidwall/gjson" -) - -type UnionVariant struct { - TypeFilter gjson.Type - DiscriminatorValue interface{} - Type reflect.Type -} - -var unionRegistry = map[reflect.Type]unionEntry{} -var unionVariants = map[reflect.Type]interface{}{} - -type unionEntry struct { - discriminatorKey string - variants []UnionVariant -} - -func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVariant) { - unionRegistry[typ] = unionEntry{ - discriminatorKey: discriminator, - variants: variants, - } - for _, variant := range variants { - unionVariants[variant.Type] = typ - } -} - -// Useful to wrap a union type to force it to use [apijson.UnmarshalJSON] since you cannot define an -// UnmarshalJSON function on the interface itself. -type UnionUnmarshaler[T any] struct { - Value T -} - -func (c *UnionUnmarshaler[T]) UnmarshalJSON(buf []byte) error { - return UnmarshalRoot(buf, &c.Value) -} diff --git a/packages/sdk/go/internal/apijson/tag.go b/packages/sdk/go/internal/apijson/tag.go deleted file mode 100644 index 812fb3caf..000000000 --- a/packages/sdk/go/internal/apijson/tag.go +++ /dev/null @@ -1,47 +0,0 @@ -package apijson - -import ( - "reflect" - "strings" -) - -const jsonStructTag = "json" -const formatStructTag = "format" - -type parsedStructTag struct { - name string - required bool - extras bool - metadata bool - inline bool -} - -func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { - 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 - case "inline": - tag.inline = true - } - } - return -} - -func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { - format, ok = field.Tag.Lookup(formatStructTag) - return -} diff --git a/packages/sdk/go/internal/apiquery/encoder.go b/packages/sdk/go/internal/apiquery/encoder.go deleted file mode 100644 index 0922c2316..000000000 --- a/packages/sdk/go/internal/apiquery/encoder.go +++ /dev/null @@ -1,341 +0,0 @@ -package apiquery - -import ( - "encoding/json" - "fmt" - "reflect" - "strconv" - "strings" - "sync" - "time" - - "github.com/sst/opencode-sdk-go/internal/param" -) - -var encoders sync.Map // map[reflect.Type]encoderFunc - -type encoder struct { - dateFormat string - root bool - settings QuerySettings -} - -type encoderFunc func(key string, value reflect.Value) []Pair - -type encoderField struct { - tag parsedStructTag - fn encoderFunc - idx []int -} - -type encoderEntry struct { - reflect.Type - dateFormat string - root bool - settings QuerySettings -} - -type Pair struct { - key string - value string -} - -func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { - entry := encoderEntry{ - Type: t, - dateFormat: e.dateFormat, - root: e.root, - settings: e.settings, - } - - 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) []Pair { - wg.Wait() - return f(key, v) - })) - 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 marshalerEncoder(key string, value reflect.Value) []Pair { - s, _ := value.Interface().(json.Marshaler).MarshalJSON() - return []Pair{{key, string(s)}} -} - -func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - return e.newTimeTypeEncoder(t) - } - if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { - return marshalerEncoder - } - e.root = false - switch t.Kind() { - case reflect.Pointer: - encoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair) { - if !value.IsValid() || value.IsNil() { - return - } - pairs = encoder(key, value.Elem()) - return - } - case reflect.Struct: - return e.newStructTypeEncoder(t) - case reflect.Array: - fallthrough - case reflect.Slice: - 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) newStructTypeEncoder(t reflect.Type) encoderFunc { - if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { - return e.newFieldTypeEncoder(t) - } - - encoderFields := []encoderField{} - - // 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 query tag is not present, then we skip, which is intentionally - // different behavior from the stdlib. - ptag, ok := parseQueryStructTag(field) - if !ok { - continue - } - - if ptag.name == "-" && !ptag.inline { - 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{}) - - return func(key string, value reflect.Value) (pairs []Pair) { - for _, ef := range encoderFields { - var subkey string = e.renderKeyPath(key, ef.tag.name) - if ef.tag.inline { - subkey = key - } - - field := value.FieldByIndex(ef.idx) - pairs = append(pairs, ef.fn(subkey, field)...) - } - return - } -} - -func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { - keyEncoder := e.typeEncoder(t.Key()) - elementEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair) { - iter := value.MapRange() - for iter.Next() { - encodedKey := keyEncoder("", iter.Key()) - if len(encodedKey) != 1 { - panic("Unexpected number of parts for encoded map key. Are you using a non-primitive for this map?") - } - subkey := encodedKey[0].value - keyPath := e.renderKeyPath(key, subkey) - pairs = append(pairs, elementEncoder(keyPath, iter.Value())...) - } - return - } -} - -func (e *encoder) renderKeyPath(key string, subkey string) string { - if len(key) == 0 { - return subkey - } - if e.settings.NestedFormat == NestedQueryFormatDots { - return fmt.Sprintf("%s.%s", key, subkey) - } - return fmt.Sprintf("%s[%s]", key, subkey) -} - -func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { - switch e.settings.ArrayFormat { - case ArrayQueryFormatComma: - innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, v reflect.Value) []Pair { - elements := []string{} - for i := 0; i < v.Len(); i++ { - for _, pair := range innerEncoder("", v.Index(i)) { - elements = append(elements, pair.value) - } - } - if len(elements) == 0 { - return []Pair{} - } - return []Pair{{key, strings.Join(elements, ",")}} - } - case ArrayQueryFormatRepeat: - innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair) { - for i := 0; i < value.Len(); i++ { - pairs = append(pairs, innerEncoder(key, value.Index(i))...) - } - return pairs - } - case ArrayQueryFormatIndices: - panic("The array indices format is not supported yet") - case ArrayQueryFormatBrackets: - innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) []Pair { - pairs := []Pair{} - for i := 0; i < value.Len(); i++ { - pairs = append(pairs, innerEncoder(key+"[]", value.Index(i))...) - } - return pairs - } - default: - panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat)) - } -} - -func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { - switch t.Kind() { - case reflect.Pointer: - inner := t.Elem() - - innerEncoder := e.newPrimitiveTypeEncoder(inner) - return func(key string, v reflect.Value) []Pair { - if !v.IsValid() || v.IsNil() { - return nil - } - return innerEncoder(key, v.Elem()) - } - case reflect.String: - return func(key string, v reflect.Value) []Pair { - return []Pair{{key, v.String()}} - } - case reflect.Bool: - return func(key string, v reflect.Value) []Pair { - if v.Bool() { - return []Pair{{key, "true"}} - } - return []Pair{{key, "false"}} - } - case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - return func(key string, v reflect.Value) []Pair { - return []Pair{{key, strconv.FormatInt(v.Int(), 10)}} - } - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return func(key string, v reflect.Value) []Pair { - return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}} - } - case reflect.Float32, reflect.Float64: - return func(key string, v reflect.Value) []Pair { - return []Pair{{key, strconv.FormatFloat(v.Float(), 'f', -1, 64)}} - } - case reflect.Complex64, reflect.Complex128: - bitSize := 64 - if t.Kind() == reflect.Complex128 { - bitSize = 128 - } - return func(key string, v reflect.Value) []Pair { - return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}} - } - default: - return func(key string, v reflect.Value) []Pair { - 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) []Pair { - present := value.FieldByName("Present") - if !present.Bool() { - return nil - } - null := value.FieldByName("Null") - if null.Bool() { - // TODO: Error? - return nil - } - raw := value.FieldByName("Raw") - if !raw.IsNil() { - return e.typeEncoder(raw.Type())(key, raw) - } - return enc(key, value.FieldByName("Value")) - } -} - -func (e *encoder) newTimeTypeEncoder(t reflect.Type) encoderFunc { - format := e.dateFormat - return func(key string, value reflect.Value) []Pair { - return []Pair{{ - key, - value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format), - }} - } -} - -func (e encoder) newInterfaceEncoder() encoderFunc { - return func(key string, value reflect.Value) []Pair { - value = value.Elem() - if !value.IsValid() { - return nil - } - return e.typeEncoder(value.Type())(key, value) - } - -} diff --git a/packages/sdk/go/internal/apiquery/query.go b/packages/sdk/go/internal/apiquery/query.go deleted file mode 100644 index 6f90e9935..000000000 --- a/packages/sdk/go/internal/apiquery/query.go +++ /dev/null @@ -1,50 +0,0 @@ -package apiquery - -import ( - "net/url" - "reflect" - "time" -) - -func MarshalWithSettings(value interface{}, settings QuerySettings) url.Values { - e := encoder{time.RFC3339, true, settings} - kv := url.Values{} - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil - } - typ := val.Type() - for _, pair := range e.typeEncoder(typ)("", val) { - kv.Add(pair.key, pair.value) - } - return kv -} - -func Marshal(value interface{}) url.Values { - return MarshalWithSettings(value, QuerySettings{}) -} - -type Queryer interface { - URLQuery() url.Values -} - -type QuerySettings struct { - NestedFormat NestedQueryFormat - ArrayFormat ArrayQueryFormat -} - -type NestedQueryFormat int - -const ( - NestedQueryFormatBrackets NestedQueryFormat = iota - NestedQueryFormatDots -) - -type ArrayQueryFormat int - -const ( - ArrayQueryFormatComma ArrayQueryFormat = iota - ArrayQueryFormatRepeat - ArrayQueryFormatIndices - ArrayQueryFormatBrackets -) diff --git a/packages/sdk/go/internal/apiquery/query_test.go b/packages/sdk/go/internal/apiquery/query_test.go deleted file mode 100644 index 1e740d6a9..000000000 --- a/packages/sdk/go/internal/apiquery/query_test.go +++ /dev/null @@ -1,335 +0,0 @@ -package apiquery - -import ( - "net/url" - "testing" - "time" -) - -func P[T any](v T) *T { return &v } - -type Primitives struct { - A bool `query:"a"` - B int `query:"b"` - C uint `query:"c"` - D float64 `query:"d"` - E float32 `query:"e"` - F []int `query:"f"` -} - -type PrimitivePointers struct { - A *bool `query:"a"` - B *int `query:"b"` - C *uint `query:"c"` - D *float64 `query:"d"` - E *float32 `query:"e"` - F *[]int `query:"f"` -} - -type Slices struct { - Slice []Primitives `query:"slices"` - Mixed []interface{} `query:"mixed"` -} - -type DateTime struct { - Date time.Time `query:"date" format:"date"` - DateTime time.Time `query:"date-time" format:"date-time"` -} - -type AdditionalProperties struct { - A bool `query:"a"` - Extras map[string]interface{} `query:"-,inline"` -} - -type Recursive struct { - Name string `query:"name"` - Child *Recursive `query:"child"` -} - -type UnknownStruct struct { - Unknown interface{} `query:"unknown"` -} - -type UnionStruct struct { - Union Union `query:"union" format:"date"` -} - -type Union interface { - union() -} - -type UnionInteger int64 - -func (UnionInteger) union() {} - -type UnionString string - -func (UnionString) union() {} - -type UnionStructA struct { - Type string `query:"type"` - A string `query:"a"` - B string `query:"b"` -} - -func (UnionStructA) union() {} - -type UnionStructB struct { - Type string `query:"type"` - A string `query:"a"` -} - -func (UnionStructB) union() {} - -type UnionTime time.Time - -func (UnionTime) union() {} - -type DeeplyNested struct { - A DeeplyNested1 `query:"a"` -} - -type DeeplyNested1 struct { - B DeeplyNested2 `query:"b"` -} - -type DeeplyNested2 struct { - C DeeplyNested3 `query:"c"` -} - -type DeeplyNested3 struct { - D *string `query:"d"` -} - -var tests = map[string]struct { - enc string - val interface{} - settings QuerySettings -}{ - "primitives": { - "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4", - Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - QuerySettings{}, - }, - - "slices_brackets": { - `mixed[]=1&mixed[]=2.3&mixed[]=hello&slices[][a]=false&slices[][a]=false&slices[][b]=237628372683&slices[][b]=237628372683&slices[][c]=654&slices[][c]=654&slices[][d]=9999.43&slices[][d]=9999.43&slices[][e]=43.7599983215332&slices[][e]=43.7599983215332&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4`, - Slices{ - Slice: []Primitives{ - {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - }, - Mixed: []interface{}{1, 2.3, "hello"}, - }, - QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, - }, - - "slices_comma": { - `mixed=1,2.3,hello`, - Slices{ - Mixed: []interface{}{1, 2.3, "hello"}, - }, - QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - }, - - "slices_repeat": { - `mixed=1&mixed=2.3&mixed=hello&slices[a]=false&slices[a]=false&slices[b]=237628372683&slices[b]=237628372683&slices[c]=654&slices[c]=654&slices[d]=9999.43&slices[d]=9999.43&slices[e]=43.7599983215332&slices[e]=43.7599983215332&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4`, - Slices{ - Slice: []Primitives{ - {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, - }, - Mixed: []interface{}{1, 2.3, "hello"}, - }, - QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, - }, - - "primitive_pointer_struct": { - "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4,5", - 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}, - }, - QuerySettings{}, - }, - - "datetime_struct": { - `date=2006-01-02&date-time=2006-01-02T15:04:05Z`, - 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), - }, - QuerySettings{}, - }, - - "additional_properties": { - `a=true&bar=value&foo=true`, - AdditionalProperties{ - A: true, - Extras: map[string]interface{}{ - "bar": "value", - "foo": true, - }, - }, - QuerySettings{}, - }, - - "recursive_struct_brackets": { - `child[name]=Alex&name=Robert`, - Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, - QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - }, - - "recursive_struct_dots": { - `child.name=Alex&name=Robert`, - Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, - QuerySettings{NestedFormat: NestedQueryFormatDots}, - }, - - "unknown_struct_number": { - `unknown=12`, - UnknownStruct{ - Unknown: 12., - }, - QuerySettings{}, - }, - - "unknown_struct_map_brackets": { - `unknown[foo]=bar`, - UnknownStruct{ - Unknown: map[string]interface{}{ - "foo": "bar", - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - }, - - "unknown_struct_map_dots": { - `unknown.foo=bar`, - UnknownStruct{ - Unknown: map[string]interface{}{ - "foo": "bar", - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatDots}, - }, - - "union_string": { - `union=hello`, - UnionStruct{ - Union: UnionString("hello"), - }, - QuerySettings{}, - }, - - "union_integer": { - `union=12`, - UnionStruct{ - Union: UnionInteger(12), - }, - QuerySettings{}, - }, - - "union_struct_discriminated_a": { - `union[a]=foo&union[b]=bar&union[type]=typeA`, - UnionStruct{ - Union: UnionStructA{ - Type: "typeA", - A: "foo", - B: "bar", - }, - }, - QuerySettings{}, - }, - - "union_struct_discriminated_b": { - `union[a]=foo&union[type]=typeB`, - UnionStruct{ - Union: UnionStructB{ - Type: "typeB", - A: "foo", - }, - }, - QuerySettings{}, - }, - - "union_struct_time": { - `union=2010-05-23`, - UnionStruct{ - Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), - }, - QuerySettings{}, - }, - - "deeply_nested_brackets": { - `a[b][c][d]=hello`, - DeeplyNested{ - A: DeeplyNested1{ - B: DeeplyNested2{ - C: DeeplyNested3{ - D: P("hello"), - }, - }, - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - }, - - "deeply_nested_dots": { - `a.b.c.d=hello`, - DeeplyNested{ - A: DeeplyNested1{ - B: DeeplyNested2{ - C: DeeplyNested3{ - D: P("hello"), - }, - }, - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatDots}, - }, - - "deeply_nested_brackets_empty": { - ``, - DeeplyNested{ - A: DeeplyNested1{ - B: DeeplyNested2{ - C: DeeplyNested3{ - D: nil, - }, - }, - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - }, - - "deeply_nested_dots_empty": { - ``, - DeeplyNested{ - A: DeeplyNested1{ - B: DeeplyNested2{ - C: DeeplyNested3{ - D: nil, - }, - }, - }, - }, - QuerySettings{NestedFormat: NestedQueryFormatDots}, - }, -} - -func TestEncode(t *testing.T) { - for name, test := range tests { - t.Run(name, func(t *testing.T) { - values := MarshalWithSettings(test.val, test.settings) - str, _ := url.QueryUnescape(values.Encode()) - if str != test.enc { - t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.enc, str) - } - }) - } -} diff --git a/packages/sdk/go/internal/apiquery/tag.go b/packages/sdk/go/internal/apiquery/tag.go deleted file mode 100644 index 7ccd739c2..000000000 --- a/packages/sdk/go/internal/apiquery/tag.go +++ /dev/null @@ -1,41 +0,0 @@ -package apiquery - -import ( - "reflect" - "strings" -) - -const queryStructTag = "query" -const formatStructTag = "format" - -type parsedStructTag struct { - name string - omitempty bool - inline bool -} - -func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { - raw, ok := field.Tag.Lookup(queryStructTag) - 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 "omitempty": - tag.omitempty = true - case "inline": - tag.inline = true - } - } - return -} - -func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { - format, ok = field.Tag.Lookup(formatStructTag) - return -} diff --git a/packages/sdk/go/internal/param/field.go b/packages/sdk/go/internal/param/field.go deleted file mode 100644 index 4d0fd9c6f..000000000 --- a/packages/sdk/go/internal/param/field.go +++ /dev/null @@ -1,29 +0,0 @@ -package param - -import ( - "fmt" -) - -type FieldLike interface{ field() } - -// Field is a wrapper used for all values sent to the API, -// to distinguish zero values from null or omitted fields. -// -// It also allows sending arbitrary deserializable values. -// -// To instantiate a Field, use the helpers exported from -// the package root: `F()`, `Null()`, `Raw()`, etc. -type Field[T any] struct { - FieldLike - Value T - Null bool - Present bool - Raw any -} - -func (f Field[T]) String() string { - if s, ok := any(f.Value).(fmt.Stringer); ok { - return s.String() - } - return fmt.Sprintf("%v", f.Value) -} diff --git a/packages/sdk/go/internal/requestconfig/requestconfig.go b/packages/sdk/go/internal/requestconfig/requestconfig.go deleted file mode 100644 index 95111f757..000000000 --- a/packages/sdk/go/internal/requestconfig/requestconfig.go +++ /dev/null @@ -1,636 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package requestconfig - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "math" - "math/rand" - "mime" - "net/http" - "net/url" - "runtime" - "strconv" - "strings" - "time" - - "github.com/sst/opencode-sdk-go/internal" - "github.com/sst/opencode-sdk-go/internal/apierror" - "github.com/sst/opencode-sdk-go/internal/apiform" - "github.com/sst/opencode-sdk-go/internal/apiquery" - "github.com/sst/opencode-sdk-go/internal/param" -) - -func getDefaultHeaders() map[string]string { - return map[string]string{ - "User-Agent": fmt.Sprintf("Opencode/Go %s", internal.PackageVersion), - } -} - -func getNormalizedOS() string { - switch runtime.GOOS { - case "ios": - return "iOS" - case "android": - return "Android" - case "darwin": - return "MacOS" - case "window": - return "Windows" - case "freebsd": - return "FreeBSD" - case "openbsd": - return "OpenBSD" - case "linux": - return "Linux" - default: - return fmt.Sprintf("Other:%s", runtime.GOOS) - } -} - -func getNormalizedArchitecture() string { - switch runtime.GOARCH { - case "386": - return "x32" - case "amd64": - return "x64" - case "arm": - return "arm" - case "arm64": - return "arm64" - default: - return fmt.Sprintf("other:%s", runtime.GOARCH) - } -} - -func getPlatformProperties() map[string]string { - return map[string]string{ - "X-Stainless-Lang": "go", - "X-Stainless-Package-Version": internal.PackageVersion, - "X-Stainless-OS": getNormalizedOS(), - "X-Stainless-Arch": getNormalizedArchitecture(), - "X-Stainless-Runtime": "go", - "X-Stainless-Runtime-Version": runtime.Version(), - } -} - -type RequestOption interface { - Apply(*RequestConfig) error -} - -type RequestOptionFunc func(*RequestConfig) error -type PreRequestOptionFunc func(*RequestConfig) error - -func (s RequestOptionFunc) Apply(r *RequestConfig) error { return s(r) } -func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) } - -func NewRequestConfig(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) (*RequestConfig, error) { - var reader io.Reader - - contentType := "application/json" - hasSerializationFunc := false - - if body, ok := body.(json.Marshaler); ok { - content, err := body.MarshalJSON() - if err != nil { - return nil, err - } - reader = bytes.NewBuffer(content) - hasSerializationFunc = true - } - if body, ok := body.(apiform.Marshaler); ok { - var ( - content []byte - err error - ) - content, contentType, err = body.MarshalMultipart() - if err != nil { - return nil, err - } - reader = bytes.NewBuffer(content) - hasSerializationFunc = true - } - if body, ok := body.(apiquery.Queryer); ok { - hasSerializationFunc = true - params := body.URLQuery().Encode() - if params != "" { - u = u + "?" + params - } - } - if body, ok := body.([]byte); ok { - reader = bytes.NewBuffer(body) - hasSerializationFunc = true - } - if body, ok := body.(io.Reader); ok { - reader = body - hasSerializationFunc = true - } - - // Fallback to json serialization if none of the serialization functions that we expect - // to see is present. - if body != nil && !hasSerializationFunc { - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(true) - if err := enc.Encode(body); err != nil { - return nil, err - } - reader = buf - } - - req, err := http.NewRequestWithContext(ctx, method, u, nil) - if err != nil { - return nil, err - } - if reader != nil { - req.Header.Set("Content-Type", contentType) - } - - req.Header.Set("Accept", "application/json") - req.Header.Set("X-Stainless-Retry-Count", "0") - req.Header.Set("X-Stainless-Timeout", "0") - for k, v := range getDefaultHeaders() { - req.Header.Add(k, v) - } - - for k, v := range getPlatformProperties() { - req.Header.Add(k, v) - } - cfg := RequestConfig{ - MaxRetries: 2, - Context: ctx, - Request: req, - HTTPClient: http.DefaultClient, - Body: reader, - } - cfg.ResponseBodyInto = dst - err = cfg.Apply(opts...) - if err != nil { - return nil, err - } - - // This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only - // apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified - // by the user and we should respect that. - if req.Header.Get("X-Stainless-Timeout") == "0" { - if cfg.RequestTimeout == time.Duration(0) { - req.Header.Del("X-Stainless-Timeout") - } else { - req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds()))) - } - } - - return &cfg, nil -} - -func UseDefaultParam[T any](dst *param.Field[T], src *T) { - if !dst.Present && src != nil { - dst.Value = *src - dst.Present = true - } -} - -// This interface is primarily used to describe an [*http.Client], but also -// supports custom HTTP implementations. -type HTTPDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// RequestConfig represents all the state related to one request. -// -// Editing the variables inside RequestConfig directly is unstable api. Prefer -// composing the RequestOption instead if possible. -type RequestConfig struct { - MaxRetries int - RequestTimeout time.Duration - Context context.Context - Request *http.Request - BaseURL *url.URL - // DefaultBaseURL will be used if BaseURL is not explicitly overridden using - // WithBaseURL. - DefaultBaseURL *url.URL - CustomHTTPDoer HTTPDoer - HTTPClient *http.Client - Middlewares []middleware - // If ResponseBodyInto not nil, then we will attempt to deserialize into - // ResponseBodyInto. If Destination is a []byte, then it will return the body as - // is. - ResponseBodyInto interface{} - // ResponseInto copies the \*http.Response of the corresponding request into the - // given address - ResponseInto **http.Response - Body io.Reader -} - -// middleware is exactly the same type as the Middleware type found in the [option] package, -// but it is redeclared here for circular dependency issues. -type middleware = func(*http.Request, middlewareNext) (*http.Response, error) - -// middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package, -// but it is redeclared here for circular dependency issues. -type middlewareNext = func(*http.Request) (*http.Response, error) - -func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext { - return func(req *http.Request) (res *http.Response, err error) { - return middleware(req, next) - } -} - -func shouldRetry(req *http.Request, res *http.Response) bool { - // If there is no way to recover the Body, then we shouldn't retry. - if req.Body != nil && req.GetBody == nil { - return false - } - - // If there is no response, that indicates that there is a connection error - // so we retry the request. - if res == nil { - return true - } - - // If the header explicitly wants a retry behavior, respect that over the - // http status code. - if res.Header.Get("x-should-retry") == "true" { - return true - } - if res.Header.Get("x-should-retry") == "false" { - return false - } - - return res.StatusCode == http.StatusRequestTimeout || - res.StatusCode == http.StatusConflict || - res.StatusCode == http.StatusTooManyRequests || - res.StatusCode >= http.StatusInternalServerError -} - -func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) { - if resp == nil { - return 0, false - } - - type retryData struct { - header string - units time.Duration - - // custom is used when the regular algorithm failed and is optional. - // the returned duration is used verbatim (units is not applied). - custom func(string) (time.Duration, bool) - } - - nop := func(string) (time.Duration, bool) { return 0, false } - - // the headers are listed in order of preference - retries := []retryData{ - { - header: "Retry-After-Ms", - units: time.Millisecond, - custom: nop, - }, - { - header: "Retry-After", - units: time.Second, - - // retry-after values are expressed in either number of - // seconds or an HTTP-date indicating when to try again - custom: func(ra string) (time.Duration, bool) { - t, err := time.Parse(time.RFC1123, ra) - if err != nil { - return 0, false - } - return time.Until(t), true - }, - }, - } - - for _, retry := range retries { - v := resp.Header.Get(retry.header) - if v == "" { - continue - } - if retryAfter, err := strconv.ParseFloat(v, 64); err == nil { - return time.Duration(retryAfter * float64(retry.units)), true - } - if d, ok := retry.custom(v); ok { - return d, true - } - } - - return 0, false -} - -// isBeforeContextDeadline reports whether the non-zero Time t is -// before ctx's deadline. If ctx does not have a deadline, it -// always reports true (the deadline is considered infinite). -func isBeforeContextDeadline(t time.Time, ctx context.Context) bool { - d, ok := ctx.Deadline() - if !ok { - return true - } - return t.Before(d) -} - -// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func -// to handle timeouts etc. It wraps an existing io.ReadCloser. -type bodyWithTimeout struct { - stop func() // stops the time.Timer waiting to cancel the request - rc io.ReadCloser -} - -func (b *bodyWithTimeout) Read(p []byte) (n int, err error) { - n, err = b.rc.Read(p) - if err == nil { - return n, nil - } - if err == io.EOF { - return n, err - } - return n, err -} - -func (b *bodyWithTimeout) Close() error { - err := b.rc.Close() - b.stop() - return err -} - -func retryDelay(res *http.Response, retryCount int) time.Duration { - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says. - - if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute { - return retryAfterDelay - } - - maxDelay := 8 * time.Second - delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount))) - if delay > maxDelay { - delay = maxDelay - } - - jitter := rand.Int63n(int64(delay / 4)) - delay -= time.Duration(jitter) - return delay -} - -func (cfg *RequestConfig) Execute() (err error) { - if cfg.BaseURL == nil { - if cfg.DefaultBaseURL != nil { - cfg.BaseURL = cfg.DefaultBaseURL - } else { - return fmt.Errorf("requestconfig: base url is not set") - } - } - - cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/")) - if err != nil { - return err - } - - if cfg.Body != nil && cfg.Request.Body == nil { - switch body := cfg.Body.(type) { - case *bytes.Buffer: - b := body.Bytes() - cfg.Request.ContentLength = int64(body.Len()) - cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil } - cfg.Request.Body, _ = cfg.Request.GetBody() - case *bytes.Reader: - cfg.Request.ContentLength = int64(body.Len()) - cfg.Request.GetBody = func() (io.ReadCloser, error) { - _, err := body.Seek(0, 0) - return io.NopCloser(body), err - } - cfg.Request.Body, _ = cfg.Request.GetBody() - default: - if rc, ok := body.(io.ReadCloser); ok { - cfg.Request.Body = rc - } else { - cfg.Request.Body = io.NopCloser(body) - } - } - } - - handler := cfg.HTTPClient.Do - if cfg.CustomHTTPDoer != nil { - handler = cfg.CustomHTTPDoer.Do - } - for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 { - handler = applyMiddleware(cfg.Middlewares[i], handler) - } - - // Don't send the current retry count in the headers if the caller modified the header defaults. - shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0" - - var res *http.Response - var cancel context.CancelFunc - for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 { - ctx := cfg.Request.Context() - if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) { - ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout) - defer func() { - // The cancel function is nil if it was handed off to be handled in a different scope. - if cancel != nil { - cancel() - } - }() - } - - req := cfg.Request.Clone(ctx) - if shouldSendRetryCount { - req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount)) - } - - res, err = handler(req) - if ctx != nil && ctx.Err() != nil { - return ctx.Err() - } - if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries { - break - } - - // Prepare next request and wait for the retry delay - if cfg.Request.GetBody != nil { - cfg.Request.Body, err = cfg.Request.GetBody() - if err != nil { - return err - } - } - - // Can't actually refresh the body, so we don't attempt to retry here - if cfg.Request.GetBody == nil && cfg.Request.Body != nil { - break - } - - // Close the response body before retrying to prevent connection leaks - if res != nil && res.Body != nil { - res.Body.Close() - } - - time.Sleep(retryDelay(res, retryCount)) - } - - // Save *http.Response if it is requested to, even if there was an error making the request. This is - // useful in cases where you might want to debug by inspecting the response. Note that if err != nil, - // the response should be generally be empty, but there are edge cases. - if cfg.ResponseInto != nil { - *cfg.ResponseInto = res - } - if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok { - *responseBodyInto = res - } - - // If there was a connection error in the final request or any other transport error, - // return that early without trying to coerce into an APIError. - if err != nil { - return err - } - - if res.StatusCode >= 400 { - contents, err := io.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return err - } - - // If there is an APIError, re-populate the response body so that debugging - // utilities can conveniently dump the response without issue. - res.Body = io.NopCloser(bytes.NewBuffer(contents)) - - // Load the contents into the error format if it is provided. - aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode} - err = aerr.UnmarshalJSON(contents) - if err != nil { - return err - } - return &aerr - } - - _, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response) - if cfg.ResponseBodyInto == nil || intoCustomResponseBody { - // We aren't reading the response body in this scope, but whoever is will need the - // cancel func from the context to observe request timeouts. - // Put the cancel function in the response body so it can be handled elsewhere. - if cancel != nil { - res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel} - cancel = nil - } - return nil - } - - contents, err := io.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return fmt.Errorf("error reading response body: %w", err) - } - - // If we are not json, return plaintext - contentType := res.Header.Get("content-type") - mediaType, _, _ := mime.ParseMediaType(contentType) - isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json") - if !isJSON { - switch dst := cfg.ResponseBodyInto.(type) { - case *string: - *dst = string(contents) - case **string: - tmp := string(contents) - *dst = &tmp - case *[]byte: - *dst = contents - default: - return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType) - } - return nil - } - - switch dst := cfg.ResponseBodyInto.(type) { - // If the response happens to be a byte array, deserialize the body as-is. - case *[]byte: - *dst = contents - default: - err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto) - if err != nil { - return fmt.Errorf("error parsing response json: %w", err) - } - } - - return nil -} - -func ExecuteNewRequest(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) error { - cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...) - if err != nil { - return err - } - return cfg.Execute() -} - -func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig { - if cfg == nil { - return nil - } - req := cfg.Request.Clone(ctx) - var err error - if req.Body != nil { - req.Body, err = req.GetBody() - } - if err != nil { - return nil - } - new := &RequestConfig{ - MaxRetries: cfg.MaxRetries, - RequestTimeout: cfg.RequestTimeout, - Context: ctx, - Request: req, - BaseURL: cfg.BaseURL, - HTTPClient: cfg.HTTPClient, - Middlewares: cfg.Middlewares, - } - - return new -} - -func (cfg *RequestConfig) Apply(opts ...RequestOption) error { - for _, opt := range opts { - err := opt.Apply(cfg) - if err != nil { - return err - } - } - return nil -} - -// PreRequestOptions is used to collect all the options which need to be known before -// a call to [RequestConfig.ExecuteNewRequest], such as path parameters -// or global defaults. -// PreRequestOptions will return a [RequestConfig] with the options applied. -// -// Only request option functions of type [PreRequestOptionFunc] are applied. -func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) { - cfg := RequestConfig{} - for _, opt := range opts { - if opt, ok := opt.(PreRequestOptionFunc); ok { - err := opt.Apply(&cfg) - if err != nil { - return cfg, err - } - } - } - return cfg, nil -} - -// WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL. -// This is always overridden by setting a base URL with WithBaseURL. -// WithBaseURL should be used instead of WithDefaultBaseURL except in internal code. -func WithDefaultBaseURL(baseURL string) RequestOption { - u, err := url.Parse(baseURL) - return RequestOptionFunc(func(r *RequestConfig) error { - if err != nil { - return err - } - r.DefaultBaseURL = u - return nil - }) -} diff --git a/packages/sdk/go/internal/testutil/testutil.go b/packages/sdk/go/internal/testutil/testutil.go deleted file mode 100644 index 826d266f2..000000000 --- a/packages/sdk/go/internal/testutil/testutil.go +++ /dev/null @@ -1,27 +0,0 @@ -package testutil - -import ( - "net/http" - "os" - "strconv" - "testing" -) - -func CheckTestServer(t *testing.T, url string) bool { - if _, err := http.Get(url); err != nil { - const SKIP_MOCK_TESTS = "SKIP_MOCK_TESTS" - if str, ok := os.LookupEnv(SKIP_MOCK_TESTS); ok { - skip, err := strconv.ParseBool(str) - if err != nil { - t.Fatalf("strconv.ParseBool(os.LookupEnv(%s)) failed: %s", SKIP_MOCK_TESTS, err) - } - if skip { - t.Skip("The test will not run without a mock Prism server running against your OpenAPI spec") - return false - } - t.Errorf("The test will not run without a mock Prism server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) - return false - } - } - return true -} diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go deleted file mode 100644 index 8dc40e747..000000000 --- a/packages/sdk/go/internal/version.go +++ /dev/null @@ -1,5 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package internal - -const PackageVersion = "0.18.0" // x-release-please-version |
