@@ -13,20 +13,20 @@ import (
13
13
var NotFound = errors .New ("not found" )
14
14
var Expired = errors .New ("expired" )
15
15
16
- type cache struct {
16
+ type Cache [ T any ] struct {
17
17
domain string
18
18
cacheDir string
19
19
}
20
20
21
- type data struct {
22
- Val [] byte
21
+ type data [ T any ] struct {
22
+ Val T
23
23
Exp time.Time
24
24
}
25
25
26
- type Option func (* cache )
26
+ type Option [ T any ] func (* Cache [ T ] )
27
27
28
- func New (domain string , opts ... Option ) * cache {
29
- result := & cache {domain : domain }
28
+ func New [ T any ] (domain string , opts ... Option [ T ] ) * Cache [ T ] {
29
+ result := & Cache [ T ] {domain : domain }
30
30
31
31
var err error
32
32
result .cacheDir , err = os .UserCacheDir ()
@@ -41,56 +41,102 @@ func New(domain string, opts ...Option) *cache {
41
41
return result
42
42
}
43
43
44
- func WithCacheDir (dir string ) Option {
45
- return func (c * cache ) {
44
+ func WithCacheDir [ T any ] (dir string ) Option [ T ] {
45
+ return func (c * Cache [ T ] ) {
46
46
c .cacheDir = dir
47
47
}
48
48
}
49
49
50
- func (c * cache ) Set (key string , val []byte , dur time.Duration ) error {
51
- d , err := json .Marshal (data {Val : val , Exp : time .Now ().Add (dur )})
50
+ // Set stores a value in the cache with the given key and expiration duration.
51
+ func (c * Cache [T ]) Set (key string , val T , dur time.Duration ) error {
52
+ d , err := json .Marshal (data [T ]{Val : val , Exp : time .Now ().Add (dur )})
52
53
if err != nil {
53
54
return errors .WithStack (err )
54
55
}
55
56
56
57
return errors .WithStack (os .WriteFile (c .filename (key ), d , 0644 ))
57
58
}
58
59
59
- func (c * cache ) SetT (key string , val []byte , t time.Time ) error {
60
- d , err := json .Marshal (data {Val : val , Exp : t })
60
+ // SetWithTime is like Set but it allows the caller to specify the expiration
61
+ // time of the value.
62
+ func (c * Cache [T ]) SetWithTime (key string , val T , t time.Time ) error {
63
+ d , err := json .Marshal (data [T ]{Val : val , Exp : t })
61
64
if err != nil {
62
65
return errors .WithStack (err )
63
66
}
64
67
65
68
return errors .WithStack (os .WriteFile (c .filename (key ), d , 0644 ))
66
69
}
67
70
68
- func (c * cache ) Get (key string ) ([]byte , error ) {
71
+ // Get retrieves a value from the cache with the given key.
72
+ func (c * Cache [T ]) Get (key string ) (T , error ) {
69
73
path := c .filename (key )
74
+ resultData := data [T ]{}
75
+
70
76
if _ , err := os .Stat (path ); errors .Is (err , os .ErrNotExist ) {
71
- return nil , NotFound
77
+ return resultData . Val , NotFound
72
78
}
73
79
74
80
content , err := os .ReadFile (path )
75
81
if err != nil {
76
- return nil , errors .WithStack (err )
82
+ return resultData . Val , errors .WithStack (err )
77
83
}
78
- d := data {}
79
- if err := json .Unmarshal (content , & d ); err != nil {
80
- return nil , errors .WithStack (err )
84
+
85
+ if err := json .Unmarshal (content , & resultData ); err != nil {
86
+ return resultData . Val , errors .WithStack (err )
81
87
}
82
- if time .Now ().After (d .Exp ) {
83
- return nil , Expired
88
+ if time .Now ().After (resultData .Exp ) {
89
+ return resultData . Val , Expired
84
90
}
85
- return d .Val , nil
91
+ return resultData .Val , nil
86
92
}
87
93
88
- func (c * cache ) filename (key string ) string {
89
- dir := filepath .Join (c .cacheDir , c .domain )
90
- _ = os .MkdirAll (dir , 0755 )
91
- return filepath .Join (dir , key )
94
+ // GetOrSet is a convenience method that gets the value from the cache if it
95
+ // exists, otherwise it calls the provided function to get the value and sets
96
+ // it in the cache.
97
+ // If the function returns an error, the error is returned and the value is not
98
+ // cached.
99
+ func (c * Cache [T ]) GetOrSet (
100
+ key string ,
101
+ f func () (T , time.Duration , error ),
102
+ ) (T , error ) {
103
+ if val , err := c .Get (key ); err == nil || ! IsCacheMiss (err ) {
104
+ return val , err
105
+ }
106
+
107
+ val , dur , err := f ()
108
+ if err != nil {
109
+ return val , err
110
+ }
111
+
112
+ return val , c .Set (key , val , dur )
113
+ }
114
+
115
+ // GetOrSetWithTime is like GetOrSet but it allows the caller to specify the
116
+ // expiration time of the value.
117
+ func (c * Cache [T ]) GetOrSetWithTime (
118
+ key string ,
119
+ f func () (T , time.Time , error ),
120
+ ) (T , error ) {
121
+ if val , err := c .Get (key ); err == nil || ! IsCacheMiss (err ) {
122
+ return val , err
123
+ }
124
+
125
+ val , t , err := f ()
126
+ if err != nil {
127
+ return val , err
128
+ }
129
+
130
+ return val , c .SetWithTime (key , val , t )
92
131
}
93
132
133
+ // IsCacheMiss returns true if the error is NotFound or Expired.
94
134
func IsCacheMiss (err error ) bool {
95
135
return errors .Is (err , NotFound ) || errors .Is (err , Expired )
96
136
}
137
+
138
+ func (c * Cache [T ]) filename (key string ) string {
139
+ dir := filepath .Join (c .cacheDir , c .domain )
140
+ _ = os .MkdirAll (dir , 0755 )
141
+ return filepath .Join (dir , key )
142
+ }
0 commit comments