diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c7159c1..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.2" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index f2700c5..b4e52eb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/miru-ml%2Fmiru-server-e8b887c478291aecbbf06e532903242a5d5ff682a2a6814921bb770c97a1753d.yml openapi_spec_hash: b15141c2e0e81b8029c620d4aef4188c -config_hash: 3de34a47f7bb67e784afed4170b209b1 +config_hash: 95b6fa4e87744247f5bdbf841c16ce0a diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e54a2..2b74387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## 0.1.0 (2025-12-19) + +Full Changelog: [v0.0.2...v0.1.0](https://github.com/miruml/go-server-sdk/compare/v0.0.2...v0.1.0) + +### ⚠ BREAKING CHANGES + +* **api:** change route to be /beta instead of /v1 + +### Features + +* **encoder:** support bracket encoding form-data object members ([42cd12f](https://github.com/miruml/go-server-sdk/commit/42cd12f63596c6f1bc5aa86d621f6929716b6ac8)) + + +### Bug Fixes + +* **mcp:** correct code tool API endpoint ([c2128e7](https://github.com/miruml/go-server-sdk/commit/c2128e744bbd9473c8a1b6bef9cee4c3e1984558)) +* rename param to avoid collision ([38be6e4](https://github.com/miruml/go-server-sdk/commit/38be6e4729020e74ec2ad2c6f3b7506c3e6db4b2)) +* skip usage tests that don't work with Prism ([3a4ce7e](https://github.com/miruml/go-server-sdk/commit/3a4ce7e207e17b8730279da7d1de116ed8005ff3)) + + +### Chores + +* add float64 to valid types for RegisterFieldValidator ([dbeb503](https://github.com/miruml/go-server-sdk/commit/dbeb503b9299414959b2ce494ab88b3d552a4615)) +* bump gjson version ([561ebe2](https://github.com/miruml/go-server-sdk/commit/561ebe292506a695c7c6722a558efc4a57a4e7ca)) +* elide duplicate aliases ([362f943](https://github.com/miruml/go-server-sdk/commit/362f943009b5ee378163d4db6473150fc301b82b)) +* fix empty interfaces ([74e4e50](https://github.com/miruml/go-server-sdk/commit/74e4e50593ba2d1e56e6671d0d69365e766f62a6)) +* **internal:** codegen related update ([60b4b3c](https://github.com/miruml/go-server-sdk/commit/60b4b3c4cec89a4f2ed53493ac7b2217b26fb7e0)) +* **internal:** grammar fix (it's -> its) ([fa1fb81](https://github.com/miruml/go-server-sdk/commit/fa1fb81de9f7c11a310c6f56b1cfc14e0994dab9)) + + +### Refactors + +* **api:** change route to be /beta instead of /v1 ([be825a4](https://github.com/miruml/go-server-sdk/commit/be825a42443bae212e7280f89fdf6c77e2e62a1b)) + ## 0.0.2 (2025-10-21) Full Changelog: [v0.0.1...v0.0.2](https://github.com/miruml/go-server-sdk/compare/v0.0.1...v0.0.2) diff --git a/README.md b/README.md index f94db25..2c8efbd 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or to pin the version: ```sh -go get -u 'github.com/miruml/go-server-sdk@v0.0.2' +go get -u 'github.com/miruml/go-server-sdk@v0.1.0' ``` @@ -130,7 +130,7 @@ custom := param.Override[miruserver.FooParams](12) ### Request unions -Unions are represented as a struct with fields prefixed by "Of" for each of it's variants, +Unions are represented as a struct with fields prefixed by "Of" for each of its variants, only one field can be non-zero. The non-zero field will be serialized. Sub-properties of the union can be accessed via methods on the union struct. diff --git a/deployment_test.go b/deployment_test.go index b69b0d4..9a94f55 100644 --- a/deployment_test.go +++ b/deployment_test.go @@ -31,7 +31,7 @@ func TestDeploymentNewWithOptionalParams(t *testing.T) { DeviceID: "dvc_123", NewConfigInstances: []miruserver.DeploymentNewParamsNewConfigInstance{{ ConfigSchemaID: "cfg_sch_123", - Content: map[string]interface{}{ + Content: map[string]any{ "direction": "forward", "speed": 100, "duration": 10, diff --git a/go.mod b/go.mod index d8689cb..1e27ccc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/miruml/go-server-sdk go 1.22 require ( - github.com/tidwall/gjson v1.14.4 + github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) diff --git a/go.sum b/go.sum index a70a5e0..32ba293 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index e93aa87..32ca1f0 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -60,6 +60,7 @@ type encoderField struct { type encoderEntry struct { reflect.Type dateFormat string + arrayFmt string root bool } @@ -77,6 +78,7 @@ func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ Type: t, dateFormat: e.dateFormat, + arrayFmt: e.arrayFmt, root: e.root, } @@ -178,34 +180,9 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { } } -func arrayKeyEncoder(arrayFmt string) func(string, int) string { - var keyFn func(string, int) string - switch arrayFmt { - case "comma", "repeat": - keyFn = func(k string, _ int) string { return k } - case "brackets": - keyFn = func(key string, _ int) string { return key + "[]" } - case "indices:dots": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "." + strconv.Itoa(i) - } - case "indices:brackets": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "[" + strconv.Itoa(i) + "]" - } - } - return keyFn -} - func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { itemEncoder := e.typeEncoder(t.Elem()) - keyFn := arrayKeyEncoder(e.arrayFmt) + keyFn := e.arrayKeyEncoder() return func(key string, v reflect.Value, writer *multipart.Writer) error { if keyFn == nil { return fmt.Errorf("apiform: unsupported array format") @@ -303,13 +280,10 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { }) return func(key string, value reflect.Value, writer *multipart.Writer) error { - if key != "" { - key = key + "." - } - + keyFn := e.objKeyEncoder(key) for _, ef := range encoderFields { field := value.FieldByIndex(ef.idx) - err := ef.fn(key+ef.tag.name, field, writer) + err := ef.fn(keyFn(ef.tag.name), field, writer) if err != nil { return err } @@ -405,6 +379,43 @@ func (e *encoder) newReaderTypeEncoder() encoderFunc { } } +func (e encoder) arrayKeyEncoder() func(string, int) string { + var keyFn func(string, int) string + switch e.arrayFmt { + case "comma", "repeat": + keyFn = func(k string, _ int) string { return k } + case "brackets": + keyFn = func(key string, _ int) string { return key + "[]" } + case "indices:dots": + keyFn = func(k string, i int) string { + if k == "" { + return strconv.Itoa(i) + } + return k + "." + strconv.Itoa(i) + } + case "indices:brackets": + keyFn = func(k string, i int) string { + if k == "" { + return strconv.Itoa(i) + } + return k + "[" + strconv.Itoa(i) + "]" + } + } + return keyFn +} + +func (e encoder) objKeyEncoder(parent string) func(string) string { + if parent == "" { + return func(child string) string { return child } + } + switch e.arrayFmt { + case "brackets": + return func(child string) string { return parent + "[" + child + "]" } + default: + return func(child string) string { return parent + "." + child } + } +} + // 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 { @@ -413,10 +424,6 @@ func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipar value reflect.Value } - if key != "" { - key = key + "." - } - pairs := []mapPair{} iter := v.MapRange() @@ -434,8 +441,9 @@ func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipar }) elementEncoder := e.typeEncoder(v.Type().Elem()) + keyFn := e.objKeyEncoder(key) for _, p := range pairs { - err := elementEncoder(key+string(p.key), p.value, writer) + err := elementEncoder(keyFn(p.key), p.value, writer) if err != nil { return err } diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index e330c40..c8eef9c 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -123,6 +123,18 @@ type StructUnion struct { param.APIUnion } +type MultipartMarshalerParent struct { + Middle MultipartMarshalerMiddleNext `form:"middle"` +} + +type MultipartMarshalerMiddleNext struct { + MiddleNext MultipartMarshalerMiddle `form:"middleNext"` +} + +type MultipartMarshalerMiddle struct { + Child int `form:"child"` +} + var tests = map[string]struct { buf string val any @@ -366,6 +378,19 @@ true }, }, }, + "recursive_struct,brackets": { + `--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"}}, + }, "recursive_struct": { `--xxx @@ -529,6 +554,30 @@ Content-Disposition: form-data; name="union" Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), }, }, + "deeply-nested-struct,brackets": { + `--xxx +Content-Disposition: form-data; name="middle[middleNext][child]" + +10 +--xxx-- +`, + MultipartMarshalerParent{ + Middle: MultipartMarshalerMiddleNext{ + MiddleNext: MultipartMarshalerMiddle{ + Child: 10, + }, + }, + }, + }, + "deeply-nested-map,brackets": { + `--xxx +Content-Disposition: form-data; name="middle[middleNext][child]" + +10 +--xxx-- +`, + map[string]any{"middle": map[string]any{"middleNext": map[string]any{"child": 10}}}, + }, } func TestEncode(t *testing.T) { @@ -553,7 +602,7 @@ func TestEncode(t *testing.T) { } 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)) + t.Errorf("expected %+#v to serialize to '%s' but got '%s' (with format %s)", test.val, test.buf, string(raw), arrayFmt) } }) } diff --git a/internal/apijson/enum.go b/internal/apijson/enum.go index 18b218a..5bef11c 100644 --- a/internal/apijson/enum.go +++ b/internal/apijson/enum.go @@ -29,7 +29,7 @@ type validatorFunc func(reflect.Value) exactness var validators sync.Map var validationRegistry = map[reflect.Type][]validationEntry{} -func RegisterFieldValidator[T any, V string | bool | int](fieldName string, values ...V) { +func RegisterFieldValidator[T any, V string | bool | int | float64](fieldName string, values ...V) { var t T parentType := reflect.TypeOf(t) diff --git a/internal/version.go b/internal/version.go index 9b92696..02eac73 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.0.2" // x-release-please-version +const PackageVersion = "0.1.0" // x-release-please-version diff --git a/option/requestoption.go b/option/requestoption.go index 31d6596..3964db7 100644 --- a/option/requestoption.go +++ b/option/requestoption.go @@ -263,28 +263,28 @@ func WithRequestTimeout(dur time.Duration) RequestOption { // environment to be the "prod" environment. An environment specifies which base URL // to use by default. func WithEnvironmentProd() RequestOption { - return requestconfig.WithDefaultBaseURL("https://configs.api.miruml.com/v1/") + return requestconfig.WithDefaultBaseURL("https://configs.api.miruml.com/beta/") } // WithEnvironmentUat returns a RequestOption that sets the current // environment to be the "uat" environment. An environment specifies which base URL // to use by default. func WithEnvironmentUat() RequestOption { - return requestconfig.WithDefaultBaseURL("https://uat.api.miruml.com/v1/") + return requestconfig.WithDefaultBaseURL("https://uat.api.miruml.com/beta/") } // WithEnvironmentStaging returns a RequestOption that sets the current // environment to be the "staging" environment. An environment specifies which base URL // to use by default. func WithEnvironmentStaging() RequestOption { - return requestconfig.WithDefaultBaseURL("https://configs.dev.api.miruml.com/v1/") + return requestconfig.WithDefaultBaseURL("https://configs.dev.api.miruml.com/beta/") } // WithEnvironmentLocal returns a RequestOption that sets the current // environment to be the "local" environment. An environment specifies which base URL // to use by default. func WithEnvironmentLocal() RequestOption { - return requestconfig.WithDefaultBaseURL("http://localhost:8080/v1/") + return requestconfig.WithDefaultBaseURL("http://localhost:8080/beta/") } // WithAPIKey returns a RequestOption that sets the client setting "api_key". diff --git a/packages/respjson/respjson.go b/packages/respjson/respjson.go index cc0088c..9e61c5c 100644 --- a/packages/respjson/respjson.go +++ b/packages/respjson/respjson.go @@ -5,7 +5,7 @@ package respjson // Use [Field.Valid] to check if an optional value was null or omitted. // // A Field will always occur in the following structure, where it -// mirrors the original field in it's parent struct: +// mirrors the original field in its parent struct: // // type ExampleObject struct { // Foo bool `json:"foo"` diff --git a/usage_test.go b/usage_test.go index 87a9f4d..5ec2beb 100644 --- a/usage_test.go +++ b/usage_test.go @@ -24,6 +24,7 @@ func TestUsage(t *testing.T) { option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) + t.Skip("Prism tests are disabled") configInstance, err := client.ConfigInstances.Get( context.TODO(), "cfg_inst_123",