Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.prism.log
.stdy.log
codegen.log
Brewfile.lock.json
.idea/
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.5.3"
".": "0.6.0"
}
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Or to pin the version:
<!-- x-release-please-start-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'
```

<!-- x-release-please-end -->
Expand Down
20 changes: 20 additions & 0 deletions internal/apiform/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -265,6 +277,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)
}
Expand Down
36 changes: 36 additions & 0 deletions internal/apiform/form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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]"
Expand Down
26 changes: 21 additions & 5 deletions internal/apiform/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions internal/apijson/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
17 changes: 17 additions & 0 deletions internal/apijson/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
26 changes: 21 additions & 5 deletions internal/apijson/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion internal/requestconfig/requestconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds
}
params := q.Encode()
if params != "" {
u = u + "?" + params
parsed, _ := url.Parse(u)
if parsed.RawQuery != "" {
parsed.RawQuery = parsed.RawQuery + "&" + params
u = parsed.String()
} else {
u = u + "?" + params
}
}
}
if body, ok := body.([]byte); ok {
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.5.3" // x-release-please-version
const PackageVersion = "0.6.0" // x-release-please-version
Loading