Skip to content

Commit ea98ee6

Browse files
pararangMuhammad Ikhsanactions-user
authored
feat(delete): implement delete interface (#15)
* feat(delete): implement delete interface * chore: Updated coverage badge. Co-authored-by: Muhammad Ikhsan <muhammad.ikhsan@efishery.com> Co-authored-by: GitHub Action <action@github.com>
1 parent 5d0d021 commit ea98ee6

File tree

3 files changed

+148
-17
lines changed

3 files changed

+148
-17
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![Coverage](https://img.shields.io/badge/Coverage-86.3%25-brightgreen)
1+
![Coverage](https://img.shields.io/badge/Coverage-85.7%25-brightgreen)
22
# gostein
33

44
[Stein](https://steinhq.com/) API wrapper for Go.
@@ -80,6 +80,7 @@ resp, err = steinClient.Add("sheet1", rows...)
8080
...
8181
```
8282
### Update
83+
The update operation return string that indicate ranges of the sheet that was updated. Example: `Sheet1!A3:B3`.
8384
#### Update single row
8485
`Limit 1` indicate to update only the first row that match the `Condition`.
8586
```go
@@ -113,11 +114,40 @@ resp, err := sc.Update("Sheet1", UpdateParams{
113114
...
114115
```
115116
> :warning: **If `Limit` is not set, all rows those match the `Condition` will be updated.**
117+
### Delete
118+
The delete operation return int64 that indicate the number of rows that was deleted.
119+
#### Delete single row
120+
`Limit 1` indicate to update only the first row that match the `Condition`.
121+
```go
122+
...
123+
resp, err := sc.Delete("Sheet1", DeleteParams{
124+
Condition: map[string]string{
125+
"column_1": "if_has_this_value",
126+
},
127+
Limit: 1,
128+
})
129+
// handle err and do something with resp
130+
...
131+
```
132+
#### Update with multiple condition
133+
All `Condition` will be translated to `AND` condition.
134+
```go
135+
...
136+
resp, err := sc.Delete("Sheet1", DeleteParams{
137+
Condition: map[string]string{
138+
"column_1": "if_has_this_value",
139+
"column_3": "and_if_has_this_value",
140+
},
141+
})
142+
// handle err and do something with resp
143+
...
144+
```
145+
> :warning: **If `Limit` is not set, all rows those match the `Condition` will be deleted.**
116146
117147
## TODO
118148
- [x] Read data (https://docs.steinhq.com/read-data)
119149
- [x] Read data with conditions (https://docs.steinhq.com/search-data)
120150
- [x] Add data (https://docs.steinhq.com/add-rows)
121151
- [x] Update data (https://docs.steinhq.com/update-rows)
122-
- [ ] Delete data (https://docs.steinhq.com/delete-rows)
152+
- [x] Delete data (https://docs.steinhq.com/delete-rows)
123153
- [ ] Authentication (https://docs.steinhq.com/authentication)

stein.go

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ import (
1010
"strings"
1111
)
1212

13+
// UpsertResponse is the response from the either Update or Insert/add operation
14+
type UpsertResponse struct {
15+
UpdatedRange string `json:"updatedRange"`
16+
}
17+
18+
// GetParams is the parameters for the read operations: Get or search
1319
type GetParams struct {
1420
Offset int64
1521
Limit int64
16-
Search map[string]string
22+
Condition map[string]string
1723
}
1824

1925
// builds the query string from the given params with query string escaping
@@ -27,8 +33,8 @@ func (gp GetParams) queryString() string {
2733
queryString = queryString + fmt.Sprintf("limit=%d&", gp.Limit)
2834
}
2935

30-
if len(gp.Search) > 0 {
31-
jsonSearch, err := json.Marshal(gp.Search)
36+
if len(gp.Condition) > 0 {
37+
jsonSearch, err := json.Marshal(gp.Condition)
3238
if err != nil {
3339
return ""
3440
}
@@ -41,21 +47,25 @@ func (gp GetParams) queryString() string {
4147
return removeSuffix(queryString, "&")
4248
}
4349

44-
type WriteResponse struct {
45-
UpdatedRange string `json:"updatedRange"`
46-
}
47-
50+
// UpdateParams is the parameters for the update operation
4851
type UpdateParams struct {
4952
Condition map[string]string `json:"condition"`
5053
Set map[string]string `json:"set"`
5154
Limit int64 `json:"limit,omitempty"`
5255
}
5356

57+
// DeleteParams is the parameters for the delete operation
58+
type DeleteParams struct {
59+
Condition map[string]string `json:"condition"`
60+
Limit int64 `json:"limit,omitempty"`
61+
}
62+
5463
// Interface is the interface for the stein client
5564
type Interface interface {
5665
Get(sheet string, params GetParams) ([]map[string]interface{}, error)
57-
Add(sheet string, rows ...map[string]interface{}) (WriteResponse, error)
58-
Update(sheet string, params UpdateParams) (WriteResponse, error)
66+
Add(sheet string, rows ...map[string]interface{}) (UpsertResponse, error)
67+
Update(sheet string, params UpdateParams) (UpsertResponse, error)
68+
Delete(sheet string, params DeleteParams) (countDeletedRows int64, err error)
5969
}
6070

6171
type stein struct {
@@ -113,9 +123,9 @@ func (s *stein) Get(sheet string, params GetParams) ([]map[string]interface{}, e
113123
return data, nil
114124
}
115125

116-
func (s *stein) Add(sheet string, rows ...map[string]interface{}) (WriteResponse, error) {
126+
func (s *stein) Add(sheet string, rows ...map[string]interface{}) (UpsertResponse, error) {
117127
var (
118-
result WriteResponse
128+
result UpsertResponse
119129
resource = fmt.Sprintf("%s/%s", s.url, removePrefix(sheet, "/"))
120130
)
121131

@@ -143,9 +153,9 @@ func (s *stein) Add(sheet string, rows ...map[string]interface{}) (WriteResponse
143153
return result, nil
144154
}
145155

146-
func (s *stein) Update(sheet string, params UpdateParams) (WriteResponse, error) {
156+
func (s *stein) Update(sheet string, params UpdateParams) (UpsertResponse, error) {
147157
var (
148-
result WriteResponse
158+
result UpsertResponse
149159
resource = fmt.Sprintf("%s/%s", s.url, removePrefix(sheet, "/"))
150160
)
151161

@@ -172,8 +182,46 @@ func (s *stein) Update(sheet string, params UpdateParams) (WriteResponse, error)
172182

173183
err = s.decodeJSON(resp.Body, &result)
174184
if err != nil {
175-
return WriteResponse{}, ErrDecodeJSON{Err: err}
185+
return UpsertResponse{}, ErrDecodeJSON{Err: err}
176186
}
177187

178188
return result, nil
179189
}
190+
191+
func (s *stein) Delete(sheet string, params DeleteParams) (countDeletedRows int64, err error) {
192+
var (
193+
result = struct {
194+
CountDeletedRows int64 `json:"clearedRowsCount"`
195+
}{}
196+
197+
resource = fmt.Sprintf("%s/%s", s.url, removePrefix(sheet, "/"))
198+
)
199+
200+
payload, err := json.Marshal(params)
201+
if err != nil {
202+
return countDeletedRows, err
203+
}
204+
205+
req, err := http.NewRequest(http.MethodDelete, resource, bytes.NewBuffer(payload))
206+
if err != nil {
207+
return countDeletedRows, err
208+
}
209+
req.Header.Set("Content-Type", "application/json")
210+
211+
resp, err := s.httpClient.Do(req)
212+
if err != nil {
213+
return countDeletedRows, err
214+
}
215+
defer resp.Body.Close()
216+
217+
if resp.StatusCode != http.StatusOK {
218+
return countDeletedRows, ErrNot2XX{StatusCode: resp.StatusCode}
219+
}
220+
221+
err = s.decodeJSON(resp.Body, &result)
222+
if err != nil {
223+
return countDeletedRows, ErrDecodeJSON{Err: err}
224+
}
225+
226+
return result.CountDeletedRows, nil
227+
}

stein_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestSearchParams_queryString(t *testing.T) {
7878
params := GetParams{
7979
Offset: 20,
8080
Limit: 10,
81-
Search: map[string]string{
81+
Condition: map[string]string{
8282
"column_1": "value_column_1",
8383
"column_2": "value_column_2",
8484
},
@@ -205,3 +205,56 @@ func Test_stein_Update(t *testing.T) {
205205
}
206206
})
207207
}
208+
209+
func Test_stein_Delete(t *testing.T) {
210+
t.Run("should return the correct response", func(t *testing.T) {
211+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
212+
jsonBody := `{"clearedRowsCount": 345}`
213+
214+
w.Header().Set("Content-Type", "application/json")
215+
w.WriteHeader(http.StatusOK)
216+
_, _ = w.Write([]byte(jsonBody))
217+
}))
218+
defer ts.Close()
219+
220+
sc := New(ts.URL, nil)
221+
resp, err := sc.Delete("Sheet1", DeleteParams{})
222+
if err != nil {
223+
t.Errorf("Error: %v", err)
224+
}
225+
226+
assert.Equal(t, int64(345), resp)
227+
})
228+
229+
t.Run("should return error if http code not 2xx", func(t *testing.T) {
230+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
231+
w.WriteHeader(http.StatusForbidden)
232+
}))
233+
defer ts.Close()
234+
235+
sc := New(ts.URL, nil)
236+
_, err := sc.Delete("sheetname", DeleteParams{})
237+
assert.NotNil(t, err)
238+
if !errors.As(err, &ErrNot2XX{}) {
239+
t.Errorf("Expected ErrNot2XX, got %v", err)
240+
}
241+
})
242+
243+
t.Run("should return error on failed decode the response", func(t *testing.T) {
244+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
245+
jsonBody := `bad json`
246+
247+
w.Header().Set("Content-Type", "application/json")
248+
w.WriteHeader(http.StatusOK)
249+
_, _ = w.Write([]byte(jsonBody))
250+
}))
251+
defer ts.Close()
252+
253+
sc := New(ts.URL, nil)
254+
_, err := sc.Delete("/sheetname", DeleteParams{})
255+
assert.NotNil(t, err)
256+
if !errors.As(err, &ErrDecodeJSON{}) {
257+
t.Errorf("Expected ErrDecode, got %v", err)
258+
}
259+
})
260+
}

0 commit comments

Comments
 (0)