-
Notifications
You must be signed in to change notification settings - Fork 103
/
types.go
323 lines (282 loc) · 7.92 KB
/
types.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package twilio
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/ttacon/libphonenumber"
)
type PhoneNumber string
var ErrEmptyNumber = errors.New("twilio: The provided phone number was empty")
// NewPhoneNumber parses the given value as a phone number or returns an error
// if it cannot be parsed as one. If a phone number does not begin with a plus
// sign, we assume it's a US national number. Numbers are stored in E.164
// format.
func NewPhoneNumber(pn string) (PhoneNumber, error) {
if len(pn) == 0 {
return "", ErrEmptyNumber
}
num, err := libphonenumber.Parse(pn, "US")
// Add some better error messages - the ones in libphonenumber are generic
switch {
case err == libphonenumber.ErrNotANumber:
return "", fmt.Errorf("twilio: Invalid phone number: %s", pn)
case err == libphonenumber.ErrInvalidCountryCode:
return "", fmt.Errorf("twilio: Invalid country code for number: %s", pn)
case err != nil:
return "", err
}
return PhoneNumber(libphonenumber.Format(num, libphonenumber.E164)), nil
}
// Friendly returns a friendly international representation of the phone
// number, for example, "+14105554092" is returned as "+1 410-555-4092". If the
// phone number is not in E.164 format, we try to parse it as a US number. If
// we cannot parse it as a US number, it is returned as is.
func (pn PhoneNumber) Friendly() string {
num, err := libphonenumber.Parse(string(pn), "US")
if err != nil {
return string(pn)
}
return libphonenumber.Format(num, libphonenumber.INTERNATIONAL)
}
// Local returns a friendly national representation of the phone number, for
// example, "+14105554092" is returned as "(410) 555-4092". If the phone number
// is not in E.164 format, we try to parse it as a US number. If we cannot
// parse it as a US number, it is returned as is.
func (pn PhoneNumber) Local() string {
num, err := libphonenumber.Parse(string(pn), "US")
if err != nil {
return string(pn)
}
return libphonenumber.Format(num, libphonenumber.NATIONAL)
}
// A uintStr is sent back from Twilio as a str, but should be parsed as a uint.
type uintStr uint
type Segments uintStr
type NumMedia uintStr
func (seg *uintStr) UnmarshalJSON(b []byte) error {
s := new(string)
if err := json.Unmarshal(b, s); err != nil {
return err
}
u, err := strconv.ParseUint(*s, 10, 64)
if err != nil {
return err
}
*seg = uintStr(u)
return nil
}
func (seg *Segments) UnmarshalJSON(b []byte) (err error) {
u := new(uintStr)
if err = json.Unmarshal(b, u); err != nil {
return
}
*seg = Segments(*u)
return
}
func (n *NumMedia) UnmarshalJSON(b []byte) (err error) {
u := new(uintStr)
if err = json.Unmarshal(b, u); err != nil {
return
}
*n = NumMedia(*u)
return
}
// TwilioTime can parse a timestamp returned in the Twilio API and turn it into
// a valid Go Time struct.
type TwilioTime struct {
Time time.Time
Valid bool
}
// NewTwilioTime returns a TwilioTime instance. val should be formatted using
// the TimeLayout.
func NewTwilioTime(val string) *TwilioTime {
t, err := time.Parse(TimeLayout, val)
if err == nil {
return &TwilioTime{Time: t, Valid: true}
} else {
return &TwilioTime{}
}
}
// Epoch is a time that predates the formation of the company (January 1,
// 2005). Use this for start filters when you don't want to filter old results.
var Epoch = time.Date(2005, 1, 1, 0, 0, 0, 0, time.UTC)
// HeatDeath is a sentinel time that should outdate the extinction of the
// company. Use this with GetXInRange calls when you don't want to specify an
// end date. Feel free to adjust this number in the year 5960 or so.
var HeatDeath = time.Date(6000, 1, 1, 0, 0, 0, 0, time.UTC)
// The reference time, as it appears in the Twilio API.
const TimeLayout = "Mon, 2 Jan 2006 15:04:05 -0700"
// Format expected by Twilio for searching date ranges. Monitor and other API's
// offer better date search filters
const APISearchLayout = "2006-01-02"
func (t *TwilioTime) UnmarshalJSON(b []byte) error {
s := new(string)
if err := json.Unmarshal(b, s); err != nil {
return err
}
if s == nil || *s == "null" || *s == "" {
t.Valid = false
return nil
}
tim, err := time.Parse(time.RFC3339, *s)
if err != nil {
tim, err = time.Parse(TimeLayout, *s)
if err != nil {
return err
}
}
*t = TwilioTime{Time: tim, Valid: true}
return nil
}
func (tt *TwilioTime) MarshalJSON() ([]byte, error) {
if tt.Valid == false {
return []byte("null"), nil
}
b, err := json.Marshal(tt.Time)
if err != nil {
return []byte{}, err
}
return b, nil
}
var symbols = map[string]string{
"USD": "$",
"GBP": "£",
"JPY": "¥",
"MXN": "$",
"CHF": "CHF",
"CAD": "$",
"CNY": "¥",
"SGD": "$",
"EUR": "€",
}
// Price flips the sign of the amount and prints it with a currency symbol for
// the given unit.
func price(unit string, amount string) string {
if len(amount) == 0 {
return amount
}
if amount[0] == '-' {
amount = amount[1:]
} else {
amount = "-" + amount
}
for strings.Contains(amount, ".") && strings.HasSuffix(amount, "0") {
amount = amount[:len(amount)-1]
}
if strings.HasSuffix(amount, ".") {
amount = amount[:len(amount)-1]
}
unit = strings.ToUpper(unit)
if sym, ok := symbols[unit]; ok {
return sym + amount
} else {
if unit == "" {
return amount
}
return unit + " " + amount
}
}
type TwilioDuration time.Duration
func (td *TwilioDuration) UnmarshalJSON(b []byte) error {
s := new(string)
if err := json.Unmarshal(b, s); err != nil {
return err
}
if *s == "null" || *s == "" {
*td = 0
return nil
}
i, err := strconv.ParseInt(*s, 10, 64)
if err != nil {
return err
}
*td = TwilioDuration(i) * TwilioDuration(time.Second)
return nil
}
func (td TwilioDuration) String() string {
return time.Duration(td).String()
}
type AnsweredBy string
const AnsweredByHuman = AnsweredBy("human")
const AnsweredByMachine = AnsweredBy("machine")
type NullAnsweredBy struct {
Valid bool
AnsweredBy AnsweredBy
}
// The status of the message (accepted, queued, etc).
// For more information , see https://www.twilio.com/docs/api/rest/message
type Status string
func (s Status) Friendly() string {
switch s {
case StatusInProgress:
return "In Progress"
case StatusNoAnswer:
return "No Answer"
default:
return strings.Title(string(s))
}
}
// Values has the methods of url.Values, but can decode JSON from the
// response_headers field of an Alert.
type Values struct {
url.Values
}
func (h *Values) UnmarshalJSON(b []byte) error {
s := new(string)
if err := json.Unmarshal(b, s); err != nil {
return err
}
vals, err := url.ParseQuery(*s)
if err != nil {
return err
}
*h = Values{url.Values{}}
for k, arr := range vals {
for _, val := range arr {
h.Add(k, val)
}
}
return nil
}
const StatusAccepted = Status("accepted")
const StatusDelivered = Status("delivered")
const StatusReceiving = Status("receiving")
const StatusReceived = Status("received")
const StatusSending = Status("sending")
const StatusSent = Status("sent")
const StatusUndelivered = Status("undelivered")
// Call statuses
const StatusBusy = Status("busy")
const StatusCanceled = Status("canceled")
const StatusCompleted = Status("completed")
const StatusInProgress = Status("in-progress")
const StatusNoAnswer = Status("no-answer")
const StatusRinging = Status("ringing")
// Shared
const StatusFailed = Status("failed")
const StatusQueued = Status("queued")
const StatusActive = Status("active")
const StatusSuspended = Status("suspended")
const StatusClosed = Status("closed")
// A log level returned for an Alert.
type LogLevel string
const LogLevelError = LogLevel("error")
const LogLevelWarning = LogLevel("warning")
const LogLevelNotice = LogLevel("notice")
const LogLevelDebug = LogLevel("debug")
func (l LogLevel) Friendly() string {
return capitalize(string(l))
}
// capitalize the first letter in s
func capitalize(s string) string {
r, l := utf8.DecodeRuneInString(s)
b := make([]byte, l)
utf8.EncodeRune(b, unicode.ToTitle(r))
return strings.Join([]string{string(b), s[l:]}, "")
}