joist/route_cache.go
2025-09-06 17:56:59 -04:00

151 lines
3.0 KiB
Go

package joist
import (
"context"
"errors"
"log"
"sync"
"time"
"git.markbailey.dev/cerbervs/joist/internal/badger"
jerr "git.markbailey.dev/cerbervs/joist/internal/errors"
)
type routeCache struct {
rs map[string]string
ris map[string]*RouteInfo
s Storer
ttl time.Duration
}
type RouteCacheOpt func(*routeCache)
var (
rcOnce sync.Once
cache *routeCache
)
func WithTTL(ttl time.Duration) RouteCacheOpt {
return func(r *routeCache) {
r.ttl = ttl
}
}
func WithStore(store Storer) RouteCacheOpt {
return func(r *routeCache) {
r.s = store
}
}
func NewRouteCacheService(opts ...RouteCacheOpt) (*routeCache, error) {
rcOnce.Do(func() {
c, err := newRouteCache(opts...)
if err != nil {
log.Fatal(err)
}
cache = c
})
if cache == nil {
return nil, jerr.ErrCacheUninitialized
}
return cache, nil
}
func (r *routeCache) All() (map[string]string, error) {
if r.rs == nil {
return nil, jerr.ErrCacheUninitialized
}
return r.rs, nil
}
func (r *routeCache) Add(name string, path string) error {
ttl := r.ttl
if ttl == 0 {
ttl = 1 * time.Hour
}
if err := r.s.Put(context.Background(), name, []byte(path), ttl); err != nil {
return err
}
r.rs[name] = path
return nil
}
func (r *routeCache) GetPath(name string) (string, error) {
if path, ok := r.rs[name]; ok {
return path, nil
}
if path, err := r.s.Get(context.Background(), name); err == nil {
return string(path), nil
}
return "", errors.New("route not found in cache")
}
func (r *routeCache) AddRouteInfo(routeInfo RouteInfo) error {
ttl := r.ttl
if ttl == 0 {
ttl = 1 * time.Hour
}
if err := r.s.Put(context.Background(), routeInfo.Name+":method", []byte(routeInfo.Method), ttl); err != nil {
return err
}
if err := r.s.Put(context.Background(), routeInfo.Name+":path", []byte(routeInfo.Path), ttl); err != nil {
return err
}
if err := r.s.Put(context.Background(), routeInfo.Name+":description", []byte(routeInfo.Description), ttl); err != nil {
return err
}
if err := r.s.Put(context.Background(), routeInfo.Name+":title", []byte(routeInfo.Title), ttl); err != nil {
return err
}
r.rs[routeInfo.Name] = routeInfo.Path
r.ris[routeInfo.Name] = &routeInfo
return nil
}
func (r *routeCache) GetRouteInfo(name string) (RouteInfo, error) {
if ri, ok := r.ris[name]; ok {
return *ri, nil
}
if info, err := r.s.GetForPrefix(context.Background(), name); err == nil {
return RouteInfo{
Method: string(info[":method"]),
Name: name,
Path: string(info[":path"]),
Description: string(info[":description"]),
Title: string(info[":title"]),
}, nil
}
return RouteInfo{}, errors.New("route info not found in cache")
}
func newRouteCache(opts ...RouteCacheOpt) (*routeCache, error) {
s, err := badger.NewBadgerStore(badger.WithSubDir("route_cache"))
if err != nil {
return nil, jerr.Wrap(jerr.ErrFailedToCreateRouteCache, err)
}
cache := &routeCache{
rs: make(map[string]string),
s: s,
}
for _, opt := range opts {
opt(cache)
}
return cache, nil
}