-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
107 lines (93 loc) · 2.29 KB
/
client.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
package tetrio
import (
"encoding/json"
"fmt"
"io"
"strconv"
"time"
)
type Response struct {
Success bool `json:"success"`
Error json.RawMessage `json:"error"`
Data json.RawMessage `json:"data"`
Cache CacheInfo `json:"cache"`
}
type CacheInfo struct {
At CacheTime `json:"cached_at"`
Until CacheTime `json:"cached_until"`
Status string `json:"status"`
}
func (c CacheInfo) Duration() time.Duration {
return c.Until.Sub(c.At.Time)
}
type CacheTime struct {
time.Time
}
func (ct *CacheTime) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}
t, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
ct.Time = time.UnixMilli(t)
return nil
}
func (ct CacheTime) MarshalJSON() ([]byte, error) {
t := ct.UnixMilli()
str := strconv.FormatInt(t, 10)
return []byte(str), nil
}
type StatusError struct {
Code int
Content []byte
}
func (e StatusError) Error() string {
return fmt.Sprintf("tetrio: non 200 status, %d: %q...", e.Code, e.Content[:32])
}
type Error string // The API returns string errors so... not much can be done.
func (e Error) Error() string {
return fmt.Sprintf("tetrio: API error: %#q", string(e))
}
// The returned responses "data" field is *usually* an object with a single key
// that contains the actual data, this function removes that unnecessary
// wrapping.
func unwrapResponse(data json.RawMessage) json.RawMessage {
var m map[string]json.RawMessage
err := json.Unmarshal(data, &m)
if err == nil && len(m) == 1 {
// Looks weird but this works and is idiomatic.
for _, v := range m {
return v
}
}
// Error or not a single keyed wrapper object. If there was an error then it
// probably wasn't even an object in that case.
return data
}
func parseResponse(resp io.Reader) (*Response, error) {
var result Response
err := json.NewDecoder(resp).Decode(&result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, Error(result.Error)
}
return &result, nil
}
func parseDataFromJSON[T any](jsonReader io.Reader) (T, error) {
var z T
resp, err := parseResponse(jsonReader)
if err != nil {
return z, err
}
unwrapped := unwrapResponse(resp.Data)
var result T
err = json.Unmarshal(unwrapped, &result)
if err != nil {
return z, err
}
return result, nil
}