Compare commits

..

No commits in common. "main" and "feat/routing" have entirely different histories.

12 changed files with 80 additions and 103 deletions

14
go.mod
View File

@ -9,17 +9,17 @@ require (
require ( require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.34.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect google.golang.org/protobuf v1.36.6 // indirect
) )

15
go.sum
View File

@ -6,8 +6,6 @@ github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yh
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@ -29,32 +27,19 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,13 +1,14 @@
package routing package joist
import ( import (
"fmt" "fmt"
"net/http" "net/http"
) )
type HandlerFn func(w http.ResponseWriter, r *http.Request) error type (
HandlerFn func(w http.ResponseWriter, r *http.Request) error
type Middlewares []func(http.ResponseWriter, *http.Request) MiddlewareFn func(HandlerFn) HandlerFn
)
func (h HandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h HandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil { if err := h(w, r); err != nil {

View File

@ -1,4 +1,4 @@
package storage package joist
import ( import (
"context" "context"
@ -12,3 +12,11 @@ type Storer interface {
GetForPrefix(ctx context.Context, prefix string) (map[string][]byte, error) GetForPrefix(ctx context.Context, prefix string) (map[string][]byte, error)
Put(ctx context.Context, key string, val []byte, ttl time.Duration) error Put(ctx context.Context, key string, val []byte, ttl time.Duration) error
} }
type RouteCacher interface {
Add(name string, path string) error
AddRouteInfo(routeInfo RouteInfo) error
GetPath(name string) (string, error)
GetRouteInfo(name string) (RouteInfo, error)
All() (map[string]string, error)
}

View File

@ -1,4 +1,4 @@
package storage package badger
import ( import (
"context" "context"

View File

@ -1,4 +1,4 @@
package storage package badger
import ( import (
"context" "context"

View File

@ -1,26 +0,0 @@
package routing
type RouteInfo struct {
Method string
Name string
Path string
Description string
Title string
}
func NewRouteInfo(method, path, description, title string) (*RouteInfo, error) {
name, err := convertRouteToRouteName(path)
if err != nil {
return &RouteInfo{}, err
}
return &RouteInfo{
Method: method,
Name: name,
Path: path,
Description: description,
Title: title,
}, nil
}
type routeInfoKey struct{}

View File

@ -1,20 +0,0 @@
package routing
import (
"log"
"time"
"git.markbailey.dev/cerbervs/joist/internal/errors"
chi "github.com/go-chi/chi/v5"
)
func NewRouter() *Router {
ttl := 8 * time.Hour
cache, err := NewRouteCacheService(WithTTL(ttl))
if err != nil {
log.Fatal(errors.Wrap(err, errors.ErrRouterNotCreated))
}
return &Router{Mux: chi.NewRouter(), cache: cache, cacheTTL: ttl}
}

View File

@ -1 +0,0 @@
package joist

View File

@ -1,4 +1,4 @@
package routing package joist
import ( import (
"context" "context"
@ -7,37 +7,37 @@ import (
"sync" "sync"
"time" "time"
"git.markbailey.dev/cerbervs/joist/internal/badger"
jerr "git.markbailey.dev/cerbervs/joist/internal/errors" jerr "git.markbailey.dev/cerbervs/joist/internal/errors"
"git.markbailey.dev/cerbervs/joist/internal/storage"
) )
type RouteCache struct { type routeCache struct {
rs map[string]string rs map[string]string
ris map[string]*RouteInfo ris map[string]*RouteInfo
s storage.Storer s Storer
ttl time.Duration ttl time.Duration
} }
type RouteCacheOpt func(*RouteCache) type RouteCacheOpt func(*routeCache)
var ( var (
rcOnce sync.Once rcOnce sync.Once
cache *RouteCache cache *routeCache
) )
func WithTTL(ttl time.Duration) RouteCacheOpt { func WithTTL(ttl time.Duration) RouteCacheOpt {
return func(r *RouteCache) { return func(r *routeCache) {
r.ttl = ttl r.ttl = ttl
} }
} }
func WithStore(store storage.Storer) RouteCacheOpt { func WithStore(store Storer) RouteCacheOpt {
return func(r *RouteCache) { return func(r *routeCache) {
r.s = store r.s = store
} }
} }
func NewRouteCacheService(opts ...RouteCacheOpt) (*RouteCache, error) { func NewRouteCacheService(opts ...RouteCacheOpt) (*routeCache, error) {
rcOnce.Do(func() { rcOnce.Do(func() {
c, err := newRouteCache(opts...) c, err := newRouteCache(opts...)
if err != nil { if err != nil {
@ -54,14 +54,14 @@ func NewRouteCacheService(opts ...RouteCacheOpt) (*RouteCache, error) {
return cache, nil return cache, nil
} }
func (r *RouteCache) All() (map[string]string, error) { func (r *routeCache) All() (map[string]string, error) {
if r.rs == nil { if r.rs == nil {
return nil, jerr.ErrCacheUninitialized return nil, jerr.ErrCacheUninitialized
} }
return r.rs, nil return r.rs, nil
} }
func (r *RouteCache) Add(name string, path string) error { func (r *routeCache) Add(name string, path string) error {
ttl := r.ttl ttl := r.ttl
if ttl == 0 { if ttl == 0 {
ttl = 1 * time.Hour ttl = 1 * time.Hour
@ -76,7 +76,7 @@ func (r *RouteCache) Add(name string, path string) error {
return nil return nil
} }
func (r *RouteCache) GetPath(name string) (string, error) { func (r *routeCache) GetPath(name string) (string, error) {
if path, ok := r.rs[name]; ok { if path, ok := r.rs[name]; ok {
return path, nil return path, nil
} }
@ -88,7 +88,7 @@ func (r *RouteCache) GetPath(name string) (string, error) {
return "", errors.New("route not found in cache") return "", errors.New("route not found in cache")
} }
func (r *RouteCache) AddRouteInfo(routeInfo RouteInfo) error { func (r *routeCache) AddRouteInfo(routeInfo RouteInfo) error {
ttl := r.ttl ttl := r.ttl
if ttl == 0 { if ttl == 0 {
ttl = 1 * time.Hour ttl = 1 * time.Hour
@ -113,7 +113,7 @@ func (r *RouteCache) AddRouteInfo(routeInfo RouteInfo) error {
return nil return nil
} }
func (r *RouteCache) GetRouteInfo(name string) (RouteInfo, error) { func (r *routeCache) GetRouteInfo(name string) (RouteInfo, error) {
if ri, ok := r.ris[name]; ok { if ri, ok := r.ris[name]; ok {
return *ri, nil return *ri, nil
} }
@ -131,13 +131,13 @@ func (r *RouteCache) GetRouteInfo(name string) (RouteInfo, error) {
return RouteInfo{}, errors.New("route info not found in cache") return RouteInfo{}, errors.New("route info not found in cache")
} }
func newRouteCache(opts ...RouteCacheOpt) (*RouteCache, error) { func newRouteCache(opts ...RouteCacheOpt) (*routeCache, error) {
s, err := storage.NewBadgerStore(storage.WithSubDir("route_cache")) s, err := badger.NewBadgerStore(badger.WithSubDir("route_cache"))
if err != nil { if err != nil {
return nil, jerr.Wrap(jerr.ErrFailedToCreateRouteCache, err) return nil, jerr.Wrap(jerr.ErrFailedToCreateRouteCache, err)
} }
cache := &RouteCache{ cache := &routeCache{
rs: make(map[string]string), rs: make(map[string]string),
s: s, s: s,
} }

View File

@ -1,4 +1,4 @@
package routing package joist
import "testing" import "testing"

View File

@ -1,29 +1,59 @@
package routing package joist
import ( import (
"context" "context"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"time" "time"
jerr "git.markbailey.dev/cerbervs/joist/internal/errors"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
type RouteCacher interface { type RouteInfo struct {
Add(string, string) error Method string
AddRouteInfo(RouteInfo) error Name string
GetPath(string) (string, error) Path string
GetRouteInfo(string) (RouteInfo, error) Description string
All() (map[string]string, error) Title string
} }
func NewRouteInfo(method, path, description, title string) (RouteInfo, error) {
name, err := convertRouteToRouteName(path)
if err != nil {
return RouteInfo{}, err
}
return RouteInfo{
Method: method,
Name: name,
Path: path,
Description: description,
Title: title,
}, nil
}
type routeInfoKey struct{}
type Router struct { type Router struct {
*chi.Mux chi.Mux
cache RouteCacher cache RouteCacher
cacheTTL time.Duration cacheTTL time.Duration
} }
func NewRouter() Router {
ttl := 8 * time.Hour
cache, err := NewRouteCacheService(WithTTL(ttl))
if err != nil {
log.Fatal(jerr.Wrap(err, jerr.ErrRouterNotCreated))
}
return Router{cache: cache, cacheTTL: ttl}
}
func (ro *Router) WithRouteInfo(ri RouteInfo, h HandlerFn) http.HandlerFunc { func (ro *Router) WithRouteInfo(ri RouteInfo, h HandlerFn) http.HandlerFunc {
ro.cache.AddRouteInfo(ri) ro.cache.AddRouteInfo(ri)