Skip to content

Commit d758353

Browse files
authored
Merge pull request #66 from stevenctl/idiomatic-errors
idiomatic error handling
2 parents f587e92 + d8c56a0 commit d758353

File tree

4 files changed

+86
-62
lines changed

4 files changed

+86
-62
lines changed

client.go

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ type Client struct {
2020
Transport *transport
2121
}
2222

23-
// NewClient constructs a new client given a URL to a Postgrest instance.
24-
func NewClient(rawURL, schema string, headers map[string]string) *Client {
23+
// NewClientWithError constructs a new client given a URL to a Postgrest instance.
24+
func NewClientWithError(rawURL, schema string, headers map[string]string) (*Client, error) {
2525
// Create URL from rawURL
2626
baseURL, err := url.Parse(rawURL)
2727
if err != nil {
28-
return &Client{ClientError: err}
28+
return nil, err
2929
}
3030

3131
t := transport{
@@ -55,30 +55,42 @@ func NewClient(rawURL, schema string, headers map[string]string) *Client {
5555
c.Transport.header.Set(key, value)
5656
}
5757

58-
return &c
58+
return &c, nil
5959
}
6060

61-
func (c *Client) Ping() bool {
62-
req, err := http.NewRequest("GET", path.Join(c.Transport.baseURL.Path, ""), nil)
61+
// NewClient constructs a new client given a URL to a Postgrest instance.
62+
func NewClient(rawURL, schema string, headers map[string]string) *Client {
63+
client, err := NewClientWithError(rawURL, schema, headers)
6364
if err != nil {
64-
c.ClientError = err
65+
return &Client{ClientError: err}
66+
}
67+
return client
68+
}
6569

66-
return false
70+
func (c *Client) PingWithError() error {
71+
req, err := http.NewRequest("GET", path.Join(c.Transport.baseURL.Path, ""), nil)
72+
if err != nil {
73+
return err
6774
}
6875

6976
resp, err := c.session.Do(req)
7077
if err != nil {
71-
c.ClientError = err
72-
73-
return false
78+
return err
7479
}
7580

7681
if resp.Status != "200 OK" {
77-
c.ClientError = errors.New("ping failed")
82+
return errors.New("ping failed")
83+
}
7884

85+
return nil
86+
}
87+
88+
func (c *Client) Ping() bool {
89+
err := c.PingWithError()
90+
if err != nil {
91+
c.ClientError = err
7992
return false
8093
}
81-
8294
return true
8395
}
8496

@@ -106,16 +118,15 @@ func (c *Client) From(table string) *QueryBuilder {
106118
return &QueryBuilder{client: c, tableName: table, headers: map[string]string{}, params: map[string]string{}}
107119
}
108120

109-
// Rpc executes a Postgres function (a.k.a., Remote Prodedure Call), given the
121+
// RpcWithError executes a Postgres function (a.k.a., Remote Prodedure Call), given the
110122
// function name and, optionally, a body, returning the result as a string.
111-
func (c *Client) Rpc(name string, count string, rpcBody interface{}) string {
123+
func (c *Client) RpcWithError(name string, count string, rpcBody interface{}) (string, error) {
112124
// Get body if it exists
113125
var byteBody []byte = nil
114126
if rpcBody != nil {
115127
jsonBody, err := json.Marshal(rpcBody)
116128
if err != nil {
117-
c.ClientError = err
118-
return ""
129+
return "", err
119130
}
120131
byteBody = jsonBody
121132
}
@@ -124,8 +135,7 @@ func (c *Client) Rpc(name string, count string, rpcBody interface{}) string {
124135
url := path.Join(c.Transport.baseURL.Path, "rpc", name)
125136
req, err := http.NewRequest("POST", url, readerBody)
126137
if err != nil {
127-
c.ClientError = err
128-
return ""
138+
return "", err
129139
}
130140

131141
if count != "" && (count == `exact` || count == `planned` || count == `estimated`) {
@@ -134,24 +144,32 @@ func (c *Client) Rpc(name string, count string, rpcBody interface{}) string {
134144

135145
resp, err := c.session.Do(req)
136146
if err != nil {
137-
c.ClientError = err
138-
return ""
147+
return "", err
139148
}
140149

141150
body, err := io.ReadAll(resp.Body)
142151
if err != nil {
143-
c.ClientError = err
144-
return ""
152+
return "", err
145153
}
146154

147155
result := string(body)
148156

149157
err = resp.Body.Close()
158+
if err != nil {
159+
return "", err
160+
}
161+
162+
return result, nil
163+
}
164+
165+
// Rpc executes a Postgres function (a.k.a., Remote Prodedure Call), given the
166+
// function name and, optionally, a body, returning the result as a string.
167+
func (c *Client) Rpc(name string, count string, rpcBody interface{}) string {
168+
result, err := c.RpcWithError(name, count, rpcBody)
150169
if err != nil {
151170
c.ClientError = err
152171
return ""
153172
}
154-
155173
return result
156174
}
157175

execute.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ type ExecuteError struct {
2626
Message string `json:"message"`
2727
}
2828

29-
func executeHelper(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string) ([]byte, countType, error) {
30-
if client.ClientError != nil {
31-
return nil, 0, client.ClientError
29+
func executeHelper(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string, err error) ([]byte, countType, error) {
30+
if err != nil {
31+
return nil, 0, err
3232
}
33-
3433
readerBody := bytes.NewBuffer(body)
3534
baseUrl := path.Join(append([]string{client.Transport.baseURL.Path}, urlFragments...)...)
3635
req, err := http.NewRequestWithContext(ctx, method, baseUrl, readerBody)
@@ -87,17 +86,17 @@ func executeHelper(ctx context.Context, client *Client, method string, body []by
8786
return respBody, count, nil
8887
}
8988

90-
func executeString(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string) (string, countType, error) {
91-
resp, count, err := executeHelper(ctx, client, method, body, urlFragments, headers, params)
89+
func executeString(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string, err error) (string, countType, error) {
90+
resp, count, err := executeHelper(ctx, client, method, body, urlFragments, headers, params, err)
9291
return string(resp), count, err
9392
}
9493

95-
func execute(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string) ([]byte, countType, error) {
96-
return executeHelper(ctx, client, method, body, urlFragments, headers, params)
94+
func execute(ctx context.Context, client *Client, method string, body []byte, urlFragments []string, headers map[string]string, params map[string]string, err error) ([]byte, countType, error) {
95+
return executeHelper(ctx, client, method, body, urlFragments, headers, params, err)
9796
}
9897

99-
func executeTo(ctx context.Context, client *Client, method string, body []byte, to interface{}, urlFragments []string, headers map[string]string, params map[string]string) (countType, error) {
100-
resp, count, err := executeHelper(ctx, client, method, body, urlFragments, headers, params)
98+
func executeTo(ctx context.Context, client *Client, method string, body []byte, to interface{}, urlFragments []string, headers map[string]string, params map[string]string, err error) (countType, error) {
99+
resp, count, err := executeHelper(ctx, client, method, body, urlFragments, headers, params, err)
101100

102101
if err != nil {
103102
return count, err

filterbuilder.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package postgrest
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"regexp"
9+
"slices"
810
"strconv"
911
"strings"
1012
)
@@ -17,43 +19,44 @@ type FilterBuilder struct {
1719
tableName string
1820
headers map[string]string
1921
params map[string]string
22+
err error
2023
}
2124

2225
// ExecuteString runs the PostgREST query, returning the result as a JSON
2326
// string.
2427
func (f *FilterBuilder) ExecuteString() (string, int64, error) {
25-
return executeString(context.Background(), f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
28+
return executeString(context.Background(), f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params, f.err)
2629
}
2730

2831
// ExecuteStringWithContext runs the PostgREST query, returning the result as
2932
// a JSON string.
3033
func (f *FilterBuilder) ExecuteStringWithContext(ctx context.Context) (string, int64, error) {
31-
return executeString(ctx, f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
34+
return executeString(ctx, f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params, f.err)
3235
}
3336

3437
// Execute runs the PostgREST query, returning the result as a byte slice.
3538
func (f *FilterBuilder) Execute() ([]byte, int64, error) {
36-
return execute(context.Background(), f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
39+
return execute(context.Background(), f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params, f.err)
3740
}
3841

3942
// ExecuteWithContext runs the PostgREST query with the given context,
4043
// returning the result as a byte slice.
4144
func (f *FilterBuilder) ExecuteWithContext(ctx context.Context) ([]byte, int64, error) {
42-
return execute(ctx, f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params)
45+
return execute(ctx, f.client, f.method, f.body, []string{f.tableName}, f.headers, f.params, f.err)
4346
}
4447

4548
// ExecuteTo runs the PostgREST query, encoding the result to the supplied
4649
// interface. Note that the argument for the to parameter should always be a
4750
// reference to a slice.
4851
func (f *FilterBuilder) ExecuteTo(to interface{}) (countType, error) {
49-
return executeTo(context.Background(), f.client, f.method, f.body, to, []string{f.tableName}, f.headers, f.params)
52+
return executeTo(context.Background(), f.client, f.method, f.body, to, []string{f.tableName}, f.headers, f.params, f.err)
5053
}
5154

5255
// ExecuteToWithContext runs the PostgREST query with the given context,
5356
// encoding the result to the supplied interface. Note that the argument for
5457
// the to parameter should always be a reference to a slice.
5558
func (f *FilterBuilder) ExecuteToWithContext(ctx context.Context, to interface{}) (countType, error) {
56-
return executeTo(ctx, f.client, f.method, f.body, to, []string{f.tableName}, f.headers, f.params)
59+
return executeTo(ctx, f.client, f.method, f.body, to, []string{f.tableName}, f.headers, f.params, f.err)
5760
}
5861

5962
var filterOperators = []string{"eq", "neq", "gt", "gte", "lt", "lte", "like", "ilike", "is", "in", "cs", "cd", "sl", "sr", "nxl", "nxr", "adj", "ov", "fts", "plfts", "phfts", "wfts"}
@@ -74,19 +77,16 @@ func (f *FilterBuilder) appendFilter(column, filterValue string) *FilterBuilder
7477
}
7578

7679
func isOperator(value string) bool {
77-
for _, operator := range filterOperators {
78-
if value == operator {
79-
return true
80-
}
81-
}
82-
return false
80+
return slices.Contains(filterOperators, value)
8381
}
8482

8583
// Filter adds a filtering operator to the query. For a list of available
8684
// operators, see: https://postgrest.org/en/stable/api.html#operators
8785
func (f *FilterBuilder) Filter(column, operator, value string) *FilterBuilder {
8886
if !isOperator(operator) {
89-
f.client.ClientError = fmt.Errorf("invalid filter operator")
87+
err := fmt.Errorf("invalid Filter operator: %s", operator)
88+
f.client.ClientError = err
89+
f.err = errors.Join(f.err, err)
9090
return f
9191
}
9292
return f.appendFilter(column, fmt.Sprintf("%s.%s", operator, value))
@@ -200,6 +200,7 @@ func (f *FilterBuilder) ContainsObject(column string, value interface{}) *Filter
200200
sum, err := json.Marshal(value)
201201
if err != nil {
202202
f.client.ClientError = err
203+
f.err = errors.Join(f.err, fmt.Errorf("error marshaling value for ContainsObject: %w", err))
203204
return f
204205
}
205206
return f.appendFilter(column, "cs."+string(sum))
@@ -208,7 +209,9 @@ func (f *FilterBuilder) ContainsObject(column string, value interface{}) *Filter
208209
func (f *FilterBuilder) ContainedByObject(column string, value interface{}) *FilterBuilder {
209210
sum, err := json.Marshal(value)
210211
if err != nil {
212+
err := fmt.Errorf("error marshaling value for ContainedByObject: %w", err)
211213
f.client.ClientError = err
214+
f.err = errors.Join(f.err, err)
212215
return f
213216
}
214217
return f.appendFilter(column, "cd."+string(sum))
@@ -257,7 +260,9 @@ func (f *FilterBuilder) TextSearch(column, userQuery, config, tsType string) *Fi
257260
} else if tsType == "" {
258261
typePart = ""
259262
} else {
260-
f.client.ClientError = fmt.Errorf("invalid text search type")
263+
err := fmt.Errorf("invalid text search type: %s", tsType)
264+
f.client.ClientError = err
265+
f.err = errors.Join(f.err, err)
261266
return f
262267
}
263268
if config != "" {

0 commit comments

Comments
 (0)