package internal import ( "context" "fmt" "log" "net/http" "regexp" "strings" "sync" "time" ee "git.markbailey.dev/cerbervs/joist/internal/errors" "github.com/go-chi/chi/v5" ) const suff = "route_cache" type RouteCacher interface { Store(string, string) error GetPath(string) (string, error) All() (map[string]string, error) } type routeCache struct { rx chi.Router rs map[string]string s Storer } var ( rcLock sync.Mutex rcOnce sync.Once c *routeCache ) func NewRouteCache(store Storer, router chi.Router) (*routeCache, error) { rcOnce.Do(func() { if store == nil { st, err := NewBadgerStore(WithSubDir("route_cache")) if err != nil { log.Println(err) return } store = st } c = &routeCache{ rx: router, rs: make(map[string]string), s: store, } }) if c == nil { return nil, ee.ErrCacheUninitialized } rcLock.Lock() defer rcLock.Unlock() return c, nil } func (r *routeCache) All() (map[string]string, error) { if r.rs == nil { return nil, ee.ErrCacheUninitialized } return r.rs, nil } func (r *routeCache) Store(name string, path string) error { return nil } func (r *routeCache) GetPath(name string) (string, error) { if path, ok := r.rs[name]; ok { return path, nil } return string(""), ee.NewErrRouteNotFound(string(name)) } func (r *routeCache) UnmarshallFromStore() error { if err := chi.Walk(r.rx, walker(r.s)); err != nil { return err } return nil } func walker(store Storer) chi.WalkFunc { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { routeName, err := convertRouteToRouteName(route) if err != nil { return fmt.Errorf(`failed to name route "%s"`, route) } if routeName[len(routeName)-2:] != ".*" { route = regexp.MustCompile(`:.*}`).ReplaceAllString(route, "}") if len(route) > 1 { route = strings.TrimRight(route, "/") } store.Put(ctx, routeName, []byte(route), 1*time.Hour) } return nil } } func convertRouteToRouteName(route string) (string, error) { name := "app" if route == "/" { return name + ".home", nil } route = strings.ReplaceAll(route, "/", ".") route = strings.ReplaceAll(route, "-", "_") paramRegex := regexp.MustCompile(`\.{[^}]*}`) params := paramRegex.FindAllString(route, -1) route = paramRegex.ReplaceAllString(route, "") if len(params) > 0 { route += ".params" } name += strings.TrimRight(route, ".") return name, nil }