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 }