Skip to content

Commit 1b6d882

Browse files
authored
release: v0.7.1 (#195)
release:v0.7.1
1 parent 19fd59d commit 1b6d882

File tree

9 files changed

+131
-93
lines changed

9 files changed

+131
-93
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Changelog
22

3-
## [Unreleased](https://github.com/openfga/go-sdk/compare/v0.7.0...HEAD)
3+
## [Unreleased](https://github.com/openfga/go-sdk/compare/v0.7.1...HEAD)
4+
5+
## v0.7.1
6+
7+
### [0.7.1](https://github.com/openfga/go-sdk/compare/v0.7.0...v0.7.1) (2025-04-07)
8+
9+
- fix: resolves issue where SDK could stall when MaxParallelRequests * MaxBatchSize < body.Checks length
10+
- fix: resolves issue where private singleBatchCheck method in SdkClient prevented mocking
411

512
## v0.7.0
613

client/client.go

+8-46
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
_nethttp "net/http"
2121
"time"
2222

23+
"github.com/sourcegraph/conc/pool"
2324
"golang.org/x/sync/errgroup"
2425

2526
fgaSdk "github.com/openfga/go-sdk"
@@ -397,15 +398,6 @@ type SdkClient interface {
397398
*/
398399
BatchCheckExecute(request SdkClientBatchCheckRequestInterface) (*fgaSdk.BatchCheckResponse, error)
399400

400-
/*
401-
* singleBatchCheck Run a single batch check on the server.
402-
* @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
403-
* @param body BatchCheckRequest - the request to send to the server.
404-
* @param options *BatchCheckOptions - options for the request.
405-
* @return *BatchCheckResponse
406-
*/
407-
singleBatchCheck(ctx _context.Context, body fgaSdk.BatchCheckRequest, options *BatchCheckOptions) (*fgaSdk.BatchCheckResponse, error)
408-
409401
/*
410402
* Expand Expands the relationships in userset tree format.
411403
* @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@@ -2220,55 +2212,25 @@ func (client *OpenFgaClient) BatchCheckExecute(request SdkClientBatchCheckReques
22202212

22212213
chunks := chunkClientBatchCheckItems(body.Checks, int(maxBatchSize))
22222214

2223-
resultChan := make(chan *fgaSdk.BatchCheckResponse)
2224-
errorChan := make(chan error)
2225-
2226-
var wg errgroup.Group
2227-
wg.SetLimit(int(maxParallelRequests))
2215+
p := pool.NewWithResults[*fgaSdk.BatchCheckResponse]().WithContext(ctx).WithMaxGoroutines(int(maxParallelRequests))
22282216

22292217
for _, chunk := range chunks {
22302218
chunkCopy := chunk
22312219

2232-
wg.Go(func() error {
2220+
p.Go(func(ctx _context.Context) (*fgaSdk.BatchCheckResponse, error) {
22332221
batchCheckRequest := createBatchCheckRequest(chunkCopy, authorizationModelId, options.Consistency)
2234-
2235-
response, err := client.singleBatchCheck(ctx, batchCheckRequest, options)
2236-
if err != nil {
2237-
errorChan <- err
2238-
return err
2239-
}
2240-
2241-
resultChan <- response
2242-
return nil
2222+
return client.singleBatchCheck(ctx, batchCheckRequest, options)
22432223
})
22442224
}
22452225

2246-
go func() {
2247-
err := wg.Wait()
2248-
if err != nil {
2249-
errorChan <- err
2250-
}
2251-
close(resultChan)
2252-
close(errorChan)
2253-
}()
2254-
2255-
var results []*fgaSdk.BatchCheckResponse
2256-
for response := range resultChan {
2257-
results = append(results, response)
2258-
}
2259-
2260-
select {
2261-
case err := <-errorChan:
2262-
if err != nil {
2263-
return nil, err
2264-
}
2265-
default:
2266-
// No error
2226+
responses, err := p.Wait()
2227+
if err != nil {
2228+
return nil, err
22672229
}
22682230

22692231
combinedResult := make(map[string]fgaSdk.BatchCheckSingleResult)
22702232

2271-
for _, response := range results {
2233+
for _, response := range responses {
22722234
for correlationID, result := range response.GetResult() {
22732235
combinedResult[correlationID] = result
22742236
}

client/client_test.go

+85-40
Original file line numberDiff line numberDiff line change
@@ -3493,54 +3493,99 @@ func TestOpenFgaClient(t *testing.T) {
34933493
},
34943494
)
34953495

3496-
items := []ClientBatchCheckItem{
3497-
{
3498-
User: "user:1",
3499-
Relation: "viewer",
3500-
Object: "document:1",
3501-
CorrelationId: "test1",
3502-
},
3503-
{
3504-
User: "user:2",
3496+
// Create basic item template
3497+
createItem := func(i int) ClientBatchCheckItem {
3498+
return ClientBatchCheckItem{
3499+
User: fmt.Sprintf("user:%d", i),
35053500
Relation: "viewer",
3506-
Object: "document:2",
3507-
CorrelationId: "test2",
3508-
},
3509-
{
3510-
User: "user:3",
3511-
Relation: "viewer",
3512-
Object: "document:3",
3513-
CorrelationId: "test3",
3514-
},
3501+
Object: fmt.Sprintf("document:%d", i),
3502+
CorrelationId: fmt.Sprintf("test%d", i),
3503+
}
35153504
}
35163505

3517-
requestBody := ClientBatchCheckRequest{
3518-
Checks: items,
3519-
}
3506+
t.Run("Simple case with MaxBatchSize=1", func(t *testing.T) {
3507+
callCountMu.Lock()
3508+
callCount = 0
3509+
callCountMu.Unlock()
35203510

3521-
options := BatchCheckOptions{
3522-
MaxBatchSize: openfga.PtrInt32(1),
3523-
}
3511+
items := []ClientBatchCheckItem{
3512+
createItem(1),
3513+
createItem(2),
3514+
createItem(3),
3515+
}
35243516

3525-
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
3526-
defer cancel()
3517+
requestBody := ClientBatchCheckRequest{
3518+
Checks: items,
3519+
}
35273520

3528-
_, err = fgaClient.BatchCheck(ctx).
3529-
Body(requestBody).
3530-
Options(options).
3531-
Execute()
3521+
options := BatchCheckOptions{
3522+
MaxBatchSize: openfga.PtrInt32(1),
3523+
}
35323524

3533-
if err != nil {
3534-
t.Fatalf("BatchCheck error: %v", err)
3535-
}
3525+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
3526+
defer cancel()
35363527

3537-
expectedCallCount := len(items) // With MaxBatchSize=1, we expect one call per item
3538-
callCountMu.Lock()
3539-
actualCallCount := callCount
3540-
callCountMu.Unlock()
3541-
if actualCallCount != expectedCallCount {
3542-
t.Errorf("Expected exactly %d API calls with MaxBatchSize=1, got %d", expectedCallCount, actualCallCount)
3543-
}
3528+
_, err = fgaClient.BatchCheck(ctx).
3529+
Body(requestBody).
3530+
Options(options).
3531+
Execute()
3532+
3533+
if err != nil {
3534+
t.Fatalf("BatchCheck error: %v", err)
3535+
}
3536+
3537+
expectedCallCount := len(items) // With MaxBatchSize=1, we expect one call per item
3538+
callCountMu.Lock()
3539+
actualCallCount := callCount
3540+
callCountMu.Unlock()
3541+
if actualCallCount != expectedCallCount {
3542+
t.Errorf("Expected exactly %d API calls with MaxBatchSize=1, got %d", expectedCallCount, actualCallCount)
3543+
}
3544+
})
3545+
3546+
t.Run("Edge case where MaxParallelRequests * MaxBatchSize < len(items)", func(t *testing.T) {
3547+
callCountMu.Lock()
3548+
callCount = 0
3549+
callCountMu.Unlock()
3550+
3551+
// Create 101 items
3552+
var items []ClientBatchCheckItem
3553+
for i := 1; i <= 101; i++ {
3554+
items = append(items, createItem(i))
3555+
}
3556+
3557+
requestBody := ClientBatchCheckRequest{
3558+
Checks: items,
3559+
}
3560+
3561+
options := BatchCheckOptions{
3562+
MaxParallelRequests: openfga.PtrInt32(2), // 2 parallel requests
3563+
MaxBatchSize: openfga.PtrInt32(50), // 50 items per batch
3564+
}
3565+
// Total capacity: 2*50 = 100, but we have 101 items
3566+
3567+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
3568+
defer cancel()
3569+
3570+
_, err = fgaClient.BatchCheck(ctx).
3571+
Body(requestBody).
3572+
Options(options).
3573+
Execute()
3574+
3575+
if err != nil {
3576+
t.Fatalf("BatchCheck error with MaxParallelRequests=2, MaxBatchSize=50, Items=101: %v", err)
3577+
}
3578+
3579+
// We expect 3 API calls (2 batches of 50 + 1 batch of 1)
3580+
expectedCallCount := 3
3581+
callCountMu.Lock()
3582+
actualCallCount := callCount
3583+
callCountMu.Unlock()
3584+
if actualCallCount != expectedCallCount {
3585+
t.Errorf("Expected exactly %d API calls with MaxParallelRequests=2, MaxBatchSize=50, Items=101, got %d",
3586+
expectedCallCount, actualCallCount)
3587+
}
3588+
})
35443589
})
35453590
}
35463591

configuration.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import (
2121
)
2222

2323
const (
24-
SdkVersion = "0.7.0"
24+
SdkVersion = "0.7.1"
2525

26-
defaultUserAgent = "openfga-sdk go/0.7.0"
26+
defaultUserAgent = "openfga-sdk go/0.7.1"
2727
)
2828

2929
// RetryParams provides configuration for retries in case of server errors

example/example1/go.mod

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ go 1.23.0
44

55
toolchain go1.24.0
66

7-
require github.com/openfga/go-sdk v0.7.0
7+
require github.com/openfga/go-sdk v0.7.1
88

99
require (
1010
github.com/go-logr/logr v1.4.2 // indirect
1111
github.com/go-logr/stdr v1.2.2 // indirect
12+
github.com/sourcegraph/conc v0.3.0 // indirect
1213
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
1314
go.opentelemetry.io/otel v1.35.0 // indirect
1415
go.opentelemetry.io/otel/metric v1.35.0 // indirect
1516
go.opentelemetry.io/otel/trace v1.35.0 // indirect
17+
go.uber.org/atomic v1.7.0 // indirect
18+
go.uber.org/multierr v1.9.0 // indirect
1619
golang.org/x/sync v0.12.0 // indirect
1720
)
1821

1922
// To reference local build, uncomment below and run `go mod tidy`
20-
replace github.com/openfga/go-sdk v0.7.0 => ../../
23+
replace github.com/openfga/go-sdk v0.7.1 => ../../

example/example1/go.sum

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -11,6 +12,10 @@ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInw
1112
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
1213
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1314
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
16+
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
17+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
18+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
1419
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1520
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1621
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -21,6 +26,10 @@ go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/
2126
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
2227
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
2328
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
29+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
30+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
31+
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
32+
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
2433
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
2534
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
2635
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

example/opentelemetry/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ go 1.23.0
55
toolchain go1.24.0
66

77
// To reference local build, uncomment below and run `go mod tidy`
8-
replace github.com/openfga/go-sdk v0.7.0 => ../../
8+
replace github.com/openfga/go-sdk v0.7.1 => ../../
99

1010
require (
1111
github.com/joho/godotenv v1.5.1
12-
github.com/openfga/go-sdk v0.7.0
12+
github.com/openfga/go-sdk v0.7.1
1313
go.opentelemetry.io/otel v1.35.0
1414
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0
1515
go.opentelemetry.io/otel/sdk v1.35.0

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ toolchain go1.24.0
66

77
require (
88
github.com/jarcoal/httpmock v1.3.1
9+
github.com/sourcegraph/conc v0.3.0
910
go.opentelemetry.io/otel v1.35.0
1011
go.opentelemetry.io/otel/metric v1.35.0
1112
golang.org/x/sync v0.12.0
@@ -16,4 +17,6 @@ require (
1617
github.com/go-logr/stdr v1.2.2 // indirect
1718
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
1819
go.opentelemetry.io/otel/trace v1.35.0 // indirect
20+
go.uber.org/atomic v1.7.0 // indirect
21+
go.uber.org/multierr v1.9.0 // indirect
1922
)

go.sum

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -13,6 +14,10 @@ github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04
1314
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
1415
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1516
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
18+
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
19+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
20+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
1621
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1722
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1823
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -23,6 +28,10 @@ go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/
2328
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
2429
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
2530
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
31+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
32+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
33+
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
34+
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
2635
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
2736
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
2837
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

0 commit comments

Comments
 (0)