From 9130f8c40783560bc02d5b494c60549dbe092dcd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 15:16:39 +0000
Subject: [PATCH 1/6] feat(api): add pagination to the deployments endpoint
---
.stats.yml | 6 +-
README.md | 31 ++++++++
api.md | 2 +-
deployment.go | 28 ++++++-
deployment_test.go | 4 +-
packages/pagination/pagination.go | 127 ++++++++++++++++++++++++++++++
paginationauto_test.go | 39 +++++++++
paginationmanual_test.go | 47 +++++++++++
8 files changed, 275 insertions(+), 9 deletions(-)
create mode 100644 packages/pagination/pagination.go
create mode 100644 paginationauto_test.go
create mode 100644 paginationmanual_test.go
diff --git a/.stats.yml b/.stats.yml
index 6ac19ba..8943c2f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 46
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
-openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
-config_hash: 043ddc54629c6d8b889123770cb4769f
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-33f1feaba7bde46bfa36d2fefb5c3bc9512967945bccf78045ad3f64aafc4eb0.yml
+openapi_spec_hash: 52a448889d41216d1ca30e8a57115b14
+config_hash: 1f28d5c3c063f418ebd2799df1e4e781
diff --git a/README.md b/README.md
index 0b271ea..e4616d5 100644
--- a/README.md
+++ b/README.md
@@ -289,9 +289,40 @@ This library provides some conveniences for working with paginated list endpoint
You can use `.ListAutoPaging()` methods to iterate through items across all pages:
+```go
+iter := client.Deployments.ListAutoPaging(context.TODO(), kernel.DeploymentListParams{
+ AppName: "YOUR_APP",
+ Limit: kernel.Int(2),
+})
+// Automatically fetches more pages as needed.
+for iter.Next() {
+ deploymentListResponse := iter.Current()
+ fmt.Printf("%+v\n", deploymentListResponse)
+}
+if err := iter.Err(); err != nil {
+ panic(err.Error())
+}
+```
+
Or you can use simple `.List()` methods to fetch a single page and receive a standard response object
with additional helper methods like `.GetNextPage()`, e.g.:
+```go
+page, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
+ AppName: "YOUR_APP",
+ Limit: kernel.Int(2),
+})
+for page != nil {
+ for _, deployment := range page.Items {
+ fmt.Printf("%+v\n", deployment)
+ }
+ page, err = page.GetNextPage()
+}
+if err != nil {
+ panic(err.Error())
+}
+```
+
### Errors
When the API returns a non-success status code, we return an error with type
diff --git a/api.md b/api.md
index 0efea8f..30dabbc 100644
--- a/api.md
+++ b/api.md
@@ -21,7 +21,7 @@ Methods:
- client.Deployments.New(ctx context.Context, body kernel.DeploymentNewParams) (kernel.DeploymentNewResponse, error)
- client.Deployments.Get(ctx context.Context, id string) (kernel.DeploymentGetResponse, error)
-- client.Deployments.List(ctx context.Context, query kernel.DeploymentListParams) ([]kernel.DeploymentListResponse, error)
+- client.Deployments.List(ctx context.Context, query kernel.DeploymentListParams) (pagination.OffsetPagination[kernel.DeploymentListResponse], error)
- client.Deployments.Follow(ctx context.Context, id string, query kernel.DeploymentFollowParams) (kernel.DeploymentFollowResponseUnion, error)
# Apps
diff --git a/deployment.go b/deployment.go
index 9409a69..903db2e 100644
--- a/deployment.go
+++ b/deployment.go
@@ -19,6 +19,7 @@ import (
"github.com/onkernel/kernel-go-sdk/internal/apiquery"
"github.com/onkernel/kernel-go-sdk/internal/requestconfig"
"github.com/onkernel/kernel-go-sdk/option"
+ "github.com/onkernel/kernel-go-sdk/packages/pagination"
"github.com/onkernel/kernel-go-sdk/packages/param"
"github.com/onkernel/kernel-go-sdk/packages/respjson"
"github.com/onkernel/kernel-go-sdk/packages/ssestream"
@@ -66,11 +67,26 @@ func (r *DeploymentService) Get(ctx context.Context, id string, opts ...option.R
}
// List deployments. Optionally filter by application name.
-func (r *DeploymentService) List(ctx context.Context, query DeploymentListParams, opts ...option.RequestOption) (res *[]DeploymentListResponse, err error) {
+func (r *DeploymentService) List(ctx context.Context, query DeploymentListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[DeploymentListResponse], err error) {
+ var raw *http.Response
opts = append(r.Options[:], opts...)
+ opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...)
path := "deployments"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...)
+ if err != nil {
+ return nil, err
+ }
+ err = cfg.Execute()
+ if err != nil {
+ return nil, err
+ }
+ res.SetPageConfig(cfg, raw)
+ return res, nil
+}
+
+// List deployments. Optionally filter by application name.
+func (r *DeploymentService) ListAutoPaging(ctx context.Context, query DeploymentListParams, opts ...option.RequestOption) *pagination.OffsetPaginationAutoPager[DeploymentListResponse] {
+ return pagination.NewOffsetPaginationAutoPager(r.List(ctx, query, opts...))
}
// Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and
@@ -476,7 +492,11 @@ const (
type DeploymentListParams struct {
// Filter results by application name.
- AppName param.Opt[string] `query:"app_name,omitzero" json:"-"`
+ AppName string `query:"app_name,required" json:"-"`
+ // Limit the number of deployments to return.
+ Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
+ // Offset the number of deployments to return.
+ Offset param.Opt[int64] `query:"offset,omitzero" json:"-"`
paramObj
}
diff --git a/deployment_test.go b/deployment_test.go
index 931721c..03b148f 100644
--- a/deployment_test.go
+++ b/deployment_test.go
@@ -84,7 +84,9 @@ func TestDeploymentListWithOptionalParams(t *testing.T) {
option.WithAPIKey("My API Key"),
)
_, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
- AppName: kernel.String("app_name"),
+ AppName: "app_name",
+ Limit: kernel.Int(1),
+ Offset: kernel.Int(0),
})
if err != nil {
var apierr *kernel.Error
diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go
new file mode 100644
index 0000000..739e57f
--- /dev/null
+++ b/packages/pagination/pagination.go
@@ -0,0 +1,127 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package pagination
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/onkernel/kernel-go-sdk/internal/apijson"
+ "github.com/onkernel/kernel-go-sdk/internal/requestconfig"
+ "github.com/onkernel/kernel-go-sdk/option"
+ "github.com/onkernel/kernel-go-sdk/packages/param"
+ "github.com/onkernel/kernel-go-sdk/packages/respjson"
+)
+
+// aliased to make [param.APIUnion] private when embedding
+type paramUnion = param.APIUnion
+
+// aliased to make [param.APIObject] private when embedding
+type paramObj = param.APIObject
+
+type OffsetPagination[T any] struct {
+ Items []T `json:",inline"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ Items respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+ cfg *requestconfig.RequestConfig
+ res *http.Response
+}
+
+// Returns the unmodified JSON received from the API
+func (r OffsetPagination[T]) RawJSON() string { return r.JSON.raw }
+func (r *OffsetPagination[T]) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// GetNextPage returns the next page as defined by this pagination style. When
+// there is no next page, this function will return a 'nil' for the page value, but
+// will not return an error
+func (r *OffsetPagination[T]) GetNextPage() (res *OffsetPagination[T], err error) {
+ if len(r.Items) == 0 {
+ return nil, nil
+ }
+ cfg := r.cfg.Clone(r.cfg.Context)
+
+ q := cfg.Request.URL.Query()
+ offset, err := strconv.ParseInt(q.Get("offset"), 10, 64)
+ if err != nil {
+ offset = 0
+ }
+ length := int64(len(r.Items))
+ next := offset + length
+
+ if length > 0 && next != 0 {
+ err = cfg.Apply(option.WithQuery("offset", strconv.FormatInt(next, 10)))
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ return nil, nil
+ }
+ var raw *http.Response
+ cfg.ResponseInto = &raw
+ cfg.ResponseBodyInto = &res
+ err = cfg.Execute()
+ if err != nil {
+ return nil, err
+ }
+ res.SetPageConfig(cfg, raw)
+ return res, nil
+}
+
+func (r *OffsetPagination[T]) SetPageConfig(cfg *requestconfig.RequestConfig, res *http.Response) {
+ if r == nil {
+ r = &OffsetPagination[T]{}
+ }
+ r.cfg = cfg
+ r.res = res
+}
+
+type OffsetPaginationAutoPager[T any] struct {
+ page *OffsetPagination[T]
+ cur T
+ idx int
+ run int
+ err error
+ paramObj
+}
+
+func NewOffsetPaginationAutoPager[T any](page *OffsetPagination[T], err error) *OffsetPaginationAutoPager[T] {
+ return &OffsetPaginationAutoPager[T]{
+ page: page,
+ err: err,
+ }
+}
+
+func (r *OffsetPaginationAutoPager[T]) Next() bool {
+ if r.page == nil || len(r.page.Items) == 0 {
+ return false
+ }
+ if r.idx >= len(r.page.Items) {
+ r.idx = 0
+ r.page, r.err = r.page.GetNextPage()
+ if r.err != nil || r.page == nil || len(r.page.Items) == 0 {
+ return false
+ }
+ }
+ r.cur = r.page.Items[r.idx]
+ r.run += 1
+ r.idx += 1
+ return true
+}
+
+func (r *OffsetPaginationAutoPager[T]) Current() T {
+ return r.cur
+}
+
+func (r *OffsetPaginationAutoPager[T]) Err() error {
+ return r.err
+}
+
+func (r *OffsetPaginationAutoPager[T]) Index() int {
+ return r.run
+}
diff --git a/paginationauto_test.go b/paginationauto_test.go
new file mode 100644
index 0000000..e3003e1
--- /dev/null
+++ b/paginationauto_test.go
@@ -0,0 +1,39 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package kernel_test
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "github.com/onkernel/kernel-go-sdk"
+ "github.com/onkernel/kernel-go-sdk/internal/testutil"
+ "github.com/onkernel/kernel-go-sdk/option"
+)
+
+func TestAutoPagination(t *testing.T) {
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ iter := client.Deployments.ListAutoPaging(context.TODO(), kernel.DeploymentListParams{
+ AppName: "YOUR_APP",
+ Limit: kernel.Int(2),
+ })
+ // Prism mock isn't going to give us real pagination
+ for i := 0; i < 3 && iter.Next(); i++ {
+ deployment := iter.Current()
+ t.Logf("%+v\n", deployment.ID)
+ }
+ if err := iter.Err(); err != nil {
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/paginationmanual_test.go b/paginationmanual_test.go
new file mode 100644
index 0000000..53d6156
--- /dev/null
+++ b/paginationmanual_test.go
@@ -0,0 +1,47 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package kernel_test
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "github.com/onkernel/kernel-go-sdk"
+ "github.com/onkernel/kernel-go-sdk/internal/testutil"
+ "github.com/onkernel/kernel-go-sdk/option"
+)
+
+func TestManualPagination(t *testing.T) {
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ page, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
+ AppName: "YOUR_APP",
+ Limit: kernel.Int(2),
+ })
+ if err != nil {
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+ for _, deployment := range page.Items {
+ t.Logf("%+v\n", deployment.ID)
+ }
+ // Prism mock isn't going to give us real pagination
+ page, err = page.GetNextPage()
+ if err != nil {
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+ if page != nil {
+ for _, deployment := range page.Items {
+ t.Logf("%+v\n", deployment.ID)
+ }
+ }
+}
From ad37eb2bdc92a4145e5dba9d8ef8b4ad26c9b64a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 15:19:32 +0000
Subject: [PATCH 2/6] feat(api): update API spec with pagination headers
---
.stats.yml | 4 ++--
README.md | 4 ++--
deployment.go | 2 +-
deployment_test.go | 2 +-
paginationauto_test.go | 2 +-
paginationmanual_test.go | 2 +-
6 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 8943c2f..9635bb2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 46
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-33f1feaba7bde46bfa36d2fefb5c3bc9512967945bccf78045ad3f64aafc4eb0.yml
-openapi_spec_hash: 52a448889d41216d1ca30e8a57115b14
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cb38560915edce03abce2ae3ef5bc745489dbe9b6f80c2b4ff42edf8c2ff276d.yml
+openapi_spec_hash: a869194d6c864ba28d79ec0105439c3e
config_hash: 1f28d5c3c063f418ebd2799df1e4e781
diff --git a/README.md b/README.md
index e4616d5..b66f647 100644
--- a/README.md
+++ b/README.md
@@ -291,7 +291,7 @@ You can use `.ListAutoPaging()` methods to iterate through items across all page
```go
iter := client.Deployments.ListAutoPaging(context.TODO(), kernel.DeploymentListParams{
- AppName: "YOUR_APP",
+ AppName: kernel.String("YOUR_APP"),
Limit: kernel.Int(2),
})
// Automatically fetches more pages as needed.
@@ -309,7 +309,7 @@ with additional helper methods like `.GetNextPage()`, e.g.:
```go
page, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
- AppName: "YOUR_APP",
+ AppName: kernel.String("YOUR_APP"),
Limit: kernel.Int(2),
})
for page != nil {
diff --git a/deployment.go b/deployment.go
index 903db2e..368c538 100644
--- a/deployment.go
+++ b/deployment.go
@@ -492,7 +492,7 @@ const (
type DeploymentListParams struct {
// Filter results by application name.
- AppName string `query:"app_name,required" json:"-"`
+ AppName param.Opt[string] `query:"app_name,omitzero" json:"-"`
// Limit the number of deployments to return.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Offset the number of deployments to return.
diff --git a/deployment_test.go b/deployment_test.go
index 03b148f..81715eb 100644
--- a/deployment_test.go
+++ b/deployment_test.go
@@ -84,7 +84,7 @@ func TestDeploymentListWithOptionalParams(t *testing.T) {
option.WithAPIKey("My API Key"),
)
_, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
- AppName: "app_name",
+ AppName: kernel.String("app_name"),
Limit: kernel.Int(1),
Offset: kernel.Int(0),
})
diff --git a/paginationauto_test.go b/paginationauto_test.go
index e3003e1..d407e2d 100644
--- a/paginationauto_test.go
+++ b/paginationauto_test.go
@@ -25,7 +25,7 @@ func TestAutoPagination(t *testing.T) {
option.WithAPIKey("My API Key"),
)
iter := client.Deployments.ListAutoPaging(context.TODO(), kernel.DeploymentListParams{
- AppName: "YOUR_APP",
+ AppName: kernel.String("YOUR_APP"),
Limit: kernel.Int(2),
})
// Prism mock isn't going to give us real pagination
diff --git a/paginationmanual_test.go b/paginationmanual_test.go
index 53d6156..136e786 100644
--- a/paginationmanual_test.go
+++ b/paginationmanual_test.go
@@ -25,7 +25,7 @@ func TestManualPagination(t *testing.T) {
option.WithAPIKey("My API Key"),
)
page, err := client.Deployments.List(context.TODO(), kernel.DeploymentListParams{
- AppName: "YOUR_APP",
+ AppName: kernel.String("YOUR_APP"),
Limit: kernel.Int(2),
})
if err != nil {
From 65c53429690169c3ca5068b94991af6eba3832ff Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 18:00:26 +0000
Subject: [PATCH 3/6] feat(api): pagination properties added to response
(has_more, next_offset)
---
.stats.yml | 2 +-
packages/pagination/pagination.go | 7 +------
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 9635bb2..7fb3d31 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 46
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cb38560915edce03abce2ae3ef5bc745489dbe9b6f80c2b4ff42edf8c2ff276d.yml
openapi_spec_hash: a869194d6c864ba28d79ec0105439c3e
-config_hash: 1f28d5c3c063f418ebd2799df1e4e781
+config_hash: ed56f95781ec9b2e73c97e1a66606071
diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go
index 739e57f..6181b12 100644
--- a/packages/pagination/pagination.go
+++ b/packages/pagination/pagination.go
@@ -46,13 +46,8 @@ func (r *OffsetPagination[T]) GetNextPage() (res *OffsetPagination[T], err error
}
cfg := r.cfg.Clone(r.cfg.Context)
- q := cfg.Request.URL.Query()
- offset, err := strconv.ParseInt(q.Get("offset"), 10, 64)
- if err != nil {
- offset = 0
- }
+ next := r.res.Header.Get("X-Next-Offset")
length := int64(len(r.Items))
- next := offset + length
if length > 0 && next != 0 {
err = cfg.Apply(option.WithQuery("offset", strconv.FormatInt(next, 10)))
From d42bd8a79dc3f64ea6aa8c3f4239b8cfa8545d82 Mon Sep 17 00:00:00 2001
From: David Meadows
Date: Fri, 5 Sep 2025 16:18:14 -0400
Subject: [PATCH 4/6] fix(client): correctly convert header pagination value to
int
---
packages/pagination/pagination.go | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go
index 6181b12..abd8460 100644
--- a/packages/pagination/pagination.go
+++ b/packages/pagination/pagination.go
@@ -46,7 +46,11 @@ func (r *OffsetPagination[T]) GetNextPage() (res *OffsetPagination[T], err error
}
cfg := r.cfg.Clone(r.cfg.Context)
- next := r.res.Header.Get("X-Next-Offset")
+ nextStr := r.res.Header.Get("X-Next-Offset")
+ next, err := strconv.ParseInt(nextStr, 10, 64)
+ if err != nil {
+ return nil, err
+ }
length := int64(len(r.Items))
if length > 0 && next != 0 {
From 649203d6621f6850ac4cb414b04b78c9693d1923 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 6 Sep 2025 03:50:37 +0000
Subject: [PATCH 5/6] fix(internal): unmarshal correctly when there are
multiple discriminators
---
internal/apijson/decodeparam_test.go | 88 ++++++++++++++++++++++++++++
internal/apijson/union.go | 48 ++++++++-------
2 files changed, 115 insertions(+), 21 deletions(-)
diff --git a/internal/apijson/decodeparam_test.go b/internal/apijson/decodeparam_test.go
index 7abe5d8..39b8318 100644
--- a/internal/apijson/decodeparam_test.go
+++ b/internal/apijson/decodeparam_test.go
@@ -351,6 +351,36 @@ func init() {
})
}
+type FooVariant struct {
+ Type string `json:"type,required"`
+ Value string `json:"value,required"`
+}
+
+type BarVariant struct {
+ Type string `json:"type,required"`
+ Enable bool `json:"enable,required"`
+}
+
+type MultiDiscriminatorUnion struct {
+ OfFoo *FooVariant `json:",inline"`
+ OfBar *BarVariant `json:",inline"`
+
+ paramUnion
+}
+
+func init() {
+ apijson.RegisterDiscriminatedUnion[MultiDiscriminatorUnion]("type", map[string]reflect.Type{
+ "foo": reflect.TypeOf(FooVariant{}),
+ "foo_v2": reflect.TypeOf(FooVariant{}),
+ "bar": reflect.TypeOf(BarVariant{}),
+ "bar_legacy": reflect.TypeOf(BarVariant{}),
+ })
+}
+
+func (m *MultiDiscriminatorUnion) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, m)
+}
+
func (d *DiscriminatedUnion) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, d)
}
@@ -408,3 +438,61 @@ func TestDiscriminatedUnion(t *testing.T) {
})
}
}
+
+func TestMultiDiscriminatorUnion(t *testing.T) {
+ tests := map[string]struct {
+ raw string
+ target MultiDiscriminatorUnion
+ shouldFail bool
+ }{
+ "foo_variant": {
+ raw: `{"type":"foo","value":"test"}`,
+ target: MultiDiscriminatorUnion{OfFoo: &FooVariant{
+ Type: "foo",
+ Value: "test",
+ }},
+ },
+ "foo_v2_variant": {
+ raw: `{"type":"foo_v2","value":"test_v2"}`,
+ target: MultiDiscriminatorUnion{OfFoo: &FooVariant{
+ Type: "foo_v2",
+ Value: "test_v2",
+ }},
+ },
+ "bar_variant": {
+ raw: `{"type":"bar","enable":true}`,
+ target: MultiDiscriminatorUnion{OfBar: &BarVariant{
+ Type: "bar",
+ Enable: true,
+ }},
+ },
+ "bar_legacy_variant": {
+ raw: `{"type":"bar_legacy","enable":false}`,
+ target: MultiDiscriminatorUnion{OfBar: &BarVariant{
+ Type: "bar_legacy",
+ Enable: false,
+ }},
+ },
+ "invalid_type": {
+ raw: `{"type":"unknown","value":"test"}`,
+ target: MultiDiscriminatorUnion{},
+ shouldFail: true,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ var dst MultiDiscriminatorUnion
+ err := json.Unmarshal([]byte(test.raw), &dst)
+ if err != nil && !test.shouldFail {
+ t.Fatalf("failed unmarshal with err: %v", err)
+ }
+ if err == nil && test.shouldFail {
+ t.Fatalf("expected unmarshal to fail but it succeeded")
+ }
+ if !reflect.DeepEqual(dst, test.target) {
+ t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target)
+ }
+ })
+ }
+}
diff --git a/internal/apijson/union.go b/internal/apijson/union.go
index b9b8bda..9d717e7 100644
--- a/internal/apijson/union.go
+++ b/internal/apijson/union.go
@@ -39,12 +39,10 @@ func RegisterDiscriminatedUnion[T any](key string, mappings map[string]reflect.T
func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
type variantDecoder struct {
- decoder decoderFunc
- field reflect.StructField
- discriminatorValue any
+ decoder decoderFunc
+ field reflect.StructField
}
-
- variants := []variantDecoder{}
+ decoders := []variantDecoder{}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
@@ -53,18 +51,26 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
}
decoder := d.typeDecoder(field.Type)
- variants = append(variants, variantDecoder{
+ decoders = append(decoders, variantDecoder{
decoder: decoder,
field: field,
})
}
+ type discriminatedDecoder struct {
+ variantDecoder
+ discriminator any
+ }
+ discriminatedDecoders := []discriminatedDecoder{}
unionEntry, discriminated := unionRegistry[t]
- for _, unionVariant := range unionEntry.variants {
- for i := 0; i < len(variants); i++ {
- variant := &variants[i]
- if variant.field.Type.Elem() == unionVariant.Type {
- variant.discriminatorValue = unionVariant.DiscriminatorValue
+ for _, variant := range unionEntry.variants {
+ // For each union variant, find a matching decoder and save it
+ for _, decoder := range decoders {
+ if decoder.field.Type.Elem() == variant.Type {
+ discriminatedDecoders = append(discriminatedDecoders, discriminatedDecoder{
+ decoder,
+ variant.DiscriminatorValue,
+ })
break
}
}
@@ -73,10 +79,10 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
if discriminated && n.Type == gjson.JSON && len(unionEntry.discriminatorKey) != 0 {
discriminator := n.Get(unionEntry.discriminatorKey).Value()
- for _, variant := range variants {
- if discriminator == variant.discriminatorValue {
- inner := v.FieldByIndex(variant.field.Index)
- return variant.decoder(n, inner, state)
+ for _, decoder := range discriminatedDecoders {
+ if discriminator == decoder.discriminator {
+ inner := v.FieldByIndex(decoder.field.Index)
+ return decoder.decoder(n, inner, state)
}
}
return errors.New("apijson: was not able to find discriminated union variant")
@@ -85,15 +91,15 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
// Set bestExactness to worse than loose
bestExactness := loose - 1
bestVariant := -1
- for i, variant := range variants {
+ for i, decoder := range decoders {
// Pointers are used to discern JSON object variants from value variants
- if n.Type != gjson.JSON && variant.field.Type.Kind() == reflect.Ptr {
+ if n.Type != gjson.JSON && decoder.field.Type.Kind() == reflect.Ptr {
continue
}
sub := decoderState{strict: state.strict, exactness: exact}
- inner := v.FieldByIndex(variant.field.Index)
- err := variant.decoder(n, inner, &sub)
+ inner := v.FieldByIndex(decoder.field.Index)
+ err := decoder.decoder(n, inner, &sub)
if err != nil {
continue
}
@@ -116,11 +122,11 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
return errors.New("apijson: was not able to coerce type as union strictly")
}
- for i := 0; i < len(variants); i++ {
+ for i := 0; i < len(decoders); i++ {
if i == bestVariant {
continue
}
- v.FieldByIndex(variants[i].field.Index).SetZero()
+ v.FieldByIndex(decoders[i].field.Index).SetZero()
}
return nil
From 2a425e88c53788014f77816778063db37d646078 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 6 Sep 2025 03:50:54 +0000
Subject: [PATCH 6/6] release: 0.11.1
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 16 ++++++++++++++++
README.md | 2 +-
internal/version.go | 2 +-
4 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f7014c3..e82003f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.11.0"
+ ".": "0.11.1"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b890e4e..2ea13b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 0.11.1 (2025-09-06)
+
+Full Changelog: [v0.11.0...v0.11.1](https://github.com/onkernel/kernel-go-sdk/compare/v0.11.0...v0.11.1)
+
+### Features
+
+* **api:** add pagination to the deployments endpoint ([9130f8c](https://github.com/onkernel/kernel-go-sdk/commit/9130f8c40783560bc02d5b494c60549dbe092dcd))
+* **api:** pagination properties added to response (has_more, next_offset) ([65c5342](https://github.com/onkernel/kernel-go-sdk/commit/65c53429690169c3ca5068b94991af6eba3832ff))
+* **api:** update API spec with pagination headers ([ad37eb2](https://github.com/onkernel/kernel-go-sdk/commit/ad37eb2bdc92a4145e5dba9d8ef8b4ad26c9b64a))
+
+
+### Bug Fixes
+
+* **client:** correctly convert header pagination value to int ([d42bd8a](https://github.com/onkernel/kernel-go-sdk/commit/d42bd8a79dc3f64ea6aa8c3f4239b8cfa8545d82))
+* **internal:** unmarshal correctly when there are multiple discriminators ([649203d](https://github.com/onkernel/kernel-go-sdk/commit/649203d6621f6850ac4cb414b04b78c9693d1923))
+
## 0.11.0 (2025-09-04)
Full Changelog: [v0.10.0...v0.11.0](https://github.com/onkernel/kernel-go-sdk/compare/v0.10.0...v0.11.0)
diff --git a/README.md b/README.md
index b66f647..083f30a 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/onkernel/kernel-go-sdk@v0.11.0'
+go get -u 'github.com/onkernel/kernel-go-sdk@v0.11.1'
```
diff --git a/internal/version.go b/internal/version.go
index 153db11..8985cf3 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.11.0" // x-release-please-version
+const PackageVersion = "0.11.1" // x-release-please-version