Skip to content

Commit 811a539

Browse files
feat(api): api update
1 parent 884e0ef commit 811a539

File tree

19 files changed

+370
-402
lines changed

19 files changed

+370
-402
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 44
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-3b740aa1797fa0df593719056d00d7dc052b100b4d38593f1e9454c1be99be91.yml
3-
openapi_spec_hash: 1f3d27b14316ef4cc23fa6e926d61371
4-
config_hash: 6ecca2f2b1cb843a69ccd54e8c6179dc
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-9118b947ebe901fea6fe0bdb068f9d76b6cd1cb9dd4fd5e58cf53f716c59970f.yml
3+
openapi_spec_hash: d844099910f7699aaf0a6f1ba7a5df9f
4+
config_hash: e19ee82b9c29b95754a9f0c3d58e3034

api.md

Lines changed: 41 additions & 41 deletions
Large diffs are not rendered by default.

internal/encoding/json/encode.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ type mapEncoder struct {
776776
}
777777

778778
func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
779-
if v.IsNil() /* EDIT(begin) */ || sentinel.IsValueNull(v) /* EDIT(end) */ {
779+
if v.IsNil() {
780780
e.WriteString("null")
781781
return
782782
}
@@ -855,7 +855,7 @@ type sliceEncoder struct {
855855
}
856856

857857
func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
858-
if v.IsNil() /* EDIT(begin) */ || sentinel.IsValueNull(v) /* EDIT(end) */ {
858+
if v.IsNil() {
859859
e.WriteString("null")
860860
return
861861
}
@@ -916,7 +916,14 @@ type ptrEncoder struct {
916916
}
917917

918918
func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
919-
if v.IsNil() {
919+
// EDIT(begin)
920+
//
921+
// if v.IsNil() {
922+
// e.WriteString("null")
923+
// return
924+
// }
925+
926+
if v.IsNil() || sentinel.IsValueNullPtr(v) || sentinel.IsValueNullSlice(v) {
920927
e.WriteString("null")
921928
return
922929
}

internal/encoding/json/sentinel/null.go

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,52 @@ import (
66
"sync"
77
)
88

9-
type cacheEntry struct {
10-
x any
11-
ptr uintptr
12-
kind reflect.Kind
9+
var nullPtrsCache sync.Map // map[reflect.Type]*T
10+
11+
func NullPtr[T any]() *T {
12+
t := shims.TypeFor[T]()
13+
ptr, loaded := nullPtrsCache.Load(t) // avoid premature allocation
14+
if !loaded {
15+
ptr, _ = nullPtrsCache.LoadOrStore(t, new(T))
16+
}
17+
return (ptr.(*T))
1318
}
1419

15-
var nullCache sync.Map // map[reflect.Type]cacheEntry
20+
var nullSlicesCache sync.Map // map[reflect.Type][]T
1621

17-
func NewNullSentinel[T any](mk func() T) T {
22+
func NullSlice[T any]() []T {
1823
t := shims.TypeFor[T]()
19-
entry, loaded := nullCache.Load(t) // avoid premature allocation
24+
slice, loaded := nullSlicesCache.Load(t) // avoid premature allocation
2025
if !loaded {
21-
x := mk()
22-
ptr := reflect.ValueOf(x).Pointer()
23-
entry, _ = nullCache.LoadOrStore(t, cacheEntry{x, ptr, t.Kind()})
26+
slice, _ = nullSlicesCache.LoadOrStore(t, []T{})
2427
}
25-
return entry.(cacheEntry).x.(T)
28+
return slice.([]T)
2629
}
2730

28-
// for internal use only
29-
func IsValueNull(v reflect.Value) bool {
30-
switch v.Kind() {
31-
case reflect.Map, reflect.Slice:
32-
null, ok := nullCache.Load(v.Type())
33-
return ok && v.Pointer() == null.(cacheEntry).ptr
31+
func IsNullPtr[T any](ptr *T) bool {
32+
nullptr, ok := nullPtrsCache.Load(shims.TypeFor[T]())
33+
return ok && ptr == nullptr.(*T)
34+
}
35+
36+
func IsNullSlice[T any](slice []T) bool {
37+
nullSlice, ok := nullSlicesCache.Load(shims.TypeFor[T]())
38+
return ok && reflect.ValueOf(slice).Pointer() == reflect.ValueOf(nullSlice).Pointer()
39+
}
40+
41+
// internal only
42+
func IsValueNullPtr(v reflect.Value) bool {
43+
if v.Kind() != reflect.Ptr {
44+
return false
3445
}
35-
return false
46+
nullptr, ok := nullPtrsCache.Load(v.Type().Elem())
47+
return ok && v.Pointer() == reflect.ValueOf(nullptr).Pointer()
3648
}
3749

38-
func IsNull[T any](v T) bool {
39-
t := shims.TypeFor[T]()
40-
switch t.Kind() {
41-
case reflect.Map, reflect.Slice:
42-
null, ok := nullCache.Load(t)
43-
return ok && reflect.ValueOf(v).Pointer() == null.(cacheEntry).ptr
50+
// internal only
51+
func IsValueNullSlice(v reflect.Value) bool {
52+
if v.Kind() != reflect.Slice {
53+
return false
4454
}
45-
return false
55+
nullSlice, ok := nullSlicesCache.Load(v.Type().Elem())
56+
return ok && v.Pointer() == reflect.ValueOf(nullSlice).Pointer()
4657
}

internal/encoding/json/sentinel/sentinel_test.go

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package sentinel_test
22

33
import (
44
"github.com/babelcloud/gbox-sdk-go/internal/encoding/json/sentinel"
5-
"github.com/babelcloud/gbox-sdk-go/packages/param"
65
"reflect"
76
"slices"
87
"testing"
@@ -16,19 +15,25 @@ type Pair struct {
1615
func TestNullSlice(t *testing.T) {
1716
var nilSlice []int = nil
1817
var nonNilSlice []int = []int{1, 2, 3}
19-
var nullSlice []int = param.NullSlice[[]int]()
18+
var nullSlice []int = sentinel.NullSlice[int]()
2019

2120
cases := map[string]Pair{
22-
"nilSlice": {sentinel.IsNull(nilSlice), false},
23-
"nullSlice": {sentinel.IsNull(nullSlice), true},
24-
"newNullSlice": {sentinel.IsNull(param.NullSlice[[]int]()), true},
21+
"nilSlice": {sentinel.IsNullSlice(nilSlice), false},
22+
"nullSlice": {sentinel.IsNullSlice(nullSlice), true},
23+
"newNullSlice": {sentinel.IsNullSlice(sentinel.NullSlice[int]()), true},
2524
"lenNullSlice": {len(nullSlice) == 0, true},
26-
"nilSliceValue": {sentinel.IsValueNull(reflect.ValueOf(nilSlice)), false},
27-
"nullSliceValue": {sentinel.IsValueNull(reflect.ValueOf(nullSlice)), true},
25+
"nilSliceValue": {sentinel.IsValueNullSlice(reflect.ValueOf(nilSlice)), false},
26+
"nullSliceValue": {sentinel.IsValueNullSlice(reflect.ValueOf(nullSlice)), true},
2827
"compareSlices": {slices.Compare(nilSlice, nullSlice) == 0, true},
2928
"compareNonNilSlices": {slices.Compare(nonNilSlice, nullSlice) == 0, false},
3029
}
3130

31+
nilSlice = append(nullSlice, 12)
32+
cases["append_result"] = Pair{sentinel.IsNullSlice(nilSlice), false}
33+
cases["mutated_result"] = Pair{sentinel.IsNullSlice(nullSlice), true}
34+
cases["append_result_len"] = Pair{len(nilSlice) == 1, true}
35+
cases["append_null_slice_len"] = Pair{len(nullSlice) == 0, true}
36+
3237
for name, c := range cases {
3338
t.Run(name, func(t *testing.T) {
3439
got, want := c.got, c.want
@@ -39,20 +44,37 @@ func TestNullSlice(t *testing.T) {
3944
}
4045
}
4146

42-
func TestNullMap(t *testing.T) {
43-
var nilMap map[string]int = nil
44-
var nonNilMap map[string]int = map[string]int{"a": 1, "b": 2}
45-
var nullMap map[string]int = param.NullMap[map[string]int]()
47+
func TestNullPtr(t *testing.T) {
48+
var s *string = nil
49+
var i *int = nil
50+
var slice *[]int = nil
51+
52+
var nullptrStr *string = sentinel.NullPtr[string]()
53+
var nullptrInt *int = sentinel.NullPtr[int]()
54+
var nullptrSlice *[]int = sentinel.NullPtr[[]int]()
55+
56+
if *nullptrStr != "" {
57+
t.Errorf("Failed to safely deref")
58+
}
59+
if *nullptrInt != 0 {
60+
t.Errorf("Failed to safely deref")
61+
}
62+
if len(*nullptrSlice) != 0 {
63+
t.Errorf("Failed to safely deref")
64+
}
4665

4766
cases := map[string]Pair{
48-
"nilMap": {sentinel.IsNull(nilMap), false},
49-
"nullMap": {sentinel.IsNull(nullMap), true},
50-
"newNullMap": {sentinel.IsNull(param.NullMap[map[string]int]()), true},
51-
"lenNullMap": {len(nullMap) == 0, true},
52-
"nilMapValue": {sentinel.IsValueNull(reflect.ValueOf(nilMap)), false},
53-
"nullMapValue": {sentinel.IsValueNull(reflect.ValueOf(nullMap)), true},
54-
"compareMaps": {reflect.DeepEqual(nilMap, nullMap), false},
55-
"compareNonNilMaps": {reflect.DeepEqual(nonNilMap, nullMap), false},
67+
"nilStr": {sentinel.IsNullPtr(s), false},
68+
"nullStr": {sentinel.IsNullPtr(nullptrStr), true},
69+
70+
"nilInt": {sentinel.IsNullPtr(i), false},
71+
"nullInt": {sentinel.IsNullPtr(nullptrInt), true},
72+
73+
"nilSlice": {sentinel.IsNullPtr(slice), false},
74+
"nullSlice": {sentinel.IsNullPtr(nullptrSlice), true},
75+
76+
"nilValuePtr": {sentinel.IsValueNullPtr(reflect.ValueOf(i)), false},
77+
"nullValuePtr": {sentinel.IsValueNullPtr(reflect.ValueOf(nullptrInt)), true},
5678
}
5779

5880
for name, test := range cases {
@@ -64,28 +86,3 @@ func TestNullMap(t *testing.T) {
6486
})
6587
}
6688
}
67-
68-
func TestIsNullRepeated(t *testing.T) {
69-
// Test for slices
70-
nullSlice1 := param.NullSlice[[]int]()
71-
nullSlice2 := param.NullSlice[[]int]()
72-
if !sentinel.IsNull(nullSlice1) {
73-
t.Errorf("IsNull(nullSlice1) = false, want true")
74-
}
75-
if !sentinel.IsNull(nullSlice2) {
76-
t.Errorf("IsNull(nullSlice2) = false, want true")
77-
}
78-
if !sentinel.IsNull(nullSlice1) || !sentinel.IsNull(nullSlice2) {
79-
t.Errorf("IsNull should return true for all NullSlice instances")
80-
}
81-
82-
// Test for maps
83-
nullMap1 := param.NullMap[map[string]int]()
84-
nullMap2 := param.NullMap[map[string]int]()
85-
if !sentinel.IsNull(nullMap1) {
86-
t.Errorf("IsNull(nullMap1) = false, want true")
87-
}
88-
if !sentinel.IsNull(nullMap2) {
89-
t.Errorf("IsNull(nullMap2) = false, want true")
90-
}
91-
}

internal/paramutil/sentinel.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package paramutil
2+
3+
import (
4+
"github.com/babelcloud/gbox-sdk-go/internal/encoding/json/sentinel"
5+
)
6+
7+
// NullPtr returns a pointer to the zero value of the type T.
8+
// When used with [MarshalObject] or [MarshalUnion], it will be marshaled as null.
9+
//
10+
// It is unspecified behavior to mutate the value pointed to by the returned pointer.
11+
func NullPtr[T any]() *T {
12+
return sentinel.NullPtr[T]()
13+
}
14+
15+
// IsNullPtr returns true if the pointer was created by [NullPtr].
16+
func IsNullPtr[T any](ptr *T) bool {
17+
return sentinel.IsNullPtr(ptr)
18+
}
19+
20+
// NullSlice returns a non-nil slice with a length of 0.
21+
// When used with [MarshalObject] or [MarshalUnion], it will be marshaled as null.
22+
//
23+
// It is undefined behavior to mutate the slice returned by [NullSlice].
24+
func NullSlice[T any]() []T {
25+
return sentinel.NullSlice[T]()
26+
}
27+
28+
// IsNullSlice returns true if the slice was created by [NullSlice].
29+
func IsNullSlice[T any](slice []T) bool {
30+
return sentinel.IsNullSlice(slice)
31+
}

packages/param/null.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/param/null_test.go

Lines changed: 0 additions & 49 deletions
This file was deleted.

packages/param/param.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package param
22

33
import (
44
"encoding/json"
5-
"github.com/babelcloud/gbox-sdk-go/internal/encoding/json/sentinel"
65
"reflect"
76
)
87

@@ -59,21 +58,12 @@ func IsOmitted(v any) bool {
5958

6059
// IsNull returns true if v was set to the JSON value null.
6160
//
62-
// To set a param to null use [NullStruct], [Null], [NullMap], or [NullSlice]
61+
// To set a param to null use [NullStruct] or [Null]
6362
// depending on the type of v.
6463
//
6564
// IsNull returns false if the value is omitted.
66-
func IsNull[T any](v T) bool {
67-
if nullable, ok := any(v).(ParamNullable); ok {
68-
return nullable.null()
69-
}
70-
71-
switch reflect.TypeOf(v).Kind() {
72-
case reflect.Slice, reflect.Map:
73-
return sentinel.IsNull(v)
74-
}
75-
76-
return false
65+
func IsNull(v ParamNullable) bool {
66+
return v.null()
7767
}
7868

7969
// ParamNullable encapsulates all structs in parameters,

0 commit comments

Comments
 (0)