-
Notifications
You must be signed in to change notification settings - Fork 0
/
jsonbox.go
186 lines (165 loc) · 4.38 KB
/
jsonbox.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package jsonbox
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"time"
)
// Errors returned when working with jsonbox package
var (
ErrTODO = errors.New("functionality not implemented")
ErrName = errors.New("invalid name for either BOX_ID or COLLECTION")
)
// NewClient returns a new jsonbox client
func NewClient(baseURL string) (*Client, error) {
url, err := url.Parse(baseURL)
if err != nil {
return nil, errors.New("invalid host url")
}
return &Client{
baseURL: baseURL,
URL: url,
Timeout: time.Second * 10,
}, nil
}
// NameRegExp defines valid BOX_ID or COLLECTION names reges
var NameRegExp = regexp.MustCompile("^[a-zA-Z0-9_]*$")
// Client defines the structure of a jsonbox client
type Client struct {
baseURL string
URL *url.URL
Timeout time.Duration
}
// Create creates or adds a record to a boxId
func (c Client) Create(boxID string, val []byte) ([]byte, error) {
if ok := NameRegExp.MatchString(boxID); !ok {
return nil, ErrName
}
return c.Request(http.MethodPost, boxID, val)
}
// Read reads records for a boxId or boxId with query
func (c Client) Read(boxID string) ([]byte, error) {
return c.Request(http.MethodGet, boxID, nil)
}
// Update updates a boxID record given the record key and new value
func (c Client) Update(boxID string, recordID string, val []byte) ([]byte, error) {
p := boxID + "/" + recordID
return c.Request(http.MethodPut, p, val)
}
// Delete deletes a boxID record given the record Id
func (c Client) Delete(boxID string, recordID string) error {
p := boxID + "/" + recordID
_, err := c.Request(http.MethodDelete, p, nil)
return err
}
// IDs returns all IDs in a BoxID collection
func (c Client) IDs(boxID string) ([]string, error) {
out, err := c.Read(boxID)
if err != nil {
return nil, err
}
var ids []string
metas, err := GetRecordMetas(out)
if err != nil {
return nil, err
}
for _, m := range metas {
ids = append(ids, m.ID)
}
return ids, nil
}
// DeleteAll deletes all records for boxID
func (c Client) DeleteAll(boxID string) error {
out, err := c.Read(boxID)
if err != nil {
return fmt.Errorf("failed to READ record(s) from boxID %s: %v", boxID, err)
}
metas, err := GetRecordMetas(out)
if err != nil {
return fmt.Errorf("failed to READ record IDs from response %s: %v", boxID, err)
}
for _, m := range metas {
err := c.Delete(boxID, m.ID)
if err != nil {
return fmt.Errorf("Failed to delete record %s/%s: %v", boxID, m.ID, err)
}
}
return nil
}
// Request makes a request to json box url
func (c Client) Request(method, urlPath string, dat []byte) ([]byte, error) {
if c.baseURL == "" {
return nil, errors.New("invalid client, create using NewClient")
}
cl := &http.Client{Timeout: c.Timeout}
urlPath = strings.TrimPrefix(urlPath, "/")
urlString := c.URL.String() + "/" + urlPath
var req *http.Request
var err error
switch method {
case http.MethodPost, http.MethodPut:
if dat == nil {
return nil, errors.New("missing request payload")
}
req, err = http.NewRequest(method, urlString, bytes.NewBuffer(dat))
req.Header.Set("content-type", "application/json")
default:
req, err = http.NewRequest(method, urlString, nil)
}
if err != nil {
return nil, err
}
resp, err := cl.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %v", err)
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("%s - %s", resp.Status, out)
}
return out, nil
}
// Meta defines metadata for json stored
type Meta struct {
ID string `json:"_id"`
CreatedOn string `json:"_createdOn"`
}
// GetRecordID returns the id of a record, or the first ID if multiple records
func GetRecordID(dat []byte) (string, error) {
fmt.Printf("Dat : %s", dat)
multipleRecords := bytes.HasPrefix(dat, []byte("["))
if multipleRecords {
mm, err := GetRecordMetas(dat)
if err != nil {
return "", err
}
return mm[0].ID, nil
}
var m Meta
if err := json.Unmarshal(dat, &m); err != nil {
return "", err
}
fmt.Printf("Meta : %s", m)
return m.ID, nil
}
// GetRecordMetas returns a list of record Metas, fails for single record
func GetRecordMetas(dat []byte) ([]Meta, error) {
var mm []Meta
if err := json.Unmarshal(dat, &mm); err != nil {
return nil, err
}
if len(mm) == 0 {
return nil, errors.New("no records found")
}
return mm, nil
}