Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.0.2"
".": "0.1.0"
}
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## 0.1.0 (2025-12-18)

Full Changelog: [v0.0.2...v0.1.0](https://github.com/logchimp/logchimp-go/compare/v0.0.2...v0.1.0)

### Features

* **encoder:** support bracket encoding form-data object members ([6cd077a](https://github.com/logchimp/logchimp-go/commit/6cd077a2d95c011c3f73a2f55e48a1dc743f2901))


### Bug Fixes

* **client:** correctly specify Accept header with */* instead of empty ([ea560f3](https://github.com/logchimp/logchimp-go/commit/ea560f391ecd07b3e6269df649c98816b9cfa039))
* **mcp:** correct code tool API endpoint ([f0f8c92](https://github.com/logchimp/logchimp-go/commit/f0f8c92a0d868da0ded4baa9bfadabd0bfc81463))
* rename param to avoid collision ([12afbfb](https://github.com/logchimp/logchimp-go/commit/12afbfbeb6a2a25524caec08631ca6fb1fa1d69d))
* Windows OS detection typo ([90e9117](https://github.com/logchimp/logchimp-go/commit/90e91178a9e8a7f613cf8548b6fdad60001d0f5d))
* Windows OS detection typo ([293a91a](https://github.com/logchimp/logchimp-go/commit/293a91a03d9396732e7fce25babfab6d531e8250))


### Chores

* bump gjson version ([f327424](https://github.com/logchimp/logchimp-go/commit/f3274249578b1cef3ded236a94a1fdbc7a5a1713))
* elide duplicate aliases ([5925e69](https://github.com/logchimp/logchimp-go/commit/5925e692cd64ac89f843c23677cf917a64a4c3f7))
* **internal:** codegen related update ([1cd3a51](https://github.com/logchimp/logchimp-go/commit/1cd3a51b1980d00dba724a77dd6660f88a37e5b5))
* **internal:** grammar fix (it's -> its) ([762cdbb](https://github.com/logchimp/logchimp-go/commit/762cdbb9fe5cee144fe130cd8fd53ee968129058))

## 0.0.2 (2025-10-10)

Full Changelog: [v0.0.1...v0.0.2](https://github.com/logchimp/logchimp-go/compare/v0.0.1...v0.0.2)
Expand Down
4 changes: 2 additions & 2 deletions 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/logchimp/logchimp-go@v0.0.2'
go get -u 'github.com/logchimp/logchimp-go@v0.1.0'
```

<!-- x-release-please-end -->
Expand Down Expand Up @@ -128,7 +128,7 @@ custom := param.Override[logchimp.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.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/logchimp/logchimp-go
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
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
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/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package internal

const PackageVersion = "0.0.2" // x-release-please-version
const PackageVersion = "0.1.0" // x-release-please-version
2 changes: 1 addition & 1 deletion packages/respjson/respjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
4 changes: 2 additions & 2 deletions post.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (r *PostService) New(ctx context.Context, body PostNewParams, opts ...optio
// Update a post
func (r *PostService) Update(ctx context.Context, opts ...option.RequestOption) (err error) {
opts = slices.Concat(r.Options, opts)
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
path := "posts"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, nil, nil, opts...)
return
Expand All @@ -61,7 +61,7 @@ func (r *PostService) List(ctx context.Context, body PostListParams, opts ...opt
// Delete a posts
func (r *PostService) Delete(ctx context.Context, body PostDeleteParams, opts ...option.RequestOption) (err error) {
opts = slices.Concat(r.Options, opts)
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
path := "posts"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, nil, opts...)
return
Expand Down
1 change: 1 addition & 0 deletions usage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestUsage(t *testing.T) {
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
t.Skip("Prism tests are disabled")
response, err := client.Auth.Login(context.TODO(), logchimp.AuthLoginParams{
Email: "[email protected]",
Password: "password",
Expand Down
Loading