Skip to content

Commit a9230b1

Browse files
authored
Merge pull request #7 from tstromberg/main
Improve error compatibility, update README
2 parents b9b5842 + a9de5fa commit a9230b1

19 files changed

+301
-111
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,14 @@ on:
99
jobs:
1010
test:
1111
runs-on: ubuntu-latest
12-
strategy:
13-
matrix:
14-
go-version: [ '1.22', '1.23' ]
1512

1613
steps:
1714
- uses: actions/checkout@v4
1815

1916
- name: Set up Go
2017
uses: actions/setup-go@v5
2118
with:
22-
go-version: ${{ matrix.go-version }}
19+
go-version: '1.25'
2320

2421
- name: Run tests
2522
run: make test

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: test integration lint build clean
22

33
test:
4-
go test -v -race -cover ./...
4+
go test -race -cover ./...
55

66
DS9_TEST_PROJECT ?= integration-testing-476513
77
DS9_TEST_DATABASE ?= ds9-test

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
# ds9
22

3-
Zero-dependency Google Cloud Datastore client for Go. Drop-in replacement for `cloud.google.com/go/datastore` basic operations. In-memory mock implementation. Comprehensive testing.
3+
<img src="media/logo-small.png" alt="ds9 logo" align="right">
44

5-
**Why?** The official client has 50+ dependencies. `ds9` uses only Go stdlib—ideal for lightweight services and minimizing supply chain risk.
5+
[![Go Reference](https://pkg.go.dev/badge/github.com/codeGROOVE-dev/ds9.svg)](https://pkg.go.dev/github.com/codeGROOVE-dev/ds9)
6+
[![Go Report Card](https://goreportcard.com/badge/github.com/codeGROOVE-dev/ds9)](https://goreportcard.com/report/github.com/codeGROOVE-dev/ds9)
7+
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
8+
[![Go Version](https://img.shields.io/github/go-mod/go-version/codeGROOVE-dev/ds9)](go.mod)
9+
10+
Zero-dependency Google Cloud Datastore client for Go. Drop-in replacement for `cloud.google.com/go/datastore` with CRUD, transactions, queries, cursors, and mutations. In-memory mock implementation. Comprehensive testing.
11+
12+
**Why?** The official client has 50+ dependencies. `ds9` has zero—ideal for lightweight services and minimizing supply chain risk.
613

714
## Installation
815

@@ -36,6 +43,7 @@ Just switch the import path from `cloud.google.com/go/datastore` to `github.com/
3643
- **Cursors**: Start, End, DecodeCursor
3744
- **Keys**: NameKey, IDKey, IncompleteKey, AllocateIDs, parent keys
3845
- **Mutations**: NewInsert, NewUpdate, NewUpsert, NewDelete, Mutate
46+
- **Errors**: ErrNoSuchEntity, ErrInvalidKey, ErrInvalidEntityType, ErrConcurrentTransaction, Done, MultiError
3947
- **Types**: string, int, int64, int32, bool, float64, time.Time, slices ([]string, []int64, []int, []float64, []bool)
4048

4149
**Unsupported Features**
@@ -46,6 +54,6 @@ These features are unsupported just because we haven't found a use for the featu
4654

4755
## Testing
4856

49-
* Use `github.com/codeGROOVE-dev/ds9/pkg/mock` package for in-memory testing. It should work even if you choose not to use ds9.
57+
* Use `datastore.NewMockClient(t)` for in-memory testing. Works even if you're still using the official client.
5058
* See [TESTING.md](TESTING.md) for integration tests.
5159
* We aim to maintain 85% test coverage - please don't send PRs without tests.

integration/integration_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,19 @@ func TestIntegrationBatchOperations(t *testing.T) {
186186

187187
var retrieved []integrationEntity
188188
err = client.GetMulti(ctx, keys, &retrieved)
189-
if !errors.Is(err, datastore.ErrNoSuchEntity) {
190-
t.Errorf("expected datastore.ErrNoSuchEntity after DeleteMulti, got %v", err)
189+
if err == nil {
190+
t.Fatal("expected error after DeleteMulti, got nil")
191+
}
192+
193+
var multiErr datastore.MultiError
194+
if !errors.As(err, &multiErr) {
195+
t.Fatalf("expected MultiError after DeleteMulti, got %T: %v", err, err)
196+
}
197+
198+
for i, e := range multiErr {
199+
if !errors.Is(e, datastore.ErrNoSuchEntity) {
200+
t.Errorf("expected ErrNoSuchEntity at index %d, got %v", i, e)
201+
}
191202
}
192203
})
193204
}

pkg/datastore/cursor_coverage_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func TestCursorWithLimitedResults(t *testing.T) {
115115
for {
116116
var entity testEntity
117117
_, err := it.Next(&entity)
118-
if errors.Is(err, datastore.ErrDone) {
118+
if errors.Is(err, datastore.Done) {
119119
break
120120
}
121121
if err != nil {

pkg/datastore/entity_coverage_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func TestIterator_Coverage(t *testing.T) {
212212
Time time.Time
213213
}
214214
_, err := it.Next(&entity)
215-
if errors.Is(err, ErrDone) {
215+
if errors.Is(err, Done) {
216216
break
217217
}
218218
if err != nil {

pkg/datastore/errors.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
package datastore
22

3-
import "errors"
3+
import (
4+
"errors"
5+
"fmt"
6+
)
47

58
var (
6-
// ErrNoSuchEntity is returned when an entity is not found.
9+
// ErrInvalidEntityType is returned when functions like Get or Next are
10+
// passed a dst or src argument of invalid type.
11+
ErrInvalidEntityType = errors.New("datastore: invalid entity type")
12+
13+
// ErrInvalidKey is returned when an invalid key is presented.
14+
ErrInvalidKey = errors.New("datastore: invalid key")
15+
16+
// ErrNoSuchEntity is returned when no entity was found for a given key.
717
ErrNoSuchEntity = errors.New("datastore: no such entity")
818

9-
// ErrDone is returned by Iterator.Next when no more results are available.
10-
ErrDone = errors.New("datastore: no more results")
19+
// ErrConcurrentTransaction is returned when a transaction is used concurrently.
20+
ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")
21+
22+
// Done is returned by Iterator.Next when no more results are available.
23+
// This matches the official cloud.google.com/go/datastore API.
24+
//
25+
//nolint:revive,errname,staticcheck // Name must match official API (iterator.Done)
26+
Done = errors.New("datastore: no more items in iterator")
1127
)
28+
29+
// MultiError is returned by batch operations when there are errors with
30+
// particular elements. Errors will be in a one-to-one correspondence with
31+
// the input elements; successful elements will have a nil entry.
32+
type MultiError []error
33+
34+
func (m MultiError) Error() string {
35+
s, n := "", 0
36+
for _, e := range m {
37+
if e != nil {
38+
if n == 0 {
39+
s = e.Error()
40+
}
41+
n++
42+
}
43+
}
44+
switch n {
45+
case 0:
46+
return "(0 errors)"
47+
case 1:
48+
return s
49+
case 2:
50+
return s + " (and 1 other error)"
51+
}
52+
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
53+
}

pkg/datastore/iterator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (it *Iterator) Next(dst any) (*Key, error) {
4141
return nil, it.err
4242
}
4343
if !it.fetchNext {
44-
return nil, ErrDone
44+
return nil, Done
4545
}
4646

4747
// Fetch next batch
@@ -51,7 +51,7 @@ func (it *Iterator) Next(dst any) (*Key, error) {
5151
}
5252

5353
if len(it.results) == 0 {
54-
return nil, ErrDone
54+
return nil, Done
5555
}
5656
}
5757

pkg/datastore/iterator_coverage_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestIteratorMultipleFetches(t *testing.T) {
6464
Time time.Time
6565
}
6666
_, err := it.Next(&entity)
67-
if errors.Is(err, ErrDone) {
67+
if errors.Is(err, Done) {
6868
break
6969
}
7070
if err != nil {
@@ -209,7 +209,7 @@ func TestIteratorKeysOnly(t *testing.T) {
209209
Data string
210210
}
211211
key, err := it.Next(&entity)
212-
if errors.Is(err, ErrDone) {
212+
if errors.Is(err, Done) {
213213
break
214214
}
215215
if err != nil {

pkg/datastore/iterator_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestIterator(t *testing.T) {
3535
for {
3636
var entity testEntity
3737
key, err := it.Next(&entity)
38-
if errors.Is(err, datastore.ErrDone) {
38+
if errors.Is(err, datastore.Done) {
3939
break
4040
}
4141
if err != nil {
@@ -89,8 +89,8 @@ func TestIterator(t *testing.T) {
8989

9090
var entity testEntity
9191
_, err := it.Next(&entity)
92-
if !errors.Is(err, datastore.ErrDone) {
93-
t.Errorf("Expected datastore.ErrDone, got %v", err)
92+
if !errors.Is(err, datastore.Done) {
93+
t.Errorf("Expected datastore.Done, got %v", err)
9494
}
9595
})
9696
}

0 commit comments

Comments
 (0)