├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── abode.go ├── abode_test.go ├── component.go ├── go.mod └── go.sum /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: mattevansnz 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.19.x 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 by Matt Evans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # abode 🏠 2 | 3 | [![GoDoc](https://godoc.org/github.com/mattevans/abode?status.svg)](https://godoc.org/github.com/mattevans/abode) 4 | [![Build Status](https://travis-ci.org/mattevans/abode.svg?branch=master)](https://travis-ci.org/mattevans/abode) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/mattevans/abode)](https://goreportcard.com/report/github.com/mattevans/abode) 6 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/mattevans/abode/blob/master/LICENSE) 7 | 8 | - Geocode one-line addresses. 9 | - Determine timezone information for a given address. 10 | - This package uses the [Google Maps Web Services](https://developers.google.com/maps/web-services) to geocode the address. 11 | - You will require the [Geocoding API](https://developers.google.com/maps/documentation) enabled, and optionally the [Timezone API] if you wish to also use `Timezone()`. 12 | - Remember to set your `GOOGLE_MAPS_API_KEY` environment variable. 13 | 14 | Installation 15 | ----------------- 16 | 17 | `go get -u github.com/mattevans/abode` 18 | 19 | Example 20 | ------------- 21 | 22 | ### Geocode an address: 23 | 24 | ```go 25 | addr := "193 Rogers Ave, Brooklyn, New York" 26 | 27 | address, err := abode.ExplodeWithContext(ctx, addr) 28 | if err != nil { 29 | return err 30 | } 31 | ``` 32 | 33 | Returns... 34 | 35 | ```go 36 | abode.Address{ 37 | AddressLine1: "193 Rogers Avenue", 38 | AddressLine2: "Brooklyn" 39 | AddressCity: nil, 40 | AddressState: "New York" 41 | AddressCountry: "United States" 42 | AddressZip: "11216" 43 | AddressLat: 40.6706073, 44 | AddressLng: -73.9530182, 45 | FormattedAddress: "193 Rogers Ave, Brooklyn, NY 11216, USA", 46 | } 47 | ``` 48 | 49 | ### Timezone information for an address: 50 | 51 | ```go 52 | addr := "193 Rogers Ave, Brooklyn, New York" 53 | 54 | address, err := abode.Timezone(ctx, addr) 55 | if err != nil { 56 | return err 57 | } 58 | ``` 59 | 60 | Returns... 61 | 62 | ```go 63 | abode.Location{ 64 | DstOffset: 0, 65 | RawOffset: -17762, 66 | TimeZoneId: "GMT-04:56:02", 67 | TimeZoneName: "America/New_York" 68 | } 69 | ``` 70 | 71 | Disclaimer 72 | ------------- 73 | 74 | Ensure your end results are used in conjunction with a Google Map to avoid violating the [Google Maps API Terms of Service](https://developers.google.com/maps/documentation/geocoding/policies). 75 | -------------------------------------------------------------------------------- /abode.go: -------------------------------------------------------------------------------- 1 | package abode 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "googlemaps.github.io/maps" 11 | ) 12 | 13 | var ( 14 | client *maps.Client 15 | addressLineDelimiter = "," 16 | ) 17 | 18 | // Address represents a response Address from abode. 19 | type Address struct { 20 | AddressLine1 *string `json:"address_line1"` 21 | AddressLine2 *string `json:"address_line2"` 22 | AddressCity *string `json:"address_city"` 23 | AddressState *string `json:"address_state"` 24 | AddressCountry *string `json:"address_country"` 25 | AddressCountryCode *string `json:"address_country_code"` 26 | AddressZip *string `json:"address_zip"` 27 | AddressLat *float64 `json:"address_lat"` 28 | AddressLng *float64 `json:"address_lng"` 29 | FormattedAddress *string `json:"formatted_address"` 30 | } 31 | 32 | type Location struct { 33 | DstOffset int `json:"dst_offset"` 34 | RawOffset int `json:"raw_offset"` 35 | TimeZoneId string `json:"time_zone_id"` 36 | TimeZoneName string `json:"time_zone_name"` 37 | } 38 | 39 | // client will initialize a Google Maps API client. 40 | func mapsClient() error { 41 | var ( 42 | err error 43 | key = os.Getenv("GOOGLE_MAPS_API_KEY") 44 | ) 45 | 46 | if key == "" { 47 | return errors.New("missing `GOOGLE_MAPS_API_KEY`") 48 | } 49 | 50 | client, err = maps.NewClient(maps.WithAPIKey(key)) 51 | return err 52 | } 53 | 54 | // Explode takes a one-line address string, explodes it using gmaps.Geocode() and returns an *Address. 55 | // Deprecated: Use ExplodeWithContext instead. 56 | func Explode(address string) (*Address, error) { 57 | return ExplodeWithContext(context.Background(), address) 58 | } 59 | 60 | // ExplodeWithContext takes a one-line address string, explodes it using gmaps.Geocode() and returns an *Address. 61 | func ExplodeWithContext(ctx context.Context, address string) (*Address, error) { 62 | if client == nil { 63 | if err := mapsClient(); err != nil { 64 | return nil, err 65 | } 66 | } 67 | 68 | rsp, err := client.Geocode(ctx, &maps.GeocodingRequest{ 69 | Address: address, 70 | }) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | if len(rsp) < 1 { 76 | return nil, err 77 | } 78 | 79 | var ( 80 | geocodeResult = rsp[0] 81 | components = geocodeResult.AddressComponents 82 | formattedAddress = geocodeResult.FormattedAddress 83 | lat = geocodeResult.Geometry.Location.Lat 84 | lng = geocodeResult.Geometry.Location.Lng 85 | ) 86 | 87 | return &Address{ 88 | AddressLine1: compose(addressLine1Composition, "", components, false), 89 | AddressLine2: compose(addressLine2Composition, addressLineDelimiter, components, false), 90 | AddressCity: compose(addressCityComposition, addressLineDelimiter, components, false), 91 | AddressState: compose(addressStateComposition, addressLineDelimiter, components, false), 92 | AddressCountry: compose(addressCountryComposition, addressLineDelimiter, components, false), 93 | AddressCountryCode: compose(addressCountryCodeComposition, addressLineDelimiter, components, true), 94 | AddressZip: compose(addressPostalCodeComposition, addressLineDelimiter, components, false), 95 | AddressLat: &lat, 96 | AddressLng: &lng, 97 | FormattedAddress: &formattedAddress, 98 | }, err 99 | } 100 | 101 | // Timezone takes a one-line address string, and determine timezone/location data for it using gmaps.Timezone(). 102 | func Timezone(ctx context.Context, address string) (*Location, error) { 103 | if client == nil { 104 | if err := mapsClient(); err != nil { 105 | return nil, err 106 | } 107 | } 108 | 109 | geocodeResult, err := ExplodeWithContext(ctx, address) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if geocodeResult.AddressLat == nil || geocodeResult.AddressLng == nil { 115 | return nil, errors.New("unable to determine latitude and longitude for address") 116 | } 117 | 118 | resp, err := client.Timezone(ctx, &maps.TimezoneRequest{ 119 | Location: &maps.LatLng{Lat: *geocodeResult.AddressLat, Lng: *geocodeResult.AddressLng}, 120 | }) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return &Location{ 126 | DstOffset: resp.DstOffset, 127 | RawOffset: resp.RawOffset, 128 | TimeZoneId: resp.TimeZoneID, 129 | TimeZoneName: resp.TimeZoneName, 130 | }, err 131 | } 132 | 133 | func compose(composition []string, delimiter string, components []maps.AddressComponent, useShortName bool) *string { 134 | var str string 135 | 136 | for _, element := range composition { 137 | component := getComponentByType(components, element) 138 | if useShortName { 139 | if component != nil && !strings.Contains(str, component.ShortName) { 140 | str = fmt.Sprintf("%s %s%s", str, component.ShortName, delimiter) 141 | } 142 | 143 | continue 144 | } 145 | 146 | if component != nil && !strings.Contains(str, component.LongName) { 147 | str = fmt.Sprintf("%s %s%s", str, component.LongName, delimiter) 148 | } 149 | } 150 | 151 | if str == "" { 152 | return nil 153 | } 154 | 155 | str = strings.TrimPrefix(strings.TrimSuffix(str, delimiter), " ") 156 | 157 | return &str 158 | } 159 | -------------------------------------------------------------------------------- /abode_test.go: -------------------------------------------------------------------------------- 1 | package abode 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/kylelemons/godebug/pretty" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | func TestExplode_US(t *testing.T) { 13 | RegisterTestingT(t) 14 | 15 | if client == nil { 16 | if err := mapsClient(); err != nil { 17 | t.Errorf("Unexpected error: %s", err) 18 | } 19 | } 20 | 21 | var ( 22 | line1 = "193 Rogers Avenue" 23 | line2 = "Brooklyn" 24 | state = "New York" 25 | country = "United States" 26 | countryCode = "US" 27 | zip = "11216" 28 | lat = 40.6706252 29 | lng = -73.9530545 30 | formatted = "193 Rogers Ave, Brooklyn, NY 11216, USA" 31 | 32 | expected = &Address{ 33 | AddressLine1: &line1, 34 | AddressLine2: &line2, 35 | AddressCity: nil, 36 | AddressState: &state, 37 | AddressCountry: &country, 38 | AddressCountryCode: &countryCode, 39 | AddressZip: &zip, 40 | AddressLat: &lat, 41 | AddressLng: &lng, 42 | FormattedAddress: &formatted, 43 | } 44 | ) 45 | 46 | result, err := ExplodeWithContext(context.Background(), "193 Rogers Ave, Brooklyn, New York") 47 | if err != nil { 48 | t.Errorf("Unexpected error: %s", err) 49 | } 50 | 51 | if !reflect.DeepEqual(result, expected) { 52 | t.Errorf("Unexpected Results\n%s", pretty.Compare(result, expected)) 53 | } 54 | } 55 | 56 | func TestExplode_International(t *testing.T) { 57 | RegisterTestingT(t) 58 | 59 | if client == nil { 60 | if err := mapsClient(); err != nil { 61 | t.Errorf("Unexpected error: %s", err) 62 | } 63 | } 64 | 65 | var ( 66 | line1 = "1 4 Abercrombie Street" 67 | line2 = "Howick" 68 | city = "Auckland" 69 | state = "Auckland" 70 | country = "New Zealand" 71 | countryCode = "NZ" 72 | zip = "2014" 73 | lat = -36.8990751 74 | lng = 174.9334851 75 | formatted = "1/4 Abercrombie Street, Howick, Auckland 2014, New Zealand" 76 | 77 | expected = &Address{ 78 | AddressLine1: &line1, 79 | AddressLine2: &line2, 80 | AddressCity: &city, 81 | AddressState: &state, 82 | AddressCountry: &country, 83 | AddressCountryCode: &countryCode, 84 | AddressZip: &zip, 85 | AddressLat: &lat, 86 | AddressLng: &lng, 87 | FormattedAddress: &formatted, 88 | } 89 | ) 90 | 91 | result, err := ExplodeWithContext(context.Background(), "1/4 Abercrombie Street, Auckland") 92 | if err != nil { 93 | t.Errorf("Unexpected error: %s", err) 94 | } 95 | 96 | if !reflect.DeepEqual(result, expected) { 97 | t.Errorf("Unexpected Results\n%s", pretty.Compare(result, expected)) 98 | } 99 | } 100 | 101 | func TestTimezone_US(t *testing.T) { 102 | RegisterTestingT(t) 103 | 104 | if client == nil { 105 | if err := mapsClient(); err != nil { 106 | t.Errorf("Unexpected error: %s", err) 107 | } 108 | } 109 | 110 | var ( 111 | expected = &Location{ 112 | DstOffset: 0, 113 | RawOffset: -17762, 114 | TimeZoneId: "America/New_York", 115 | TimeZoneName: "GMT-04:56:02", 116 | } 117 | ) 118 | 119 | result, err := Timezone(context.Background(), "193 Rogers Ave, Brooklyn, New York") 120 | if err != nil { 121 | t.Errorf("Unexpected error: %s", err) 122 | } 123 | 124 | if !reflect.DeepEqual(result, expected) { 125 | t.Errorf("Unexpected Results\n%s", pretty.Compare(result, expected)) 126 | } 127 | } 128 | 129 | func TestTimezone_International(t *testing.T) { 130 | RegisterTestingT(t) 131 | 132 | if client == nil { 133 | if err := mapsClient(); err != nil { 134 | t.Errorf("Unexpected error: %s", err) 135 | } 136 | } 137 | 138 | var ( 139 | expected = &Location{ 140 | DstOffset: 0, 141 | RawOffset: 41944, 142 | TimeZoneId: "Pacific/Auckland", 143 | TimeZoneName: "GMT+11:39:04", 144 | } 145 | ) 146 | 147 | result, err := Timezone(context.Background(), "1/4 Abercrombie Street, Auckland") 148 | if err != nil { 149 | t.Errorf("Unexpected error: %s", err) 150 | } 151 | 152 | if !reflect.DeepEqual(result, expected) { 153 | t.Errorf("Unexpected Results\n%s", pretty.Compare(result, expected)) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | package abode 2 | 3 | import "googlemaps.github.io/maps" 4 | 5 | // Define the different address component types. 6 | const ( 7 | AddressComponentTypeSubPremise = "subpremise" 8 | AddressComponentTypePremise = "premise" 9 | AddressComponentTypeStreetNumber = "street_number" 10 | AddressComponentTypeRoute = "route" 11 | AddressComponentTypeStreetAddress = "street_address" 12 | AddressComponentTypeSubLocality = "sublocality" 13 | AddressComponentTypeLocality = "locality" 14 | AddressComponentTypeAdminAreaLevel1 = "administrative_area_level_1" 15 | AddressComponentTypeAdminAreaLevel2 = "administrative_area_level_2" 16 | AddressComponentTypeAdminAreaLevel3 = "administrative_area_level_3" 17 | AddressComponentTypeAdminAreaLevel4 = "administrative_area_level_4" 18 | AddressComponentTypeAdminAreaLevel5 = "administrative_area_level_5" 19 | AddressComponentTypeCountry = "country" 20 | AddressComponentTypePostalCode = "postal_code" 21 | ) 22 | 23 | // Defines the address components that make up Address.AddressLine1. 24 | var addressLine1Composition = []string{ 25 | AddressComponentTypeSubPremise, 26 | AddressComponentTypePremise, 27 | AddressComponentTypeStreetNumber, 28 | AddressComponentTypeRoute, 29 | AddressComponentTypeStreetAddress, 30 | } 31 | 32 | // Defines the address components that make up Address.AddressLine2. 33 | var addressLine2Composition = []string{ 34 | AddressComponentTypeSubLocality, 35 | } 36 | 37 | // Defines the address components that make up Address.AddressCity. 38 | var addressCityComposition = []string{ 39 | AddressComponentTypeLocality, 40 | } 41 | 42 | // Defines the address components that make up Address.AddressState. 43 | var addressStateComposition = []string{ 44 | AddressComponentTypeAdminAreaLevel1, 45 | } 46 | 47 | // Defines the address components that make up Address.Country. 48 | var addressCountryComposition = []string{ 49 | AddressComponentTypeCountry, 50 | } 51 | 52 | // Defines the address components that make up Address.CountryCode. 53 | var addressCountryCodeComposition = []string{ 54 | AddressComponentTypeCountry, 55 | } 56 | 57 | // Defines the address components that make up Address.Zip. 58 | var addressPostalCodeComposition = []string{ 59 | AddressComponentTypePostalCode, 60 | } 61 | 62 | // getComponentByType will return the given componentType from a slice of 63 | // maps.AddressComponent's. 64 | func getComponentByType(components []maps.AddressComponent, componentType string) *maps.AddressComponent { 65 | for k, component := range components { 66 | if isComponentType(component.Types, componentType) { 67 | return &components[k] 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | // isComponentType will check to see if e is contained within s. 74 | func isComponentType(s []string, e string) bool { 75 | for _, a := range s { 76 | if a == e { 77 | return true 78 | } 79 | } 80 | return false 81 | } 82 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattevans/abode 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/kylelemons/godebug v1.1.0 7 | github.com/onsi/gomega v1.20.2 8 | googlemaps.github.io/maps v1.3.2 9 | ) 10 | 11 | require ( 12 | github.com/google/go-cmp v0.5.8 // indirect 13 | github.com/google/uuid v1.1.1 // indirect 14 | go.opencensus.io v0.22.3 // indirect 15 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 16 | golang.org/x/text v0.3.7 // indirect 17 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 8 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= 9 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 14 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 15 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 16 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 17 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 18 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 19 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 20 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 21 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 22 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 23 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 24 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 25 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 26 | github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= 27 | github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= 28 | github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 32 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 33 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 36 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 37 | go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= 38 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 41 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 42 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 43 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 44 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 45 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 46 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 47 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 48 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 49 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 50 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 51 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 52 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 55 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 56 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 57 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 60 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 61 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 62 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 63 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= 64 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 67 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 68 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 69 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 70 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 71 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 72 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 73 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 74 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 75 | googlemaps.github.io/maps v1.3.2 h1:3YfYdVWFTFi7lVdCdrDYW3dqHvfCSUdC7/x8pbMOuKQ= 76 | googlemaps.github.io/maps v1.3.2/go.mod h1:cCq0JKYAnnCRSdiaBi7Ex9CW15uxIAk7oPi8V/xEh6s= 77 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 78 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 79 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 80 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 81 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 82 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 83 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 84 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 85 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 86 | --------------------------------------------------------------------------------