Skip to content

Commit 0e3e593

Browse files
committed
Return HTTP response codes as typed errors
This allows to differentiate a datasource not found from other errors, e.g. ```go _, err = client.GetDatasource(ctx, dsID) if errors.Is(err, sdk.ErrNotFound) { fmt.Fprintf(os.Stderr, "Creating new datasource %s (id=%d)\n", ds.Name, ds.ID) } ```
1 parent 9de4d14 commit 0e3e593

File tree

11 files changed

+223
-93
lines changed

11 files changed

+223
-93
lines changed

README.md

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ It was made foremost for
1717
later separated from it and moved to this new repository because the
1818
library is useful per se.
1919

20+
The library requires at least Go 1.13.
21+
2022
## Library design principles
2123

2224
1. SDK offers client functionality so it covers Grafana REST API with
@@ -102,20 +104,7 @@ datasources. State of support for misc API parts noted below.
102104
| Frontend settings | - |
103105
| Admin | partially |
104106

105-
There is no exact roadmap. The integration tests are being run against the
106-
following Grafana versions:
107-
108-
* [6.7.1](./travis.yml)
109-
* [6.6.2](/.travis.yml)
110-
* [6.5.3](/.travis.yml)
111-
* [6.4.5](/.travis.yml)
112-
113-
With the following Go versions:
114-
115-
* 1.14.x
116-
* 1.13.x
117-
* 1.12.x
118-
* 1.11.x
107+
The integration tests are being run for the Grafana and Go versions listed in [`.github/workflows/go.yml`](.github/workflows/go.yml).
119108

120109
I still have interest to this library development but not always have
121110
time for it. So I gladly accept new contributions. Drop an issue or

rest-admin.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78
)
89

910
// CreateUser creates a new global user.
@@ -14,13 +15,17 @@ func (r *Client) CreateUser(ctx context.Context, user User) (StatusMessage, erro
1415
raw []byte
1516
resp StatusMessage
1617
err error
18+
code int
1719
)
1820
if raw, err = json.Marshal(user); err != nil {
1921
return StatusMessage{}, err
2022
}
21-
if raw, _, err = r.post(ctx, "api/admin/users", nil, raw); err != nil {
23+
if raw, code, err = r.post(ctx, "api/admin/users", nil, raw); err != nil {
2224
return StatusMessage{}, err
2325
}
26+
if code != http.StatusOK {
27+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
28+
}
2429
if err = json.Unmarshal(raw, &resp); err != nil {
2530
return StatusMessage{}, err
2631
}
@@ -35,13 +40,17 @@ func (r *Client) UpdateUserPermissions(ctx context.Context, permissions UserPerm
3540
raw []byte
3641
reply StatusMessage
3742
err error
43+
code int
3844
)
3945
if raw, err = json.Marshal(permissions); err != nil {
4046
return StatusMessage{}, err
4147
}
42-
if raw, _, err = r.put(ctx, fmt.Sprintf("api/admin/users/%d/permissions", uid), nil, raw); err != nil {
48+
if raw, code, err = r.put(ctx, fmt.Sprintf("api/admin/users/%d/permissions", uid), nil, raw); err != nil {
4349
return StatusMessage{}, err
4450
}
51+
if code != http.StatusOK {
52+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
53+
}
4554
err = json.Unmarshal(raw, &reply)
4655
return reply, err
4756
}
@@ -54,11 +63,15 @@ func (r *Client) SwitchUserContext(ctx context.Context, uid uint, oid uint) (Sta
5463
raw []byte
5564
resp StatusMessage
5665
err error
66+
code int
5767
)
5868

59-
if raw, _, err = r.post(ctx, fmt.Sprintf("/api/users/%d/using/%d", uid, oid), nil, raw); err != nil {
69+
if raw, code, err = r.post(ctx, fmt.Sprintf("/api/users/%d/using/%d", uid, oid), nil, raw); err != nil {
6070
return StatusMessage{}, err
6171
}
72+
if code != http.StatusOK {
73+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
74+
}
6275
if err = json.Unmarshal(raw, &resp); err != nil {
6376
return StatusMessage{}, err
6477
}

rest-alertnotification.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"net/http"
2324
)
2425

2526
// GetAllAlertNotifications gets all alert notification channels.
@@ -34,7 +35,7 @@ func (c *Client) GetAllAlertNotifications(ctx context.Context) ([]AlertNotificat
3435
if raw, code, err = c.get(ctx, "api/alert-notifications", nil); err != nil {
3536
return nil, err
3637
}
37-
if code != 200 {
38+
if code != http.StatusOK {
3839
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
3940
}
4041
err = json.Unmarshal(raw, &an)
@@ -53,7 +54,7 @@ func (c *Client) GetAlertNotificationUID(ctx context.Context, uid string) (Alert
5354
if raw, code, err = c.get(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid), nil); err != nil {
5455
return an, err
5556
}
56-
if code != 200 {
57+
if code != http.StatusOK {
5758
return an, fmt.Errorf("HTTP error %d: returns %s", code, raw)
5859
}
5960
err = json.Unmarshal(raw, &an)
@@ -72,7 +73,7 @@ func (c *Client) GetAlertNotificationID(ctx context.Context, id uint) (AlertNoti
7273
if raw, code, err = c.get(ctx, fmt.Sprintf("api/alert-notifications/%d", id), nil); err != nil {
7374
return an, err
7475
}
75-
if code != 200 {
76+
if code != http.StatusOK {
7677
return an, fmt.Errorf("HTTP error %d: returns %s", code, raw)
7778
}
7879
err = json.Unmarshal(raw, &an)
@@ -93,7 +94,7 @@ func (c *Client) CreateAlertNotification(ctx context.Context, an AlertNotificati
9394
if raw, code, err = c.post(ctx, "api/alert-notifications", nil, raw); err != nil {
9495
return -1, err
9596
}
96-
if code != 200 {
97+
if code != http.StatusOK {
9798
return -1, fmt.Errorf("HTTP error %d: returns %s", code, raw)
9899
}
99100
result := struct {
@@ -117,7 +118,7 @@ func (c *Client) UpdateAlertNotificationUID(ctx context.Context, an AlertNotific
117118
if raw, code, err = c.put(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid), nil, raw); err != nil {
118119
return err
119120
}
120-
if code != 200 {
121+
if code != http.StatusOK {
121122
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
122123
}
123124
return nil
@@ -137,7 +138,7 @@ func (c *Client) UpdateAlertNotificationID(ctx context.Context, an AlertNotifica
137138
if raw, code, err = c.put(ctx, fmt.Sprintf("api/alert-notifications/%d", id), nil, raw); err != nil {
138139
return err
139140
}
140-
if code != 200 {
141+
if code != http.StatusOK {
141142
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
142143
}
143144
return nil
@@ -154,7 +155,7 @@ func (c *Client) DeleteAlertNotificationUID(ctx context.Context, uid string) err
154155
if raw, code, err = c.delete(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid)); err != nil {
155156
return err
156157
}
157-
if code != 200 {
158+
if code != http.StatusOK {
158159
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
159160
}
160161
return nil
@@ -171,7 +172,7 @@ func (c *Client) DeleteAlertNotificationID(ctx context.Context, id uint) error {
171172
if raw, code, err = c.delete(ctx, fmt.Sprintf("api/alert-notifications/%d", id)); err != nil {
172173
return err
173174
}
174-
if code != 200 {
175+
if code != http.StatusOK {
175176
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
176177
}
177178
return nil

rest-annotation.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78
"net/url"
89
"strconv"
910
"time"
@@ -19,13 +20,17 @@ func (r *Client) CreateAnnotation(ctx context.Context, a CreateAnnotationRequest
1920
raw []byte
2021
resp StatusMessage
2122
err error
23+
code int
2224
)
2325
if raw, err = json.Marshal(a); err != nil {
2426
return StatusMessage{}, errors.Wrap(err, "marshal request")
2527
}
26-
if raw, _, err = r.post(ctx, "api/annotations", nil, raw); err != nil {
28+
if raw, code, err = r.post(ctx, "api/annotations", nil, raw); err != nil {
2729
return StatusMessage{}, errors.Wrap(err, "create annotation")
2830
}
31+
if code != http.StatusOK {
32+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
33+
}
2934
if err = json.Unmarshal(raw, &resp); err != nil {
3035
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
3136
}
@@ -38,13 +43,17 @@ func (r *Client) PatchAnnotation(ctx context.Context, id uint, a PatchAnnotation
3843
raw []byte
3944
resp StatusMessage
4045
err error
46+
code int
4147
)
4248
if raw, err = json.Marshal(a); err != nil {
4349
return StatusMessage{}, errors.Wrap(err, "marshal request")
4450
}
45-
if raw, _, err = r.patch(ctx, fmt.Sprintf("api/annotations/%d", id), nil, raw); err != nil {
51+
if raw, code, err = r.patch(ctx, fmt.Sprintf("api/annotations/%d", id), nil, raw); err != nil {
4652
return StatusMessage{}, errors.Wrap(err, "patch annotation")
4753
}
54+
if code != http.StatusOK {
55+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
56+
}
4857
if err = json.Unmarshal(raw, &resp); err != nil {
4958
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
5059
}
@@ -58,15 +67,19 @@ func (r *Client) GetAnnotations(ctx context.Context, params ...GetAnnotationsPar
5867
err error
5968
resp []AnnotationResponse
6069
requestParams = make(url.Values)
70+
code int
6171
)
6272

6373
for _, p := range params {
6474
p(requestParams)
6575
}
6676

67-
if raw, _, err = r.get(ctx, "api/annotations", requestParams); err != nil {
77+
if raw, code, err = r.get(ctx, "api/annotations", requestParams); err != nil {
6878
return nil, errors.Wrap(err, "get annotations")
6979
}
80+
if code != http.StatusOK {
81+
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
82+
}
7083
if err = json.Unmarshal(raw, &resp); err != nil {
7184
return nil, errors.Wrap(err, "unmarshal response message")
7285
}
@@ -79,11 +92,15 @@ func (r *Client) DeleteAnnotation(ctx context.Context, id uint) (StatusMessage,
7992
raw []byte
8093
err error
8194
resp StatusMessage
95+
code int
8296
)
8397

84-
if raw, _, err = r.delete(ctx, fmt.Sprintf("api/annotations/%d", id)); err != nil {
98+
if raw, code, err = r.delete(ctx, fmt.Sprintf("api/annotations/%d", id)); err != nil {
8599
return StatusMessage{}, errors.Wrap(err, "delete annotation")
86100
}
101+
if code != http.StatusOK {
102+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
103+
}
87104
if err = json.Unmarshal(raw, &resp); err != nil {
88105
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
89106
}

rest-common.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package sdk
2+
3+
import "errors"
4+
5+
var (
6+
ErrNotFound = errors.New("not found")
7+
ErrAlreadyExists = errors.New("already exists")
8+
ErrNotAccessDenied = errors.New("access denied")
9+
ErrNotAuthorized = errors.New("not authorized")
10+
ErrCannotCreate = errors.New("cannot create; see body for details")
11+
)

rest-dashboard.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"context"
2525
"encoding/json"
2626
"fmt"
27+
"net/http"
2728
"net/url"
2829
"strconv"
2930
"strings"
@@ -167,7 +168,7 @@ func (r *Client) getRawDashboard(ctx context.Context, path string) ([]byte, Boar
167168
if raw, code, err = r.get(ctx, fmt.Sprintf("api/dashboards/%s", path), nil); err != nil {
168169
return nil, BoardProperties{}, err
169170
}
170-
if code != 200 {
171+
if code != http.StatusOK {
171172
return nil, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
172173
}
173174
dec := json.NewDecoder(bytes.NewReader(raw))
@@ -251,7 +252,7 @@ func (r *Client) Search(ctx context.Context, params ...SearchParam) ([]FoundBoar
251252
if raw, code, err = r.get(ctx, "api/search", q); err != nil {
252253
return nil, err
253254
}
254-
if code != 200 {
255+
if code != http.StatusOK {
255256
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
256257
}
257258
err = json.Unmarshal(raw, &boards)
@@ -302,13 +303,20 @@ func (r *Client) SetDashboard(ctx context.Context, board Board, params SetDashbo
302303
if raw, code, err = r.post(ctx, "api/dashboards/db", nil, raw); err != nil {
303304
return StatusMessage{}, err
304305
}
305-
if err = json.Unmarshal(raw, &resp); err != nil {
306-
return StatusMessage{}, err
307-
}
308-
if code != 200 {
309-
return resp, fmt.Errorf("HTTP error %d: returns %s", code, *resp.Message)
306+
switch code { // https://grafana.com/docs/grafana/latest/http_api/dashboard/#create--update-dashboard
307+
case http.StatusOK:
308+
err = json.Unmarshal(raw, &resp)
309+
case http.StatusForbidden:
310+
err = fmt.Errorf("database dashboard with uid %q %w", board.UID, ErrNotAccessDenied)
311+
case http.StatusUnauthorized:
312+
err = fmt.Errorf("database dashboard with uid %q %w", board.UID, ErrNotAuthorized)
313+
case http.StatusPreconditionFailed:
314+
err = fmt.Errorf("database dashboard with uid %q %w", board.UID, ErrCannotCreate)
315+
default: // includes http.StatusBadRequest
316+
err = fmt.Errorf("HTTP error %d: returns %s", code, raw)
310317
}
311-
return resp, nil
318+
319+
return resp, err
312320
}
313321

314322
//SetRawDashboardWithParam sends the serialized along with request parameters
@@ -330,7 +338,7 @@ func (r *Client) SetRawDashboardWithParam(ctx context.Context, request RawBoardR
330338
if err = json.Unmarshal(rawResp, &resp); err != nil {
331339
return StatusMessage{}, err
332340
}
333-
if code != 200 {
341+
if code != http.StatusOK {
334342
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, *resp.Message)
335343
}
336344
return resp, nil
@@ -365,13 +373,17 @@ func (r *Client) DeleteDashboard(ctx context.Context, slug string) (StatusMessag
365373
raw []byte
366374
reply StatusMessage
367375
err error
376+
code int
368377
)
369378
if slug, isBoardFromDB = cleanPrefix(slug); !isBoardFromDB {
370379
return StatusMessage{}, errors.New("only database dashboards (with 'db/' prefix in a slug) can be removed")
371380
}
372-
if raw, _, err = r.delete(ctx, fmt.Sprintf("api/dashboards/db/%s", slug)); err != nil {
381+
if raw, code, err = r.delete(ctx, fmt.Sprintf("api/dashboards/db/%s", slug)); err != nil {
373382
return StatusMessage{}, err
374383
}
384+
if code != http.StatusOK {
385+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
386+
}
375387
err = json.Unmarshal(raw, &reply)
376388
return reply, err
377389
}
@@ -383,10 +395,14 @@ func (r *Client) DeleteDashboardByUID(ctx context.Context, uid string) (StatusMe
383395
raw []byte
384396
reply StatusMessage
385397
err error
398+
code int
386399
)
387-
if raw, _, err = r.delete(ctx, fmt.Sprintf("api/dashboards/uid/%s", uid)); err != nil {
400+
if raw, code, err = r.delete(ctx, fmt.Sprintf("api/dashboards/uid/%s", uid)); err != nil {
388401
return StatusMessage{}, err
389402
}
403+
if code != http.StatusOK {
404+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
405+
}
390406
err = json.Unmarshal(raw, &reply)
391407
return reply, err
392408
}

0 commit comments

Comments
 (0)