Skip to content

Commit

Permalink
Expand address.Format with validation helpers. Add tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
bojanz committed Oct 9, 2020
1 parent d99feaa commit 0dbfd5e
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# address [![Build Status](https://travis-ci.org/bojanz/address.png?branch=master)](https://travis-ci.org/bojanz/address) [![Go Report Card](https://goreportcard.com/badge/github.com/bojanz/address)](https://goreportcard.com/report/github.com/bojanz/address) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bojanz/address)](https://pkg.go.dev/github.com/bojanz/address)
# address [![Build Status](https://travis-ci.com/bojanz/address.png?branch=master)](https://travis-ci.com/bojanz/address) [![Go Report Card](https://goreportcard.com/badge/github.com/bojanz/address)](https://goreportcard.com/report/github.com/bojanz/address) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bojanz/address)](https://pkg.go.dev/github.com/bojanz/address)

Provides address validation and formatting.
32 changes: 32 additions & 0 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package address

import "regexp"

// Address represents an address.
type Address struct {
Line1 string
Expand Down Expand Up @@ -49,6 +51,36 @@ func (f Format) IsRequired(field Field) bool {
return false
}

// CheckRequired checks whether a required field is valid (non-blank).
//
// Non-required fields are considered valid even if they're blank.
func (f Format) CheckRequired(field Field, value string) bool {
required := f.IsRequired(field)
return !required || (required && value != "")
}

// CheckRegion checks whether the given region is valid.
//
// An empty region is considered valid.
func (f Format) CheckRegion(region string) bool {
if region == "" || len(f.Regions) == 0 {
return true
}
_, ok := f.Regions[region]
return ok
}

// CheckPostalCode checks whether the given postal code is valid.
//
// An empty postal code is considered valid.
func (f Format) CheckPostalCode(postalCode string) bool {
if postalCode == "" || f.PostalCodePattern == "" {
return true
}
rx := regexp.MustCompile(f.PostalCodePattern)
return rx.MatchString(postalCode)
}

// GetFormats returns all known address formats, keyed by country code.
//
// The ZZ address format represents the generic fallback.
Expand Down
161 changes: 161 additions & 0 deletions address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) 2020 Bojan Zivanovic and contributors
// SPDX-License-Identifier: MIT

package address_test

import (
"reflect"
"testing"

"github.com/bojanz/address"
)

func TestAddress_IsEmpty(t *testing.T) {
a := address.Address{}
if !a.IsEmpty() {
t.Errorf("expected address to be empty.")
}

a = address.Address{Locality: "Belgrade"}
if !a.IsEmpty() {
t.Errorf("expected address to be empty.")
}

a = address.Address{CountryCode: "RS"}
if a.IsEmpty() {
t.Errorf("expected address to not be empty.")
}
}

func TestFormat_IsRequired(t *testing.T) {
format := address.GetFormat("RS")
got := format.IsRequired(address.FieldLine1)
if !got {
t.Errorf("expected FieldLine1 to be required.")
}

got = format.IsRequired(address.FieldLine2)
if got {
t.Errorf("expected FieldLine2 to not be required.")
}
}

func TestFormat_CheckRequired(t *testing.T) {
tests := []struct {
field address.Field
value string
want bool
}{
// Required and empty.
{address.FieldLine1, "", false},
// Optional and empty.
{address.FieldLine2, "", true},
// Required and non-empty.
{address.FieldLocality, "Belgrade", true},
// Optional and non-empty.
{address.FieldPostalCode, "11000", true},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
format := address.GetFormat("RS")
got := format.CheckRequired(tt.field, tt.value)
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}

func TestFormat_CheckRegion(t *testing.T) {
tests := []struct {
countryCode string
region string
want bool
}{
// Empty value.
{"US", "", true},
// Valid ISO code.
{"US", "CA", true},
// Invalid ISO code.
{"US", "California", false},
// Country with no predefined regions.
{"RS", "Vojvodina", true},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
format := address.GetFormat(tt.countryCode)
got := format.CheckRegion(tt.region)
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}

func TestFormat_CheckPostalCode(t *testing.T) {
tests := []struct {
countryCode string
postalCode string
want bool
}{
// Empty value.
{"FR", "", true},
// Valid postal code.
{"FR", "75002", true},
// Invalid postal code.
{"FR", "INVALID", false},
// Country with no predefined pattern.
{"AG", "AG123", true},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
format := address.GetFormat(tt.countryCode)
got := format.CheckPostalCode(tt.postalCode)
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}

func TestGetFormat(t *testing.T) {
// Existing format.
got := address.GetFormat("RS")
want := address.Format{
Layout: "%1\n%2\n%P %L",
Required: []address.Field{address.FieldLine1, address.FieldLocality},
PostalCodePattern: "\\d{5,6}",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}

// Generic format.
generic := address.GetFormat("ZZ")
want = address.Format{
Layout: "%1\n%2\n%L",
Required: []address.Field{address.FieldLine1, address.FieldLocality},
}
if !reflect.DeepEqual(generic, want) {
t.Errorf("got %v, want %v", generic, want)
}

// Non-existent format.
got = address.GetFormat("IC")
if !reflect.DeepEqual(got, generic) {
t.Errorf("got %v, want %v", got, generic)
}
}

func TestGetFormats(t *testing.T) {
formats := address.GetFormats()
for _, countryCode := range []string{"ZZ", "RS"} {
_, ok := formats[countryCode]
if !ok {
t.Errorf("no %v address format found.", countryCode)
}
}
}

0 comments on commit 0dbfd5e

Please sign in to comment.