From 22ed58cca58650432ef3cfaefbe3cb80d85d60ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 04:30:24 +0000 Subject: [PATCH 1/8] chore(internal): update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c6d0501..8554aff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log codegen.log Brewfile.lock.json .idea/ From cb79b17587f210a297a451bde4d02934c0e7de67 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 03:09:19 +0000 Subject: [PATCH 2/8] chore(ci): skip lint on metadata-only changes Note that we still want to run tests, as these depend on the metadata. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa6130c..e45b092 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,8 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/cas-parser-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: |- github.repository == 'stainless-sdks/cas-parser-go' && - (github.event_name == 'push' || github.event.pull_request.head.repo.fork) + (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && + (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@v6 From 6da30504423b6f369d2a1a3e9ddb24263856db9c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 03:54:56 +0000 Subject: [PATCH 3/8] chore(internal): support default value struct tag --- internal/apiform/tag.go | 26 +++++++++++++++++++++----- internal/apijson/encoder.go | 8 ++++++++ internal/apijson/json_test.go | 17 +++++++++++++++++ internal/apijson/tag.go | 26 +++++++++++++++++++++----- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/internal/apiform/tag.go b/internal/apiform/tag.go index d9915d4..f0c9d14 100644 --- a/internal/apiform/tag.go +++ b/internal/apiform/tag.go @@ -9,13 +9,15 @@ const apiStructTag = "api" const jsonStructTag = "json" const formStructTag = "form" const formatStructTag = "format" +const defaultStructTag = "default" type parsedStructTag struct { - name string - required bool - extras bool - metadata bool - omitzero bool + name string + required bool + extras bool + metadata bool + omitzero bool + defaultValue any } func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { @@ -45,9 +47,23 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool } parseApiStructTag(field, &tag) + parseDefaultStructTag(field, &tag) return tag, ok } +func parseDefaultStructTag(field reflect.StructField, tag *parsedStructTag) { + if field.Type.Kind() != reflect.String { + // Only strings are currently supported + return + } + + raw, ok := field.Tag.Lookup(defaultStructTag) + if !ok { + return + } + tag.defaultValue = raw +} + func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { raw, ok := field.Tag.Lookup(apiStructTag) if !ok { diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index 0decb73..5db38b7 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -12,6 +12,8 @@ import ( "time" "github.com/tidwall/sjson" + + shimjson "github.com/CASParser/cas-parser-go/internal/encoding/json" ) var encoders sync.Map // map[encoderEntry]encoderFunc @@ -271,6 +273,12 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { if err != nil { return nil, err } + if ef.tag.defaultValue != nil && (!field.IsValid() || field.IsZero()) { + encoded, err = shimjson.Marshal(ef.tag.defaultValue) + if err != nil { + return nil, err + } + } if encoded == nil { continue } diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go index 19b3614..2853bf9 100644 --- a/internal/apijson/json_test.go +++ b/internal/apijson/json_test.go @@ -614,3 +614,20 @@ func TestEncode(t *testing.T) { }) } } + +type StructWithDefault struct { + Type string `json:"type" default:"foo"` +} + +func TestDefault(t *testing.T) { + value := StructWithDefault{} + expected := `{"type":"foo"}` + + raw, err := Marshal(value) + if err != nil { + t.Fatalf("serialization of %v failed with error %v", value, err) + } + if string(raw) != expected { + t.Fatalf("expected %+#v to serialize to %s but got %s", value, expected, string(raw)) + } +} diff --git a/internal/apijson/tag.go b/internal/apijson/tag.go index 17b2130..efcaf8c 100644 --- a/internal/apijson/tag.go +++ b/internal/apijson/tag.go @@ -8,13 +8,15 @@ import ( const apiStructTag = "api" const jsonStructTag = "json" const formatStructTag = "format" +const defaultStructTag = "default" type parsedStructTag struct { - name string - required bool - extras bool - metadata bool - inline bool + name string + required bool + extras bool + metadata bool + inline bool + defaultValue any } func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { @@ -42,9 +44,23 @@ func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool // the `api` struct tag is only used alongside `json` for custom behaviour parseApiStructTag(field, &tag) + parseDefaultStructTag(field, &tag) return tag, ok } +func parseDefaultStructTag(field reflect.StructField, tag *parsedStructTag) { + if field.Type.Kind() != reflect.String { + // Only strings are currently supported + return + } + + raw, ok := field.Tag.Lookup(defaultStructTag) + if !ok { + return + } + tag.defaultValue = raw +} + func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { raw, ok := field.Tag.Lookup(apiStructTag) if !ok { From 17d404d8858084b9b71419138e72de0dc7e6f65b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 03:56:52 +0000 Subject: [PATCH 4/8] chore(client): fix multipart serialisation of Default() fields --- internal/apiform/encoder.go | 8 ++++++++ internal/apiform/form_test.go | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 8810511..507c337 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -265,6 +265,14 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { } return typeEncoderFn(key, value, writer) } + } else if ptag.defaultValue != nil { + typeEncoderFn := e.typeEncoder(field.Type) + encoderFn = func(key string, value reflect.Value, writer *multipart.Writer) error { + if value.IsZero() { + return typeEncoderFn(key, reflect.ValueOf(ptag.defaultValue), writer) + } + return typeEncoderFn(key, value, writer) + } } else { encoderFn = e.typeEncoder(field.Type) } diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index 9826914..906e76a 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -123,6 +123,11 @@ type StructUnion struct { param.APIUnion } +type ConstantStruct struct { + Anchor string `form:"anchor" default:"created_at"` + Seconds int `form:"seconds"` +} + type MultipartMarshalerParent struct { Middle MultipartMarshalerMiddleNext `form:"middle"` } @@ -554,6 +559,37 @@ Content-Disposition: form-data; name="union" Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), }, }, + "constant_zero_value": { + `--xxx +Content-Disposition: form-data; name="anchor" + +created_at +--xxx +Content-Disposition: form-data; name="seconds" + +3600 +--xxx-- +`, + ConstantStruct{ + Seconds: 3600, + }, + }, + "constant_explicit_value": { + `--xxx +Content-Disposition: form-data; name="anchor" + +created_at_override +--xxx +Content-Disposition: form-data; name="seconds" + +3600 +--xxx-- +`, + ConstantStruct{ + Anchor: "created_at_override", + Seconds: 3600, + }, + }, "deeply-nested-struct,brackets": { `--xxx Content-Disposition: form-data; name="middle[middleNext][child]" From f7cfb89b74abdde929b9bf09e7f461f0a59701cb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:41:50 +0000 Subject: [PATCH 5/8] fix: prevent duplicate ? in query params --- internal/requestconfig/requestconfig.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index b64449f..6ccb460 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -121,7 +121,16 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds } params := q.Encode() if params != "" { - u = u + "?" + params + parsed, err := url.Parse(u) + if err != nil { + return nil, err + } + if parsed.RawQuery != "" { + parsed.RawQuery = parsed.RawQuery + "&" + params + u = parsed.String() + } else { + u = u + "?" + params + } } } if body, ok := body.([]byte); ok { From 7cb383af66275655755216a51b0d0af6fcd3b09e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:44:27 +0000 Subject: [PATCH 6/8] chore: remove unnecessary error check for url parsing --- internal/requestconfig/requestconfig.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index 6ccb460..d0e8a5c 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -121,10 +121,7 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds } params := q.Encode() if params != "" { - parsed, err := url.Parse(u) - if err != nil { - return nil, err - } + parsed, _ := url.Parse(u) if parsed.RawQuery != "" { parsed.RawQuery = parsed.RawQuery + "&" + params u = parsed.String() From 6c3981526f178833589ac8991f3e39ac3bffb2cf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:45:06 +0000 Subject: [PATCH 7/8] feat(internal): support comma format in multipart form encoding --- internal/apiform/encoder.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 507c337..b3c8eb2 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -183,6 +183,18 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { itemEncoder := e.typeEncoder(t.Elem()) keyFn := e.arrayKeyEncoder() + if e.arrayFmt == "comma" { + return func(key string, v reflect.Value, writer *multipart.Writer) error { + if v.Len() == 0 { + return nil + } + elements := make([]string, v.Len()) + for i := 0; i < v.Len(); i++ { + elements[i] = fmt.Sprint(v.Index(i).Interface()) + } + return writer.WriteField(key, strings.Join(elements, ",")) + } + } return func(key string, v reflect.Value, writer *multipart.Writer) error { if keyFn == nil { return fmt.Errorf("apiform: unsupported array format") From b1a476587e8bc7806df47befbe25f60de2ce8ef1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:45:36 +0000 Subject: [PATCH 8/8] release: 0.6.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6bc1697..4208b5c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.3" + ".": "0.6.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c46bf2..aeaeaaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 0.6.0 (2026-03-27) + +Full Changelog: [v0.5.3...v0.6.0](https://github.com/CASParser/cas-parser-go/compare/v0.5.3...v0.6.0) + +### Features + +* **internal:** support comma format in multipart form encoding ([6c39815](https://github.com/CASParser/cas-parser-go/commit/6c3981526f178833589ac8991f3e39ac3bffb2cf)) + + +### Bug Fixes + +* prevent duplicate ? in query params ([f7cfb89](https://github.com/CASParser/cas-parser-go/commit/f7cfb89b74abdde929b9bf09e7f461f0a59701cb)) + + +### Chores + +* **ci:** skip lint on metadata-only changes ([cb79b17](https://github.com/CASParser/cas-parser-go/commit/cb79b17587f210a297a451bde4d02934c0e7de67)) +* **client:** fix multipart serialisation of Default() fields ([17d404d](https://github.com/CASParser/cas-parser-go/commit/17d404d8858084b9b71419138e72de0dc7e6f65b)) +* **internal:** support default value struct tag ([6da3050](https://github.com/CASParser/cas-parser-go/commit/6da30504423b6f369d2a1a3e9ddb24263856db9c)) +* **internal:** update gitignore ([22ed58c](https://github.com/CASParser/cas-parser-go/commit/22ed58cca58650432ef3cfaefbe3cb80d85d60ee)) +* remove unnecessary error check for url parsing ([7cb383a](https://github.com/CASParser/cas-parser-go/commit/7cb383af66275655755216a51b0d0af6fcd3b09e)) + ## 0.5.3 (2026-03-17) Full Changelog: [v0.5.2...v0.5.3](https://github.com/CASParser/cas-parser-go/compare/v0.5.2...v0.5.3) diff --git a/README.md b/README.md index f31c9dd..449caa4 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Or to pin the version: ```sh -go get -u 'github.com/CASParser/cas-parser-go@v0.5.3' +go get -u 'github.com/CASParser/cas-parser-go@v0.6.0' ``` diff --git a/internal/version.go b/internal/version.go index a2967c7..577a4dc 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.5.3" // x-release-please-version +const PackageVersion = "0.6.0" // x-release-please-version