diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2026-05-11 16:45:09 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2026-05-11 16:45:09 -0400 |
| commit | 329257be8d9fb05d3dcea49823acea0f878ed52c (patch) | |
| tree | 8d51639a3fd9184e1c56fa0f11dc614230fe7d38 | |
| parent | 6f2c582f691c6984d5296b714ae41477a102a77b (diff) | |
| download | lulu-329257be8d9fb05d3dcea49823acea0f878ed52c.zip | |
validate email and phone number
| -rw-r--r-- | cost.go | 12 | ||||
| -rw-r--r-- | cost_test.go | 8 | ||||
| -rw-r--r-- | ship.go | 84 |
3 files changed, 72 insertions, 32 deletions
@@ -19,12 +19,12 @@ type PrintJobCostLineItem struct { } type printJobCostReqShipAddr struct { - City string `json:"city"` - Country string `json:"country_code"` - PostCode string `json:"postcode"` - State string `json:"state_code"` - Street1 string `json:"street1"` - Phone string `json:"phone_number"` + City string `json:"city"` + Country string `json:"country_code"` + PostCode string `json:"postcode"` + State string `json:"state_code"` + Street1 string `json:"street1"` + Phone PhoneNumber `json:"phone_number"` } // PrintJobCost is the response from /print-job-cost-calculations/. diff --git a/cost_test.go b/cost_test.go index 1abe63c..15b2fe1 100644 --- a/cost_test.go +++ b/cost_test.go @@ -35,7 +35,7 @@ var printJobCostReqSample = printJobCostReq{ PostCode: "23552", State: "", Street1: "Holstenstr. 40", - Phone: "844-212-0689", + Phone: MustParsePhoneNumber("844-212-0689"), }, Express, } @@ -45,7 +45,7 @@ var printJobCostSample = PrintJobCost{ City: "Lübeck", PostCode: "23552", Street1: "Holstenstr. 40", - Phone: "844-212-0689", + Phone: MustParsePhoneNumber("844-212-0689"), State: "", Country: "DE", IsBusiness: false, @@ -143,7 +143,7 @@ func TestPrintJobCost(t *testing.T) { PostCode: "23552", State: "", Street1: "Holstenstr. 40", - Phone: "844-212-0689", + Phone: MustParsePhoneNumber("844-212-0689"), } shiplvl := printJobCostReqSample.ShipOpt cost, err := c.PrintJobCost(items, addr, shiplvl) @@ -151,7 +151,7 @@ func TestPrintJobCost(t *testing.T) { require.Equal(t, "Lübeck", cost.Addr.City) require.Equal(t, "23552", cost.Addr.PostCode) require.Equal(t, "Holstenstr. 40", cost.Addr.Street1) - require.Equal(t, "844-212-0689", cost.Addr.Phone) + require.Equal(t, MustParsePhoneNumber("844-212-0689"), cost.Addr.Phone) require.Empty(t, cost.Addr.State) require.Equal(t, "DE", cost.Addr.Country) require.False(t, cost.Addr.IsBusiness) @@ -3,10 +3,14 @@ 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 @@ -28,32 +32,32 @@ type ShippingAddress struct { 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 string // 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 string // 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. + 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 string `json:"phone_number"` - TaxId string `json:"recipient_tax_id"` + 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 { @@ -98,7 +102,15 @@ func (a *ShippingAddress) UnmarshalJSON(data []byte) error { a.Title = alias.Title a.Organization = alias.Organization - a.Email = alias.Email + + 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 @@ -123,3 +135,31 @@ const ( 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 +} |