Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dd70400
feat(api): api update
stainless-app[bot] Dec 6, 2025
e40a3de
chore(internal): codegen related update
stainless-app[bot] Dec 6, 2025
9e83189
chore: elide duplicate aliases
stainless-app[bot] Dec 6, 2025
2eeb64b
fix(mcp): correct code tool API endpoint
stainless-app[bot] Dec 6, 2025
f221c75
fix: rename param to avoid collision
stainless-app[bot] Dec 6, 2025
0af4350
codegen metadata
stainless-app[bot] Dec 8, 2025
59ae9e5
codegen metadata
stainless-app[bot] Dec 9, 2025
bf75f99
feat(encoder): support bracket encoding form-data object members
stainless-app[bot] Dec 12, 2025
aa26368
codegen metadata
stainless-app[bot] Dec 16, 2025
e162391
codegen metadata
stainless-app[bot] Dec 16, 2025
61d03bd
fix: skip usage tests that don't work with Prism
stainless-app[bot] Dec 18, 2025
e87a5d3
codegen metadata
stainless-app[bot] Dec 18, 2025
499e663
chore: add float64 to valid types for RegisterFieldValidator
stainless-app[bot] Dec 19, 2025
e871fac
codegen metadata
stainless-app[bot] Dec 19, 2025
356e8a0
codegen metadata
stainless-app[bot] Dec 19, 2025
cd38e66
codegen metadata
stainless-app[bot] Dec 29, 2025
44eb5d0
chore(internal): codegen related update
stainless-app[bot] Jan 6, 2026
85e8459
codegen metadata
stainless-app[bot] Jan 7, 2026
56ca25e
codegen metadata
stainless-app[bot] Jan 8, 2026
a6b5b3e
codegen metadata
stainless-app[bot] Jan 15, 2026
a36519d
codegen metadata
stainless-app[bot] Jan 15, 2026
c853455
chore(internal): update `actions/checkout` version
stainless-app[bot] Jan 17, 2026
6dd1ead
fix(docs): add missing pointer prefix to api.md return types
stainless-app[bot] Jan 17, 2026
4566412
release: 0.1.0-alpha.5
stainless-app[bot] Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup go
uses: actions/setup-go@v5
Expand All @@ -35,7 +35,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/sfc-nodes-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup go
uses: actions/setup-go@v5
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0-alpha.4"
".": "0.1.0-alpha.5"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 15
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-c9d6d56eabd56a40a29dc2639a77d22dd5394ecd3ec9aeaebb3a3977811571da.yml
openapi_spec_hash: beda3f45c48679e14d6fe8bbe7003d51
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/the-san-francisco-compute-company%2Fsfc-nodes-60bc3e1c43c0c297e5c78335fb472cf5ec6dd72f2125c4543d61e1b80cfd5f27.yml
openapi_spec_hash: 8fe4d4d71aa3413725688f88cbe3376f
config_hash: a187153315a646ecf95709ee4a223df5
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## 0.1.0-alpha.5 (2026-01-17)

Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)

### Features

* **api:** api update ([dd70400](https://github.com/sfcompute/nodes-go/commit/dd70400abff7e7332cf7a7f3dee59e9e2e4b61b5))
* **encoder:** support bracket encoding form-data object members ([bf75f99](https://github.com/sfcompute/nodes-go/commit/bf75f99a26c353742229dabb561684545d0835c7))


### Bug Fixes

* **docs:** add missing pointer prefix to api.md return types ([6dd1ead](https://github.com/sfcompute/nodes-go/commit/6dd1eadf389b5a9adf98195f624f0bba54f0b686))
* **mcp:** correct code tool API endpoint ([2eeb64b](https://github.com/sfcompute/nodes-go/commit/2eeb64be8909979b8cb3524a077cb764c85a1f21))
* rename param to avoid collision ([f221c75](https://github.com/sfcompute/nodes-go/commit/f221c7569b746c43bd46c4ab3c1615b30fc0c05c))
* skip usage tests that don't work with Prism ([61d03bd](https://github.com/sfcompute/nodes-go/commit/61d03bd3c1f7772a74bef48676020df27c371ab1))


### Chores

* add float64 to valid types for RegisterFieldValidator ([499e663](https://github.com/sfcompute/nodes-go/commit/499e663b659fa00c013ebe3db1e0622b5e2a6a51))
* elide duplicate aliases ([9e83189](https://github.com/sfcompute/nodes-go/commit/9e83189fd37d851e8441c313c738656a72760483))
* **internal:** codegen related update ([44eb5d0](https://github.com/sfcompute/nodes-go/commit/44eb5d0a3f745631aebb3b0d2986a049302131fe))
* **internal:** codegen related update ([e40a3de](https://github.com/sfcompute/nodes-go/commit/e40a3debee22efa7015e5230d14374245af79b1f))
* **internal:** update `actions/checkout` version ([c853455](https://github.com/sfcompute/nodes-go/commit/c853455bd8e0c977606bc29049cfc84b2caea449))

## 0.1.0-alpha.4 (2025-12-01)

Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sfcompute/nodes-go/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2025 SFC Nodes
Copyright 2026 SFC Nodes

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/sfcompute/[email protected].4'
go get -u 'github.com/sfcompute/[email protected].5'
```

<!-- x-release-please-end -->
Expand Down
28 changes: 14 additions & 14 deletions api.md

Large diffs are not rendered by default.

80 changes: 44 additions & 36 deletions internal/apiform/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type encoderField struct {
type encoderEntry struct {
reflect.Type
dateFormat string
arrayFmt string
root bool
}

Expand All @@ -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,
}

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand All @@ -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
}
Expand Down
51 changes: 50 additions & 1 deletion internal/apiform/form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/apijson/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion internal/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package internal

const PackageVersion = "0.1.0-alpha.4" // x-release-please-version
const PackageVersion = "0.1.0-alpha.5" // x-release-please-version
Loading