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
|
package lulu
import (
"encoding/json"
"fmt"
email "net/mail"
"regexp"
)
//go:generate go run github.com/yawnak/string-enumer -t ShippingLevel -t Title --text -o ./ship_gen.go .
var phoneExpr = regexp.MustCompile(`^\+?[\d\s\-.\/()]{8,20}$`)
// ShippingLevel is the quality/speed with which a package is shipped.
type ShippingLevel string
const (
Mail ShippingLevel = "MAIL" // Slowest ship method. Depending on the destination, tracking might not be available.
PriorityMail ShippingLevel = "PRIORITY_MAIL" // Priority mail shipping
Ground ShippingLevel = "GROUND" // Courier based shipping using ground transportation in the US.
Expedited ShippingLevel = "EXPEDITED" // Expedited (2nd day) delivery via air mail or equivalent.
Express ShippingLevel = "EXPRESS" // Overnight delivery. Fastest shipping available.
)
type ShippingAddress struct {
Country string // ISO 3166-2 country code
State string // 2 or 3 letter state codes (officially called ISO-3166-2 subdivision codes). They are required for some countries (e.g. US, MX, CA, AU)
City string
Street1 string // First address line
Street2 string // Second address line
PostCode string // Required for most countries
IsBusiness bool // Only relevant for US addresses. Some US carriers don't deliver to business-addresses on Saturday.
Name string // Full name of the person, including first and last name.
Title Title
Organization string // Name of an organization. Required if no person name is given.
Email *email.Address // Shipping carriers require an email address for notifications or handling delivery issues. If no email is given, the default email in the user profile will be used.
Phone PhoneNumber // Shipping carriers require a phone number for handling delivery issues. If no phone number is given, the default in the API user profile will be used.
TaxId string // The recipient’s tax identification number. Required for shipping addresses to Brazil, Chile, and Mexico.
}
// They use "country" in some places, "country_code" in others, etc.
// This is the union of all of them.
type shippingAddress struct {
Country string `json:"country"`
CountryCode string `json:"country_code"`
State string `json:"state"`
StateCode string `json:"state_code"`
City string `json:"city"`
Street1 string `json:"street1"`
Street2 string `json:"street2"`
PostCode string `json:"postcode"`
IsBusiness bool `json:"is_business"`
Name string `json:"name"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Title Title `json:"title"`
Organization string `json:"organization"`
Email string `json:"email"`
Phone PhoneNumber `json:"phone_number"`
TaxId string `json:"recipient_tax_id"`
}
func (a *ShippingAddress) UnmarshalJSON(data []byte) error {
s := string(data)
var alias shippingAddress
if err := json.Unmarshal(data, &alias); err != nil {
return err
}
if country, both := either(alias.Country, alias.CountryCode); !both {
a.Country = country
} else {
return fmt.Errorf(`address contains both "country" and "country_code": %q`, s)
}
if state, both := either(alias.State, alias.StateCode); !both {
a.State = state
} else {
return fmt.Errorf(`address contains both "state" and "state_code": %q`, s)
}
a.City = alias.City
a.Street1 = alias.Street1
a.Street2 = alias.Street2
a.PostCode = alias.PostCode
a.IsBusiness = alias.IsBusiness
hasName := alias.Name != ""
hasFirst := alias.FirstName != ""
hasLast := alias.LastName != ""
if hasName && (hasFirst || hasLast) {
return fmt.Errorf(`address contains both "name" and {"first_name", "last_name"}: %q`, s)
} else if hasName {
a.Name = alias.Name
} else if hasFirst && hasLast {
a.Name = fmt.Sprintf("%s %s", alias.FirstName, alias.LastName)
} else if hasLast {
a.Name = alias.LastName
} else if hasFirst {
a.Name = alias.FirstName
}
a.Title = alias.Title
a.Organization = alias.Organization
if alias.Email != "" {
addr, err := email.ParseAddress(alias.Email)
if err != nil {
return err
}
a.Email = addr
}
a.Phone = alias.Phone
a.TaxId = alias.TaxId
return nil
}
func either(a, b string) (string, bool) {
if a != "" && b != "" {
return a, true
} else if a != "" {
return a, false
}
return b, false
}
type Title string
const (
Mr Title = "MR"
Miss Title = "MISS"
Mrs Title = "MRS"
Ms Title = "MS"
Dr Title = "DR"
)
type PhoneNumber string
func ParsePhoneNumber(s string) (PhoneNumber, error) {
if phoneExpr.MatchString(s) {
return PhoneNumber(s), nil
}
return "", fmt.Errorf("malformed phone number %q; must fit pattern `%s`", s, phoneExpr.String())
}
// MustParsePhoneNumber is like ParsePhoneNumber but panics if the phone
// number cannot be parsed.
func MustParsePhoneNumber(s string) PhoneNumber {
n, err := ParsePhoneNumber(s)
if err != nil {
panic(fmt.Sprintf("lulu: ParsePhoneNumber(%q): %v", s, err))
}
return n
}
func (n *PhoneNumber) UnmarshalText(text []byte) error {
pn, err := ParsePhoneNumber(string(text))
if err != nil {
return err
}
*n = pn
return nil
}
|