This commit is contained in:
2025-03-10 22:20:51 +02:00
parent 0148583f5b
commit cd8c2ee09e
6 changed files with 156 additions and 12 deletions

14
main.go
View File

@@ -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 {

View File

@@ -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"},

View File

@@ -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,
},

View File

@@ -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
}

64
pokecache/pokecache.go Normal file
View File

@@ -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)
}
}
}

View File

@@ -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
}
}