From 78c842624c2b1cd2de05e018d3a8c32c2d73627a Mon Sep 17 00:00:00 2001 From: zekroTJA Date: Sat, 25 Mar 2023 10:53:30 +0000 Subject: [PATCH] add `FromMap` constructor --- errors.go | 8 +++++- timedmap.go | 67 ++++++++++++++++++++++++++++++++++++++---------- timedmap_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 14 deletions(-) diff --git a/errors.go b/errors.go index 96bd740..86f3051 100644 --- a/errors.go +++ b/errors.go @@ -1,9 +1,15 @@ package timedmap -import "errors" +import ( + "errors" +) var ( // ErrKeyNotFound is returned when a key was // requested which is not present in the map. ErrKeyNotFound = errors.New("key not found") + + // ErrValueNoMap is returned when a value passed + // expected was of another type. + ErrValueNoMap = errors.New("value is not of type map") ) diff --git a/timedmap.go b/timedmap.go index ce330da..5aca089 100644 --- a/timedmap.go +++ b/timedmap.go @@ -1,6 +1,7 @@ package timedmap import ( + "reflect" "sync" "time" ) @@ -53,23 +54,39 @@ type element struct { // can also be used to re-define the specification of // the cleanup loop when already running if you want to. func New(cleanupTickTime time.Duration, tickerChan ...<-chan time.Time) *TimedMap { - tm := &TimedMap{ - container: make(map[keyWrap]*element), - cleanerStopChan: make(chan bool), - elementPool: &sync.Pool{ - New: func() interface{} { - return new(element) - }, - }, + return newTimedMap(make(map[keyWrap]*element), cleanupTickTime, tickerChan) +} + +func FromMap( + m interface{}, + expiration time.Duration, + cleanupTickTime time.Duration, + tickerChan ...<-chan time.Time, +) (*TimedMap, error) { + mv := reflect.ValueOf(m) + if mv.Kind() != reflect.Map { + return nil, ErrValueNoMap } - if len(tickerChan) > 0 { - tm.StartCleanerExternal(tickerChan[0]) - } else if cleanupTickTime > 0 { - tm.StartCleanerInternal(cleanupTickTime) + exp := time.Now().Add(expiration) + container := make(map[keyWrap]*element) + + iter := mv.MapRange() + for iter.Next() { + key := iter.Key() + val := iter.Value() + kw := keyWrap{ + sec: 0, + key: key.Interface(), + } + el := &element{ + value: val.Interface(), + expires: exp, + } + container[kw] = el } - return tm + return newTimedMap(container, cleanupTickTime, tickerChan), nil } // Section returns a sectioned subset of @@ -383,3 +400,27 @@ func (tm *TimedMap) getSnapshot(sec int) (m map[interface{}]interface{}) { return } + +func newTimedMap( + container map[keyWrap]*element, + cleanupTickTime time.Duration, + tickerChan []<-chan time.Time, +) *TimedMap { + tm := &TimedMap{ + container: container, + cleanerStopChan: make(chan bool), + elementPool: &sync.Pool{ + New: func() interface{} { + return new(element) + }, + }, + } + + if len(tickerChan) > 0 { + tm.StartCleanerExternal(tickerChan[0]) + } else if cleanupTickTime > 0 { + tm.StartCleanerInternal(cleanupTickTime) + } + + return tm +} diff --git a/timedmap_test.go b/timedmap_test.go index fcd524c..6aa3b10 100644 --- a/timedmap_test.go +++ b/timedmap_test.go @@ -22,6 +22,65 @@ func TestNew(t *testing.T) { assert.True(t, tm.cleanerRunning) } +func TestFromMap(t *testing.T) { + t.Run("map-string-string", func(t *testing.T) { + tm, err := FromMap( + map[string]string{"foo": "bar", "bazz": "fuzz"}, + 200*time.Millisecond, 10*time.Millisecond) + assert.Nil(t, err) + + assert.EqualValues(t, "bar", tm.GetValue("foo")) + assert.EqualValues(t, "fuzz", tm.GetValue("bazz")) + + time.Sleep(500 * time.Millisecond) + + assert.False(t, tm.Contains("foo")) + assert.False(t, tm.Contains("bazz")) + }) + + t.Run("map-int-interface", func(t *testing.T) { + tm, err := FromMap( + map[int]interface{}{1: "foo", 2: 3.456}, + 200*time.Millisecond, 10*time.Millisecond) + assert.Nil(t, err) + + assert.EqualValues(t, "foo", tm.GetValue(1)) + assert.EqualValues(t, 3.456, tm.GetValue(2)) + + time.Sleep(500 * time.Millisecond) + + assert.False(t, tm.Contains(1)) + assert.False(t, tm.Contains(2)) + }) + + t.Run("map-interface-interface", func(t *testing.T) { + tm, err := FromMap( + map[interface{}]interface{}{1: "foo", "a": 3.456}, + 200*time.Millisecond, 10*time.Millisecond) + assert.Nil(t, err) + + assert.EqualValues(t, "foo", tm.GetValue(1)) + assert.EqualValues(t, 3.456, tm.GetValue("a")) + + time.Sleep(500 * time.Millisecond) + + assert.False(t, tm.Contains(1)) + assert.False(t, tm.Contains("a")) + }) + + t.Run("non-map", func(t *testing.T) { + _, err := FromMap( + "this is not a map", + 200*time.Millisecond, 10*time.Millisecond) + assert.ErrorIs(t, err, ErrValueNoMap) + + _, err = FromMap( + nil, + 200*time.Millisecond, 10*time.Millisecond) + assert.ErrorIs(t, err, ErrValueNoMap) + }) +} + func TestFlush(t *testing.T) { tm := New(dCleanupTick)