diff --git a/main.go b/main.go index 34d9ba5..48d6ea9 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ var commands map[string]cliCommand func main() { - pokeClient := pokeapi.NewClient(5 * time.Second) + pokeClient := pokeapi.NewClient(5*time.Second, time.Minute*5) cfg := &config{ pokeapiClient: pokeClient, } @@ -84,15 +84,9 @@ func commandExit(*config) error { } func cleanInput(text string) []string { - // trim input string - text = strings.TrimSpace(text) - // convert to lowercase - text = strings.ToLower(text) - // replace all double spaces into single space - text = strings.Replace(text, " ", " ", -1) - // split by space - separated := strings.Split(text, " ") - return separated + output := strings.ToLower(text) + words := strings.Fields(output) + return words } func printHelpMessage(*config) error { diff --git a/main_test.go b/main_test.go index 9aa0f3d..3b9e942 100644 --- a/main_test.go +++ b/main_test.go @@ -7,10 +7,22 @@ func TestCleanInput(t *testing.T) { input string expected []string }{ + { + input: " ", + expected: []string{}, + }, + { + input: " hello ", + expected: []string{"hello"}, + }, { input: " hello world ", expected: []string{"hello", "world"}, }, + { + input: " HellO World ", + expected: []string{"hello", "world"}, + }, { input: "Charmander Bulbasaur PIKACHU", expected: []string{"charmander", "bulbasaur", "pikachu"}, diff --git a/pokeapi/client.go b/pokeapi/client.go index 61e89dc..b3cc51a 100644 --- a/pokeapi/client.go +++ b/pokeapi/client.go @@ -1,18 +1,21 @@ package pokeapi import ( + "github.com/rdarius/boot-dev-pokedex/pokecache" "net/http" "time" ) // Client - type Client struct { + cache pokecache.Cache httpClient http.Client } // NewClient - -func NewClient(timeout time.Duration) Client { +func NewClient(timeout, cacheInterval time.Duration) Client { return Client{ + cache: pokecache.NewCache(cacheInterval), httpClient: http.Client{ Timeout: timeout, }, diff --git a/pokeapi/location_list.go b/pokeapi/location_list.go index df2fefe..e34615d 100644 --- a/pokeapi/location_list.go +++ b/pokeapi/location_list.go @@ -13,6 +13,16 @@ func (c *Client) ListLocations(pageURL *string) (RespShallowLocations, error) { url = *pageURL } + if val, ok := c.cache.Get(url); ok { + locationsResp := RespShallowLocations{} + err := json.Unmarshal(val, &locationsResp) + if err != nil { + return RespShallowLocations{}, err + } + + return locationsResp, nil + } + req, err := http.NewRequest("GET", url, nil) if err != nil { return RespShallowLocations{}, err @@ -22,7 +32,6 @@ func (c *Client) ListLocations(pageURL *string) (RespShallowLocations, error) { if err != nil { return RespShallowLocations{}, err } - defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) @@ -38,5 +47,6 @@ func (c *Client) ListLocations(pageURL *string) (RespShallowLocations, error) { return RespShallowLocations{}, err } + c.cache.Add(url, dat) return locationsResp, nil } diff --git a/pokecache/pokecache.go b/pokecache/pokecache.go new file mode 100644 index 0000000..6017d99 --- /dev/null +++ b/pokecache/pokecache.go @@ -0,0 +1,64 @@ +package pokecache + +import ( + "sync" + "time" +) + +// Cache - +type Cache struct { + cache map[string]cacheEntry + mux *sync.Mutex +} + +type cacheEntry struct { + createdAt time.Time + val []byte +} + +// NewCache - +func NewCache(interval time.Duration) Cache { + c := Cache{ + cache: make(map[string]cacheEntry), + mux: &sync.Mutex{}, + } + + go c.reapLoop(interval) + + return c +} + +// Add - +func (c *Cache) Add(key string, value []byte) { + c.mux.Lock() + defer c.mux.Unlock() + c.cache[key] = cacheEntry{ + createdAt: time.Now().UTC(), + val: value, + } +} + +// Get - +func (c *Cache) Get(key string) ([]byte, bool) { + c.mux.Lock() + defer c.mux.Unlock() + val, ok := c.cache[key] + return val.val, ok +} + +func (c *Cache) reapLoop(interval time.Duration) { + ticker := time.NewTicker(interval) + for range ticker.C { + c.reap(time.Now().UTC(), interval) + } +} + +func (c *Cache) reap(now time.Time, last time.Duration) { + c.mux.Lock() + defer c.mux.Unlock() + for k, v := range c.cache { + if v.createdAt.Before(now.Add(-last)) { + delete(c.cache, k) + } + } +} diff --git a/pokecache/pokecache_test.go b/pokecache/pokecache_test.go new file mode 100644 index 0000000..21a170b --- /dev/null +++ b/pokecache/pokecache_test.go @@ -0,0 +1,61 @@ +package pokecache + +import ( + "fmt" + "testing" + "time" +) + +func TestAddGet(t *testing.T) { + const interval = 5 * time.Second + cases := []struct { + key string + val []byte + }{ + { + key: "https://example.com", + val: []byte("testdata"), + }, + { + key: "https://example.com/path", + val: []byte("moretestdata"), + }, + } + + for i, c := range cases { + t.Run(fmt.Sprintf("Test case %v", i), func(t *testing.T) { + cache := NewCache(interval) + cache.Add(c.key, c.val) + val, ok := cache.Get(c.key) + if !ok { + t.Errorf("expected to find key") + return + } + if string(val) != string(c.val) { + t.Errorf("expected to find value") + return + } + }) + } +} + +func TestReapLoop(t *testing.T) { + const baseTime = 5 * time.Millisecond + const waitTime = baseTime + 5*time.Millisecond + cache := NewCache(baseTime) + cache.Add("https://example.com", []byte("testdata")) + + _, ok := cache.Get("https://example.com") + if !ok { + t.Errorf("expected to find key") + return + } + + time.Sleep(waitTime) + + _, ok = cache.Get("https://example.com") + if ok { + t.Errorf("expected to not find key") + return + } +}