address

package module
v0.6.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2021 License: Apache-2.0 Imports: 17 Imported by: 0

README

Address

GoDoc Tests Status Test Coverage

Address is a Go library that validates and formats addresses using data generated from Google's Address Data Service.

Installation

Install the library using Go modules. For example: go get -u github.com/fuskovic/address.

Creating Addresses

To create a new address, use New(). If the address is invalid, an error will be returned.

package main

import (
	"log"

	"github.com/fuskovic/address"
	"github.com/hashicorp/go-multierror"
)

func main() {
	addr, err := address.NewValid(
		address.WithCountry("AU"), // Must be an ISO 3166-1 country code
		address.WithName("John Citizen"),
		address.WithOrganization("Some Company Pty Ltd"),
		address.WithStreetAddress([]string{
			"525 Collins Street",
		}),
		address.WithLocality("Melbourne"),
		address.WithAdministrativeArea("VIC"), // If the country has a pre-defined list of admin areas (like here), you must use the key and not the name
		address.WithPostCode("3000"),
	)

	if err != nil {
		// If there was an error and you want to find out which validations failed,
		// type switch it as a *multierror.Error to access the list of errors
		if merr, ok := err.(*multierror.Error); ok {
			for _, subErr := range merr.Errors {
				if subErr == address.ErrInvalidCountryCode {
					log.Fatalf(subErr)
				}
			}
		}
	}

	// Use addr here
}
A note about administrative areas, localities and dependent localities

An address may contain the following subdivisions:

  • Administrative areas, such as a state, province, island, etc.
  • Localities such as cities.
  • Dependent localities such as districts, suburbs, etc.

When creating an address, certain countries have a pre-defined list of administrative areas, localities and dependent localities. In these cases, you MUST use the appropriate key when calling WithAdministrativeArea(), WithLocality() and WithDependentLocality(), otherwise, the address will fail validation.

In terms of the keys, for administrative areas we use the ISO 3166-2 subdivision codes from Google's data set where possible. If there is no ISO 3166-2 code available, we use the key defined in Google's data set. In these cases, the key is a unicode string (could be in languages other than English).

For localities and dependent localities, there are generally no ISO codes, so we use the key defined in Google's data set. The key is a unicode string and can be in a language other than English.

The reason for doing this is that when storing an address into a database, we need to store the values in a canonical form. Since these keys are very stable (in general), they are safe to store. If we need to provide a visual representation of the address, we can then use the key and a language to choose the appropriate display names.

This also allows us to do things such as rendering Canadian addresses both in French and English using a canonical address.

The library contains helpers where you can access these keys and the display names in different languages. More information available below.

Formatting Addresses

There are 2 formatters, the DefaultFormatter and a PostalLabelFormatter.

In addition, there 2 outputters, the StringOutputter and the HTMLOutputter. The outputter takes the formatted address from the formatters and turn them into their respective string or HTML representations. The Outputter is an interface, so it's possible to implement your own version of the outputter if desired.

In some countries such as China, the address is formatted as major-to-minor (i.e. country -> administrative division -> locality ...). It's possible to format it using a latinized format (address -> dependent locality -> locality ...) by setting the Latinize field in the formatter to true.

This example shows the difference between the 2 formatters and outputters (error checking omitted for brevity):

package main

import (
	"fmt"

	"github.com/fuskovic/address"
)

func main() {

	addr, _ := address.NewValid(
		address.WithCountry("AU"),
		address.WithName("John Citizen"),
		address.WithOrganization("Some Company Pty Ltd"),
		address.WithStreetAddress([]string{
			"525 Collins Street",
		}),
		address.WithLocality("Melbourne"),
		address.WithAdministrativeArea("VIC"),
		address.WithPostCode("3000"),
	)

	defStringFormatter := address.DefaultFormatter{
		Output: address.StringOutputter{},
	}

	defHTMLFormatter := address.DefaultFormatter{
		Output: address.HTMLOutputter{},
	}

	postalStringFormatter := address.PostalLabelFormatter{
		Output:            address.StringOutputter{},
		OriginCountryCode: "FR", // We are sending from France
	}

	postalHTMLFormatter := address.PostalLabelFormatter{
		Output:            address.HTMLOutputter{},
		OriginCountryCode: "FR", // We are sending from France
	}

	lang := "en" // Use the English names of the administrative areas, localities and dependent localities where possible

	fmt.Println(defStringFormatter.Format(addr, lang))
	/* Output
	Some Company Pty Ltd
	John Citizen
	525 Collins Street
	Melbourne Victoria 3000
	Australia
	*/

	fmt.Println(defHTMLFormatter.Format(addr, lang))
	/* Output
	<span class="organization">Some Company Pty Ltd</span><br>
	<span class="name">John Citizen</span><br>
	<span class="address-line-1">525 Collins Street</span><br>
	<span class="locality">Melbourne</span> <span class="administrative-area">Victoria</span> <span class="post-code">3000</span><br>
	<span class="country">Australia</span>
	*/

	fmt.Println(postalStringFormatter.Format(addr, lang))
	/* Output
	Some Company Pty Ltd
	John Citizen
	525 Collins Street
	MELBOURNE VIC 3000
	AUSTRALIE - AUSTRALIA
	*/

	fmt.Println(postalHTMLFormatter.Format(addr, lang))
	/* Output
	<span class="organization">Some Company Pty Ltd</span><br>
	<span class="name">John Citizen</span><br>
	<span class="address-line-1">525 Collins Street</span><br>
	<span class="locality">MELBOURNE</span> <span class="administrative-area">VIC</span> <span class="post-code">3000</span><br>
	<span class="country">AUSTRALIE - AUSTRALIA</span>
	*/
}

Zones

Zones are useful for calculating things like shipping costs or tax rates. A Zone consists of multiple territories, with each Territory equivalent to a rule.

Territories are able to match addresses based on their Country, AdministrativeArea, Locality, DependentLocality and PostCode.

Note that the Country must be an ISO 3166-1 country code, and if there are pre-defined lists of AdministrativeAreas, Locality, and DependentLocality for the country, the key must be used.

A quick example:

package main

import (
	"fmt"

	"github.com/fuskovic/address"
)

func main() {
	addr, _ := address.NewValid(
		address.WithCountry("AU"),
		address.WithName("John Citizen"),
		address.WithOrganization("Some Company Pty Ltd"),
		address.WithStreetAddress([]string{
			"525 Collins Street",
		}),
		address.WithLocality("Melbourne"),
		address.WithAdministrativeArea("VIC"),
		address.WithPostCode("3000"),
	)

	freeShippingToQLDAndNSW := address.Zone{
		{
			Country:            "AU",
			AdministrativeArea: "NSW",
		},
		{
			Country:            "AU",
			AdministrativeArea: "QLD",
		},
	}

	fmt.Println(freeShippingToQLDAndNSW.Contains(addr)) // false

	victorianPostCodesExceptCarltonGetDiscount := address.Zone{
		{
			Country: "AU",
			IncludedPostCodes: address.ExactMatcher{
				Ranges: []address.PostCodeRange{
					{
						Start: 3000,
						End:   3996,
					},
					{
						Start: 8000,
						End:   8873,
					},
				},
			},
			ExcludedPostCodes: address.ExactMatcher{
				Matches: []string{"3053"},
			},
		},
	}

	fmt.Println(victorianPostCodesExceptCarltonGetDiscount.Contains(addr)) // true
}

Address Data Format

In a lot of cases, you might need to display a form to the user to enter their address.

There is the ListCountries() method to get a list of available countries in your chosen language and the GetCountry() method to get detailed address format information for a given country.

GetCountry() returns a struct like so:

type CountryData struct {
	Format                     string
	LatinizedFormat            string
	Required                   []Field
	Allowed                    []Field
	DefaultLanguage            string
	AdministrativeAreaNameType FieldName
	LocalityNameType           FieldName
	DependentLocalityNameType  FieldName
	PostCodeNameType           FieldName
	PostCodeRegex              PostCodeRegexData
	AdministrativeAreas        map[string][]AdministrativeAreaData
}

The Format and LatinizedFormat fields are in Google's original formats (ex: %O%n%N%n%A%n%C %S %Z for Australia). A description of what the tokens represent is available here.

Required and Allowed represent fields that are required and allowed (not all allowed fields are required). The Field type can be converted to Google's token name by calling the Key() method.

For administrative areas, the map contains a list of administrative areas grouped by the language they are in (the map's key). Each list is sorted according to the language they are in. Administrative areas may contain localities, and localities may contain dependent localities. In all cases, each element would have an ID that you should use when creating an address or a zone.

There may also be post code validation regex. There may be further structs nested inside to validate post codes for an administrative area, locality or dependent locality. These are keyed using the appropriate ID from the list of administrative areas.

Generating Data

Directly in your environment

Install stringer: go get -u golang.org/x/tools/cmd/stringer.

To generate the data and generate the String() functions for the constants, simply run go generate from the root of the project. This will run stringer and the generator which will download the data from Google and convert the data into Go code.

Using docker

Run docker-compose run generate

License

This library is licensed under the Apache 2 License.

Documentation

Overview

Package address is a library that validates and formats addresses using data generated from Google's Address Data Service.

Code generated by address. DO NOT EDIT.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidAdministrativeArea = errors.New("invalid administrative area")

ErrInvalidAdministrativeArea indicates that the administrative area is invalid. This is usually due to the country having a pre-determined list of administrative areas and the value does not match any of the keys in the list of administrative areas.

View Source
var ErrInvalidCountryCode = errors.New("invalid country code")

ErrInvalidCountryCode indicate that the country code used to create an address is invalid.

View Source
var ErrInvalidDependentLocality = errors.New("invalid dependent locality")

ErrInvalidDependentLocality indicates that the dependent locality is invalid. This is usually due to the country having a pre-determined list of dependent localities and the value does not match any of the keys in the list of dependent localities.

View Source
var ErrInvalidLocality = errors.New("invalid locality")

ErrInvalidLocality indicates that the locality is invalid. This is usually due to the country having a pre-determined list of localities and the value does not match any of the keys in the list of localities.

View Source
var ErrInvalidPostCode = errors.New("invalid post code")

ErrInvalidPostCode indicates that the post code did not valid using the regular expressions of the country.

Functions

func Validate

func Validate(address Address) error

Validate checks and address to determine if it is valid. If you want to create valid addresses, the `address.NewValid()` function does it in one call.

func WithAdministrativeArea

func WithAdministrativeArea(administrativeArea string) func(*Address)

WithAdministrativeArea sets the administrative area (commonly known as the state) of an address. If the country of the address has a list of administrative area, then the key of the administrative area should used, otherwise, the validation will fail.

func WithCountry

func WithCountry(countryCode string) func(*Address)

WithCountry sets the country code of an address. The country code must be an ISO 3166-1 country code.

func WithDependentLocality

func WithDependentLocality(dependentLocality string) func(*Address)

WithDependentLocality sets the dependent locality (commonly known as the suburb) of an address. If the country of the address has a list of dependent localities, then the key of the dependent locality should be used, otherwise, the validation will fail.

func WithLocality

func WithLocality(locality string) func(*Address)

WithLocality sets the locality (commonly known as the city) of an address. If the country of the address has a list of localities, then the key of the locality should be used, otherwise, the validation will fail.

func WithName

func WithName(name string) func(*Address)

WithName sets the addressee's name of an address.

func WithOrganization

func WithOrganization(organization string) func(*Address)

WithOrganization sets the addressee's organization of an address.

func WithPostCode

func WithPostCode(postCode string) func(*Address)

WithPostCode sets the post code of an address.

func WithSortingCode

func WithSortingCode(sortingCode string) func(*Address)

WithSortingCode sets the sorting code of an address.

func WithStreetAddress

func WithStreetAddress(streetAddress []string) func(*Address)

WithStreetAddress sets the street address of an address. The street address is a slice of strings, with each element representing an address line.

Types

type Address

type Address struct {
	ID                 string       `json:"id,omitempty" db:"id"`
	UserID             string       `json:"user_id,omitempty" db:"user_id"`
	Country            string       `json:"country" db:"country"`
	Name               string       `json:"name" db:"name"`
	Organization       string       `json:"organization"`
	StreetAddress      Addresses    `json:"street" db:"street"`
	DependentLocality  string       `json:"dependent_locality" db:"dependent_locality"`
	Locality           string       `json:"locality" db:"locality"`
	AdministrativeArea string       `json:"administrative_area" db:"administrative_area"`
	PostCode           string       `json:"postal_code" db:"postal_code"`
	SortingCode        string       `json:"sorting_code" db:"sorting_code"`
	CreatedAt          time.Time    `json:"created_at,omitempty" db:"created_at"`
	UpdatedAt          time.Time    `json:"updated_at,omitempty" db:"updated_at"`
	DeletedAt          sql.NullTime `json:"deleted_at,omitempty" db:"deleted_at"`
}

Address represents a valid address made up of its child components.

func New

func New(fields ...func(*Address)) Address

New creates a new unvalidated address. The validity of the address should be checked using the validator.

func NewValid

func NewValid(fields ...func(*Address)) (Address, error)

NewValid creates a new Address. If the address is invalid, an error is returned. In the case where an error is returned, the error is a hashicorp/go-multierror (https://github.com/hashicorp/go-multierror). You can use a type switch to get a list of validation errors for the address.

func (Address) IsZero

func (a Address) IsZero() bool

IsZero reports whether a represents a zero/uninitialized address

type Addresses added in v0.6.8

type Addresses []string

func (*Addresses) Scan added in v0.6.8

func (a *Addresses) Scan(src interface{}) error

func (*Addresses) Value added in v0.6.8

func (a *Addresses) Value() (driver.Value, error)

type AdministrativeAreaData

type AdministrativeAreaData struct {
	ID   string
	Name string

	Localities []LocalityData
}

AdministrativeAreaData contains the name and ID of and administrative area. The ID must be passed to WithAdministrativeArea() when creating an address. The name is useful for displaying to the end user.

type CountryData

type CountryData struct {
	Format                     string
	LatinizedFormat            string
	Required                   []Field
	Allowed                    []Field
	DefaultLanguage            string
	AdministrativeAreaNameType FieldName
	LocalityNameType           FieldName
	DependentLocalityNameType  FieldName
	PostCodeNameType           FieldName
	PostCodeRegex              PostCodeRegexData
	AdministrativeAreas        map[string][]AdministrativeAreaData
}

CountryData contains the address data for a country. The AdministrativeAreas field contains a list of nested subdivisions (administrative areas, localities and dependent localities) grouped by their translated languages. They are also sorted according to the sort order of the languages they are in.

func GetCountry

func GetCountry(countryCode string) CountryData

GetCountry returns address information for a given country.

type CountryList

type CountryList []CountryListItem

CountryList contains a list of countries that can be used to create addresses.

func (CountryList) Bytes

func (c CountryList) Bytes(i int) []byte

Bytes returns a country name in bytes. This is used for sorting the countries and would not generally be used in client code.

func (CountryList) Len

func (c CountryList) Len() int

Len returns the number of countries in the list. This is used for sorting the countries and would not generally be used in client code.

func (CountryList) Swap

func (c CountryList) Swap(i, j int)

Swap swaps 2 countries in the list. This is used for sorting the countries and would not generally be used in client code.

type CountryListItem

type CountryListItem struct {
	Code string
	Name string
}

CountryListItem represents a single country, containing the ISO 3166-1 code and the name of the country.

func ListCountries

func ListCountries(language string) []CountryListItem

ListCountries returns a list of countries that can be used to create addresses. Language must be a valid ISO 639-1 language code such as: en, jp, zh, etc. If the language does not have any translations or is invalid, then English is used as the fallback language. The returned list of countries is sorted according to the chosing language.

type DefaultFormatter

type DefaultFormatter struct {
	Output   Outputter
	Latinize bool
}

DefaultFormatter formats an address using the country's address format and includes the name of the country. If Latinize is set to true, in countries where a latinized address format is provided, the latinized format is used.

func (DefaultFormatter) Format

func (d DefaultFormatter) Format(address Address, language string) string

Format formats an address. The language must be a valid ISO 639-1 language code. It is used to convert the keys in administrative areas, localities and dependent localities into their actual names. If the provided language does not have any translations, it falls back to the default language used by the country.

type DependentLocalityData

type DependentLocalityData struct {
	ID   string
	Name string
}

DependentLocalityData contains the name and ID of and administrative area. The ID must be passed to WithDependentLocalityData() when creating an address. The name is useful for displaying to the end user.

type ErrMissingRequiredFields

type ErrMissingRequiredFields struct {
	Fields []Field
	// contains filtered or unexported fields
}

ErrMissingRequiredFields indicates the a required address field is missing. The Fields field can be used to get a list of missing fields.

func (ErrMissingRequiredFields) Error

func (e ErrMissingRequiredFields) Error() string

type ErrUnsupportedFields

type ErrUnsupportedFields struct {
	Fields []Field
	// contains filtered or unexported fields
}

ErrUnsupportedFields indicates that an address field as provided, but it is not supported by the address format of the country. The Fields field can be used to get a list of unsupported fields.

func (ErrUnsupportedFields) Error

func (e ErrUnsupportedFields) Error() string

type ExactMatcher

type ExactMatcher struct {
	Matches []string
	Ranges  []PostCodeRange
}

ExactMatcher matches post codes exactly in the list defined in the Matches field. If the post code is numeric, it's also possible to define a slice of ranges using the Ranges fiel.d

func (ExactMatcher) Match

func (m ExactMatcher) Match(postCode string) bool

Match checks to see if the post code matches a post code defined in Matches or if it is within a range defined in Ranges.

type Field

type Field int

Field is an address field type.

const (
	Country Field = iota + 1
	Name
	Organization
	StreetAddress
	DependentLocality
	Locality
	AdministrativeArea
	PostCode
	SortingCode
)

func (Field) Key

func (i Field) Key() string

Key returns the corresponding one-letter abbreviation used by Google to refer to address fields. This is useful for parsing the address format for a country. See https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata for more information.

func (Field) String

func (i Field) String() string

type FieldName

type FieldName int

FieldName is the name to be used when referring to a field. For example, in India, the post code is called PIN Code instead of Post Code. The field name allows you to display the appropriate form labels to the user.

const (
	Area FieldName = iota + 1
	City
	County
	Department
	District
	DoSi
	Eircode
	Emirate
	Island
	Neighborhood
	Oblast
	PINCode
	Parish
	PostTown
	PostalCode
	Prefecture
	Province
	State
	Suburb
	Townland
	VillageTownship
	ZipCode
)

func (FieldName) String

func (i FieldName) String() string

type HTMLOutputter

type HTMLOutputter struct{}

HTMLOutputter outputs the formatted address as an HTML fragments. The address fields are annotated using the class attribute and `<br>`s are used for new lines.

func (HTMLOutputter) TransformFormat

func (h HTMLOutputter) TransformFormat(format string, upper map[Field]struct{}) string

TransformFormat transforms an address format in Google's format into a HTML template. The upper map is used to determine which fields should be converted to UPPERCASE.

type LocalityData

type LocalityData struct {
	ID   string
	Name string

	DependentLocalities []DependentLocalityData
}

LocalityData contains the name and ID of and administrative area. The ID must be passed to WithLocalityData() when creating an address. The name is useful for displaying to the end user.

type Outputter

type Outputter interface {
	TransformFormat(format string, upper map[Field]struct{}) string
}

Outputter defines an interface to transform an address format in Google's format into a Go template that is merged with the address data to produce a formatted address.

type PostCodeMatcher

type PostCodeMatcher interface {
	Match(postcode string) bool
}

PostCodeMatcher returns a boolean signaling whether the post code matched or not.

type PostCodeRange

type PostCodeRange struct {
	Start int
	End   int
}

PostCodeRange defines a range of numeric post codes, inclusive of the Start and End.

type PostCodeRegexData

type PostCodeRegexData struct {
	Regex            string
	SubdivisionRegex map[string]PostCodeRegexData
}

PostCodeRegexData contains regular expressions for validating post codes for a given country. If the country has subdivisions (administrative areas, localities and dependent localities), the SubdivisionRegex field may contain further regular expressions to Validate the post code.

type PostalLabelFormatter

type PostalLabelFormatter struct {
	Output            Outputter
	OriginCountryCode string
	Latinize          bool
}

PostalLabelFormatter formats an address for postal labels. It uppercases address fields as required by the country's addressing standards. If the address it in the same country as the origin country, the country is omitted. The country name is added to the address, both in the language of the origin country as well as English, following recommendations of the Universal Postal Union, to avoid difficulties in transit. The OriginCountryCode field should be set to the ISO 3166-1 country code of the originating country. If Latinize is set to true, in countries where a latinized address format is provided, the latinized format is used.

func (PostalLabelFormatter) Format

func (f PostalLabelFormatter) Format(address Address, language string) string

Format formats an address. The language must be a valid ISO 639-1 language code. It is used to convert the keys in administrative areas, localities and dependent localities into their actual names. If the provided language does not have any translations, it falls back to the default language used by the country.

type RegexMatcher

type RegexMatcher struct {
	Regex *regexp.Regexp
}

RegexMatcher defines a post code matcher that uses a regular expression.

func (RegexMatcher) Match

func (m RegexMatcher) Match(postCode string) bool

Match returns whether the post code is matched by the regular expression defined in the matcher.

type StringOutputter

type StringOutputter struct{}

StringOutputter outputs the formatted address as a string and `\n`s are used for new lines.

func (StringOutputter) TransformFormat

func (s StringOutputter) TransformFormat(format string, upper map[Field]struct{}) string

TransformFormat transforms an address format in Google's format into a string template. The upper map is used to determine which fields should be converted to UPPERCASE.

type Territory

type Territory struct {
	Country            string
	AdministrativeArea string
	Locality           string
	DependentLocality  string
	IncludedPostCodes  PostCodeMatcher
	ExcludedPostCodes  PostCodeMatcher
}

Territory is a rule within a zone. It is able to match the following fields: - Country: An ISO 3166-1 country code - AdministrativeArea: The administrative area name. If the country has a list of pre-defined administrative areas, use the key of the administrative area. - Locality: The locality name. If the country has a list of pre-defined localities, use the key of the locality. - DependentLocality: The dependent locality name. If the country has a list of pre-defined dependent localities, use the key of the locality. - IncludedPostCodes: A PostCodeMatcher that includes the address within the territory if the post code matches. - ExcludedPostCodes: A PostCodeMatcher that excludes the address from the territory if the post code matches.

type Zone

type Zone []Territory

Zone is a list of territories. Zones are useful for determining whether an address is in a country, administrative area or a range of post codes to determine shipping or tax rates.

func (Zone) Contains

func (z Zone) Contains(address Address) bool

Contains checks to see an address is in a zone.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL