@@ -13,20 +13,20 @@ import (
1313var NotFound = errors .New ("not found" )
1414var Expired = errors .New ("expired" )
1515
16- type cache struct {
16+ type Cache [ T any ] struct {
1717 domain string
1818 cacheDir string
1919}
2020
21- type data struct {
22- Val [] byte
21+ type data [ T any ] struct {
22+ Val T
2323 Exp time.Time
2424}
2525
26- type Option func (* cache )
26+ type Option [ T any ] func (* Cache [ T ] )
2727
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 }
3030
3131 var err error
3232 result .cacheDir , err = os .UserCacheDir ()
@@ -41,56 +41,102 @@ func New(domain string, opts ...Option) *cache {
4141 return result
4242}
4343
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 ] ) {
4646 c .cacheDir = dir
4747 }
4848}
4949
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 )})
5253 if err != nil {
5354 return errors .WithStack (err )
5455 }
5556
5657 return errors .WithStack (os .WriteFile (c .filename (key ), d , 0644 ))
5758}
5859
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 })
6164 if err != nil {
6265 return errors .WithStack (err )
6366 }
6467
6568 return errors .WithStack (os .WriteFile (c .filename (key ), d , 0644 ))
6669}
6770
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 ) {
6973 path := c .filename (key )
74+ resultData := data [T ]{}
75+
7076 if _ , err := os .Stat (path ); errors .Is (err , os .ErrNotExist ) {
71- return nil , NotFound
77+ return resultData . Val , NotFound
7278 }
7379
7480 content , err := os .ReadFile (path )
7581 if err != nil {
76- return nil , errors .WithStack (err )
82+ return resultData . Val , errors .WithStack (err )
7783 }
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 )
8187 }
82- if time .Now ().After (d .Exp ) {
83- return nil , Expired
88+ if time .Now ().After (resultData .Exp ) {
89+ return resultData . Val , Expired
8490 }
85- return d .Val , nil
91+ return resultData .Val , nil
8692}
8793
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 )
92131}
93132
133+ // IsCacheMiss returns true if the error is NotFound or Expired.
94134func IsCacheMiss (err error ) bool {
95135 return errors .Is (err , NotFound ) || errors .Is (err , Expired )
96136}
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