forked from blacklightcms/recurly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xml.go
332 lines (290 loc) · 7.94 KB
/
xml.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
324
325
326
327
328
329
330
331
332
package recurly
import (
"encoding/json"
"encoding/xml"
"strconv"
"strings"
"time"
"net/url"
)
// NullBool is able to marshal or unmarshal bools to XML in order to differentiate
// between false as a value and false as a zero-value.
type NullBool struct {
value bool
valid bool
}
// NewBool returns NullBool with a valid value of b.
func NewBool(b bool) NullBool {
return NullBool{
value: b,
valid: true,
}
}
// NewBoolPtr returns a new bool from a pointer.
func NewBoolPtr(b *bool) NullBool {
if b == nil {
return NullBool{}
}
return NewBool(*b)
}
// Bool returns the bool value, regardless of validity. Use Value() if
// you need to know whether the value is valid.
func (n NullBool) Bool() bool {
return n.value
}
// BoolPtr returns a pointer to the bool value, or nil if the value is not valid.
func (n NullBool) BoolPtr() *bool {
if n.valid {
return &n.value
}
return nil
}
// Value returns the value of NullBool. The value should only be considered
// valid if ok returns true.
func (n NullBool) Value() (value bool, ok bool) {
return n.value, n.valid
}
// Equal compares the equality of two NullBools.
func (n NullBool) Equal(v NullBool) bool {
return n.value == v.value && n.valid == v.valid
}
// MarshalJSON marshals a bool based on whether valid is true.
func (n NullBool) MarshalJSON() ([]byte, error) {
if n.valid {
return json.Marshal(n.value)
}
return []byte("null"), nil
}
// UnmarshalXML unmarshals an bool properly, as well as marshaling an empty string to nil.
func (n *NullBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v string
if err := d.DecodeElement(&v, &start); err == nil {
if val, err := strconv.ParseBool(v); err == nil {
*n = NewBool(val)
} else {
*n = NullBool{valid: false}
}
}
return nil
}
// MarshalXML marshals NullBools to XML. Otherwise nothing is
// marshaled.
func (n NullBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if n.valid {
return e.EncodeElement(n.value, start)
}
return nil
}
// NullInt is used for properly handling int types that could be null.
type NullInt struct {
value int
valid bool
}
// NewInt returns NullInt with a valid value of i.
func NewInt(i int) NullInt {
return NullInt{value: i, valid: true}
}
// NewIntPtr returns a new bool from a pointer.
func NewIntPtr(i *int) NullInt {
if i == nil {
return NullInt{}
}
return NewInt(*i)
}
// Int returns the int value, regardless of validity. Use Value() if
// you need to know whether the value is valid.
func (n NullInt) Int() int {
return n.value
}
// IntPtr returns a pointer to the int value, or nil if the value is not valid.
func (n NullInt) IntPtr() *int {
if n.valid {
return &n.value
}
return nil
}
// Value returns the value of NullInt. The value should only be considered
// valid if ok returns true.
func (n NullInt) Value() (value int, ok bool) {
return n.value, n.valid
}
// Equal compares the equality of two NullInt.
func (n NullInt) Equal(v NullInt) bool {
return n.value == v.value && n.valid == v.valid
}
// MarshalJSON marshals an int based on whether valid is true.
func (n NullInt) MarshalJSON() ([]byte, error) {
if n.valid {
return json.Marshal(n.value)
}
return []byte("null"), nil
}
// UnmarshalXML unmarshals an int properly, as well as marshaling an empty string to nil.
func (n *NullInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v struct {
Int int `xml:",chardata"`
Nil string `xml:"nil,attr"`
}
if err := d.DecodeElement(&v, &start); err != nil {
return err
} else if strings.EqualFold(v.Nil, "nil") || strings.EqualFold(v.Nil, "true") {
return nil
}
*n = NewInt(v.Int)
return nil
}
// MarshalXML marshals NullInts greater than zero to XML. Otherwise nothing is
// marshaled.
func (n NullInt) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if n.valid {
return e.EncodeElement(n.value, start)
}
return nil
}
// DateTimeFormat is the format Recurly uses to represent datetimes.
const DateTimeFormat = "2006-01-02T15:04:05Z07:00"
// NullTime is used for properly handling time.Time types that could be null.
type NullTime struct {
value time.Time
valid bool
}
// NewTime returns NullTime with a valid value of t. The NullTime value
// will be considered invalid if t.IsZero() returns true.
func NewTime(t time.Time) NullTime {
if t.IsZero() {
return NullTime{}
}
t = t.UTC()
return NullTime{value: t, valid: true}
}
// NewTimePtr returns NullTime from a pointer.
func NewTimePtr(t *time.Time) NullTime {
if t == nil || t.IsZero() {
return NullTime{}
}
return NewTime(*t)
}
// Time returns the time value, regardless of validity. Use Value() if
// you need to know whether the value is valid.
func (n NullTime) Time() time.Time {
return n.value
}
// TimePtr returns a pointer to the time.Time value, or nil if the value is not valid.
func (n NullTime) TimePtr() *time.Time {
if n.valid {
return &n.value
}
return nil
}
// Value returns the value of NullTime. The value should only be considered
// valid if ok returns true.
func (n NullTime) Value() (value time.Time, ok bool) {
return n.value, n.valid
}
// Equal compares the equality of two NullTime.
func (n NullTime) Equal(v NullTime) bool {
return n.value.Equal(v.value) && n.valid == v.valid
}
// UnmarshalXML unmarshals an int properly, as well as marshaling an empty string to nil.
func (n *NullTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v string
if err := d.DecodeElement(&v, &start); err == nil && v != "" {
parsed, err := time.Parse(DateTimeFormat, v)
if err != nil {
return err
}
*n = NewTime(parsed)
}
return nil
}
// MarshalJSON method has to be added here due to embeded interface json marshal issue in Go
// with panic on nil time field
func (n NullTime) MarshalJSON() ([]byte, error) {
if n.valid {
return json.Marshal(n.value)
}
return []byte("null"), nil
}
// MarshalXML marshals times into their proper format. Otherwise nothing is
// marshaled. All times are sent in UTC.
func (n NullTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if n.valid {
return e.EncodeElement(n.String(), start)
}
return nil
}
// String returns a string representation of the time in UTC using the
// DateTimeFormat constant as the format.
func (n NullTime) String() string {
if n.valid {
return n.value.UTC().Format(DateTimeFormat)
}
return ""
}
// NullMarshal can be embedded in structs that are read-only or just should
// never be marshaled
type NullMarshal struct{}
// MarshalXML ensures that nullMarshal doesn't marshal any xml.
func (nm NullMarshal) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return nil
}
// unmarshals href types into strings.
type href url.URL
// UnmarshalXML unmarshals an int properly, as well as marshaling an empty string to nil.
func (h *href) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v struct {
HREF string `xml:"href,attr"`
}
if err := d.DecodeElement(&v, &start); err != nil {
return err
} else if v.HREF == "" {
return nil
}
u, err := url.Parse(v.HREF)
if err != nil {
return err
}
*h = href(*u)
return nil
}
func (h *href) LastPartOfPath() string {
if h.Path == "" {
return ""
}
h.Path = strings.TrimSuffix(h.Path, "/")
idx := strings.LastIndex(h.Path, "/")
if idx == -1 || len(h.Path)-1 == idx {
return ""
}
return h.Path[idx+1:]
}
type hrefInt url.URL
// UnmarshalXML unmarshals an int properly, as well as marshaling an empty string to nil.
func (h *hrefInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v struct {
HREF string `xml:"href,attr"`
}
if err := d.DecodeElement(&v, &start); err != nil {
return err
} else if v.HREF == "" {
return nil
}
u, err := url.Parse(v.HREF)
if err != nil {
return err
}
*h = hrefInt(*u)
return nil
}
func (h *hrefInt) LastPartOfPath() int {
if h.Path == "" {
return 0
}
h.Path = strings.TrimSuffix(h.Path, "/")
idx := strings.LastIndex(h.Path, "/")
if idx == -1 || len(h.Path)-1 == idx {
return 0
}
i, _ := strconv.Atoi(h.Path[idx+1:])
return i
}