Add GeoIP info cache access

This commit is contained in:
Igor Chubin 2022-12-04 16:48:04 +01:00
parent a6f2844c67
commit 074b8b6ec8
2 changed files with 170 additions and 0 deletions

99
internal/geo/ip/ip.go Normal file
View file

@ -0,0 +1,99 @@
package ip
import (
"errors"
"os"
"path"
"strconv"
"strings"
"github.com/chubin/wttr.in/internal/config"
)
var (
ErrNotFound = errors.New("cache entry not found")
ErrInvalidCacheEntry = errors.New("invalid cache entry format")
)
// Location information.
type Location struct {
CountryCode string
Country string
Region string
City string
Latitude float64
Longitude float64
}
// Cache provides access to the IP Geodata cache.
type Cache struct {
config *config.Config
}
// NewCache returns new cache reader for the specified config.
func NewCache(config *config.Config) *Cache {
return &Cache{
config: config,
}
}
// Read returns location information from the cache, if found,
// or ErrNotFound if not found. If the entry is found, but its format
// is invalid, ErrInvalidCacheEntry is returned.
//
// Format:
//
// [CountryCode];Country;Region;City;[Latitude];[Longitude]
//
// Example:
//
// DE;Germany;Free and Hanseatic City of Hamburg;Hamburg;53.5736;9.9782
//
func (c *Cache) Read(addr string) (*Location, error) {
bytes, err := os.ReadFile(c.cacheFile(addr))
if err != nil {
return nil, ErrNotFound
}
return parseCacheEntry(string(bytes))
}
// cacheFile retuns path to the cache entry for addr.
func (c *Cache) cacheFile(addr string) string {
return path.Join(c.config.Geo.IPCache, addr)
}
// parseCacheEntry parses the location cache entry s,
// and return location, or error, if the cache entry is invalid.
func parseCacheEntry(s string) (*Location, error) {
var (
lat float64 = -1000
long float64 = -1000
err error
)
parts := strings.Split(s, ";")
if len(parts) < 4 {
return nil, ErrInvalidCacheEntry
}
if len(parts) >= 6 {
lat, err = strconv.ParseFloat(parts[4], 64)
if err != nil {
return nil, ErrInvalidCacheEntry
}
long, err = strconv.ParseFloat(parts[5], 64)
if err != nil {
return nil, ErrInvalidCacheEntry
}
}
return &Location{
CountryCode: parts[0],
Country: parts[1],
Region: parts[2],
City: parts[3],
Latitude: lat,
Longitude: long,
}, nil
}

View file

@ -0,0 +1,71 @@
package ip
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseCacheEntry(t *testing.T) {
tests := []struct {
input string
expected Location
err error
}{
{
"DE;Germany;Free and Hanseatic City of Hamburg;Hamburg;53.5736;9.9782",
Location{
CountryCode: "DE",
Country: "Germany",
Region: "Free and Hanseatic City of Hamburg",
City: "Hamburg",
Latitude: 53.5736,
Longitude: 9.9782,
},
nil,
},
{
"ES;Spain;Madrid, Comunidad de;Madrid;40.4165;-3.70256;28223;Orange Espagne SA;orange.es",
Location{
CountryCode: "ES",
Country: "Spain",
Region: "Madrid, Comunidad de",
City: "Madrid",
Latitude: 40.4165,
Longitude: -3.70256,
},
nil,
},
{
"US;United States of America;California;Mountain View",
Location{
CountryCode: "US",
Country: "United States of America",
Region: "California",
City: "Mountain View",
Latitude: -1000,
Longitude: -1000,
},
nil,
},
// Invalid entries
{
"DE;Germany;Free and Hanseatic City of Hamburg;Hamburg;53.5736;XXX",
Location{},
ErrInvalidCacheEntry,
},
}
for _, tt := range tests {
result, err := parseCacheEntry(tt.input)
if tt.err == nil {
require.NoError(t, err)
require.Equal(t, *result, tt.expected)
} else {
require.ErrorIs(t, err, tt.err)
}
}
}