From 42a063182b3dde7ea49d4ac31b6dff04c763ff96 Mon Sep 17 00:00:00 2001 From: Mark Bailey Date: Sun, 31 Aug 2025 05:54:41 -0400 Subject: [PATCH 1/5] feat: add badger store and route cacher --- go.mod | 25 +++++ go.sum | 98 +++++++++++++++++ internal/badger.go | 139 ++++++++++++++++++++++++ internal/badger_test.go | 187 +++++++++++++++++++++++++++++++++ internal/errors/errors.go | 38 +++++++ internal/errors/errors_test.go | 101 ++++++++++++++++++ internal/route_cache.go | 66 ++++++++++++ route_cache.go | 8 ++ 8 files changed, 662 insertions(+) create mode 100644 go.sum create mode 100644 internal/badger.go create mode 100644 internal/badger_test.go create mode 100644 internal/errors/errors.go create mode 100644 internal/errors/errors_test.go create mode 100644 internal/route_cache.go create mode 100644 route_cache.go diff --git a/go.mod b/go.mod index 44a5a09..003abe6 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,28 @@ module git.markbailey.dev/cerbervs/joist go 1.24.6 + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v4 v4.8.0 // indirect + github.com/dgraph-io/ristretto v0.0.2 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-chi/chi/v5 v5.2.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/pkg/errors v0.8.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1761cc3 --- /dev/null +++ b/go.sum @@ -0,0 +1,98 @@ +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= +github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +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/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +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/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/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/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/badger.go b/internal/badger.go new file mode 100644 index 0000000..e98d108 --- /dev/null +++ b/internal/badger.go @@ -0,0 +1,139 @@ +package internal + +import ( + "context" + "os" + "path/filepath" + "time" + + "git.markbailey.dev/cerbervs/joist/internal/errors" + "github.com/dgraph-io/badger/v4" +) + +const defaultBaseDir = "./data/badger" + +type BadgerOpts func(*BadgerStore) + +func WithBaseDir(baseDir string) BadgerOpts { + return func(b *BadgerStore) { + b.baseDir = baseDir + } +} + +func WithSubDir(subDir string) BadgerOpts { + return func(b *BadgerStore) { + b.subDir = subDir + } +} + +type BadgerStore struct { + baseDir string + subDir string + db *badger.DB +} + +func (b *BadgerStore) Put(ctx context.Context, key string, val []byte, ttl time.Duration) error { + done := make(chan error, 1) + + go func() { + err := b.db.Update(func(txn *badger.Txn) error { + e := badger.NewEntry([]byte(key), val).WithTTL(ttl) + return txn.SetEntry(e) + }) + done <- err + }() + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-done: + return err + } +} + +func (b *BadgerStore) Get(ctx context.Context, key string) ([]byte, error) { + done := make(chan struct { + val []byte + err error + }, 1) + + go func() { + var valCopy []byte + err := b.db.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(key)) + if err != nil { + return err + } + valCopy, err = item.ValueCopy(nil) + return err + }) + done <- struct { + val []byte + err error + }{valCopy, err} + }() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-done: + return result.val, result.err + } +} + +func (b *BadgerStore) Delete(ctx context.Context, key string) error { + done := make(chan error, 1) + + go func() { + err := b.db.Update(func(txn *badger.Txn) error { + return txn.Delete([]byte(key)) + }) + done <- err + }() + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-done: + return err + } +} + +func (b *BadgerStore) Close() error { + return b.db.Close() +} + +func (b *BadgerStore) dir() string { + if b.baseDir == "" { + b.baseDir = defaultBaseDir + } + + if b.subDir == "" { + return b.baseDir + } + + return filepath.Join(b.baseDir, b.subDir) +} + +func NewBadgerStore(opts ...BadgerOpts) (*BadgerStore, error) { + s := &BadgerStore{} + + for _, opt := range opts { + opt(s) + } + + fi, err := os.Stat(s.dir()) + if err == nil && !fi.IsDir() { + if err := os.MkdirAll(s.dir(), 0755); err != nil { + return nil, errors.NewErrBadgerInit(s.baseDir, s.subDir) + } + } + + bopts := badger.DefaultOptions(s.dir()) + s.db, err = badger.Open(bopts) + if err != nil { + return nil, err + } + + return s, nil +} diff --git a/internal/badger_test.go b/internal/badger_test.go new file mode 100644 index 0000000..ae6bae6 --- /dev/null +++ b/internal/badger_test.go @@ -0,0 +1,187 @@ +package internal + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" +) + +func newTestStore(t *testing.T) *BadgerStore { + t.Helper() + + dir := filepath.Join(os.TempDir(), "badger_test", time.Now().Format("20060102150405")) + store, err := NewBadgerStore(WithBaseDir(dir)) + if err != nil { + t.Fatalf("failed to create store: %v", err) + } + + t.Cleanup(func() { + store.Close() + os.RemoveAll(dir) + }) + + return store +} + +// generic subtest runner +type subtest struct { + name string + run func(t *testing.T) +} + +func runSubtests(t *testing.T, subs []subtest) { + for _, st := range subs { + t.Run(st.name, st.run) + } +} + +func TestBadgerStore(t *testing.T) { + tests := []struct { + name string + test func(t *testing.T, store *BadgerStore) + }{ + { + name: "Put/Get/Delete roundtrip", + test: func(t *testing.T, store *BadgerStore) { + ctx := context.Background() + key := "foo" + val := []byte("bar") + + runSubtests(t, []subtest{ + { + name: "Put", + run: func(t *testing.T) { + if err := store.Put(ctx, key, val, time.Minute); err != nil { + t.Fatalf("Put failed: %v", err) + } + }, + }, + { + name: "Get", + run: func(t *testing.T) { + got, err := store.Get(ctx, key) + if err != nil { + t.Fatalf("Get failed: %v", err) + } + if string(got) != string(val) { + t.Errorf("Get returned %q, want %q", got, val) + } + }, + }, + { + name: "Delete", + run: func(t *testing.T) { + if err := store.Delete(ctx, key); err != nil { + t.Fatalf("Delete failed: %v", err) + } + }, + }, + { + name: "Get after delete", + run: func(t *testing.T) { + _, err := store.Get(ctx, key) + if err == nil { + t.Errorf("expected error after delete, got nil") + } + }, + }, + }) + }, + }, + { + name: "PutWithTTL expires", + test: func(t *testing.T, store *BadgerStore) { + ctx := context.Background() + key := "ttl" + val := []byte("value") + + if err := store.Put(ctx, key, val, time.Second); err != nil { + t.Fatalf("Put failed: %v", err) + } + + // Should exist immediately + if _, err := store.Get(ctx, key); err != nil { + t.Fatalf("Get failed immediately: %v", err) + } + + // Wait for TTL + time.Sleep(2 * time.Second) + + _, err := store.Get(ctx, key) + if err == nil { + t.Errorf("expected error after TTL expiry, got nil") + } + }, + }, + { + name: "ContextCancellation affects operations", + test: func(t *testing.T, store *BadgerStore) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + runSubtests(t, []subtest{ + { + name: "Put fails", + run: func(t *testing.T) { + if err := store.Put(ctx, "x", []byte("y"), time.Minute); err == nil { + t.Errorf("expected Put to fail with canceled context") + } + }, + }, + { + name: "Get fails", + run: func(t *testing.T) { + if _, err := store.Get(ctx, "x"); err == nil { + t.Errorf("expected Get to fail with canceled context") + } + }, + }, + { + name: "Delete fails", + run: func(t *testing.T) { + if err := store.Delete(ctx, "x"); err == nil { + t.Errorf("expected Delete to fail with canceled context") + } + }, + }, + }) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := newTestStore(t) + tt.test(t, store) + }) + } +} + +func TestDirFunction(t *testing.T) { + tests := []struct { + name string + store *BadgerStore + want string + }{ + { + name: "default directory", + store: &BadgerStore{}, + want: defaultBaseDir, + }, + { + name: "with base and sub dir", + store: &BadgerStore{baseDir: "/tmp", subDir: "foo"}, + want: filepath.Join("/tmp", "foo"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.store.dir(); got != tt.want { + t.Errorf("expected dir %q, got %q", tt.want, got) + } + }) + } +} diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 0000000..7e331c1 --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,38 @@ +package errors + +import ( + "errors" + "fmt" + "path/filepath" +) + +var ErrCacheUninitialized = errors.New("the route cache is uninitialized") + +type ErrRouteNotFound struct { + n string +} + +func (e ErrRouteNotFound) Error() string { + return "route not found: " + string(e.n) +} + +func NewErrRouteNotFound(n string) error { + return &ErrRouteNotFound{n: n} +} + +type ErrBadgerInit struct { + baseDir string + subDir string +} + +func (e ErrBadgerInit) Error() string { + if e.subDir == "" { + return fmt.Sprintf("failed to initialize badger store at base directory: %s", e.baseDir) + } + + return fmt.Sprintf("failed to initialize badger store at directory: %s", filepath.Join(e.baseDir, e.subDir)) +} + +func NewErrBadgerInit(baseDir, subDir string) error { + return &ErrBadgerInit{baseDir: baseDir, subDir: subDir} +} diff --git a/internal/errors/errors_test.go b/internal/errors/errors_test.go new file mode 100644 index 0000000..52dbcab --- /dev/null +++ b/internal/errors/errors_test.go @@ -0,0 +1,101 @@ +package errors + +import ( + "errors" + "path/filepath" + "testing" +) + +func TestErrRouteNotFound(t *testing.T) { + tests := []struct { + name string + route string + expected string + }{ + { + name: "empty route name", + route: "", + expected: "route not found: ", + }, + { + name: "simple route name", + route: "home", + expected: "route not found: home", + }, + { + name: "complex route name", + route: "/api/v1/resource", + expected: "route not found: /api/v1/resource", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := NewErrRouteNotFound(tt.route) + if err.Error() != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, err.Error()) + } + + // also check that errors.As works + var target *ErrRouteNotFound + if !errors.As(err, &target) { + t.Errorf("expected error to be of type ErrRouteNotFound") + } + }) + } +} + +func TestErrBadgerInit(t *testing.T) { + tests := []struct { + name string + baseDir string + subDir string + expected string + }{ + { + name: "base dir only", + baseDir: "/tmp/data", + subDir: "", + expected: "failed to initialize badger store at base directory: /tmp/data", + }, + { + name: "base dir with sub dir", + baseDir: "/tmp/data", + subDir: "auth", + expected: "failed to initialize badger store at directory: " + filepath.Join("/tmp/data", "auth"), + }, + { + name: "empty paths", + baseDir: "", + subDir: "", + expected: "failed to initialize badger store at base directory: ", + }, + { + name: "empty base dir with sub dir", + baseDir: "", + subDir: "cache", + expected: "failed to initialize badger store at directory: " + filepath.Join("", "cache"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := NewErrBadgerInit(tt.baseDir, tt.subDir) + if err.Error() != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, err.Error()) + } + + // also check that errors.As works + var target *ErrBadgerInit + if !errors.As(err, &target) { + t.Errorf("expected error to be of type ErrBadgerInit") + } + }) + } +} + +func TestErrCacheUninitialized(t *testing.T) { + if ErrCacheUninitialized.Error() != "the route cache is uninitialized" { + t.Errorf("unexpected error message: %q", ErrCacheUninitialized.Error()) + } +} diff --git a/internal/route_cache.go b/internal/route_cache.go new file mode 100644 index 0000000..2e421e4 --- /dev/null +++ b/internal/route_cache.go @@ -0,0 +1,66 @@ +package internal + +import ( + "log" + "sync" + + "git.markbailey.dev/cerbervs/joist/internal/errors" +) + +const suff = "route_cache" + +type routeCache struct { + rs map[string]string + s *BadgerStore +} + +var ( + rcLock sync.Mutex + rcOnce sync.Once + c *routeCache +) + +func NewRouteCache() (*routeCache, error) { + rcOnce.Do(func() { + store, err := NewBadgerStore(WithSubDir("route_cache")) + if err != nil { + log.Printf("failed to create badger store for route cache: %v", err) + c = nil + return + } + + c = &routeCache{ + rs: make(map[string]string), + s: store, + } + }) + + if c == nil { + return nil, errors.ErrCacheUninitialized + } + + rcLock.Lock() + defer rcLock.Unlock() + + return c, nil +} + +func (r *routeCache) All() (map[string]string, error) { + if r.rs == nil { + return nil, errors.ErrCacheUninitialized + } + return r.rs, nil +} + +func (r *routeCache) Store(name string, path string) error { + // TODO: implement + return nil +} + +func (r *routeCache) GetPath(name string) (string, error) { + if path, ok := r.rs[name]; ok { + return path, nil + } + + return string(""), errors.NewErrRouteNotFound(string(name)) +} diff --git a/route_cache.go b/route_cache.go new file mode 100644 index 0000000..7d94d00 --- /dev/null +++ b/route_cache.go @@ -0,0 +1,8 @@ +// Package joist implements the main Joist framework. +package joist + +type RouteCacher interface { + Store(string, string) error + GetPath(string) (string, error) + All() (map[string]string, error) +} From e42b1d06e9207a886f54d9e7007429b616c2ebfb Mon Sep 17 00:00:00 2001 From: Mark Bailey Date: Sun, 31 Aug 2025 06:41:53 -0400 Subject: [PATCH 2/5] refactor: cleanup --- internal/badger.go | 29 ++++++++++++++++++----------- internal/badger_test.go | 16 ++++++++-------- internal/route_cache.go | 24 ++++++++++++++++-------- internal/route_cache_test.go | 1 + route_cache.go | 8 -------- 5 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 internal/route_cache_test.go delete mode 100644 route_cache.go diff --git a/internal/badger.go b/internal/badger.go index e98d108..e694f4c 100644 --- a/internal/badger.go +++ b/internal/badger.go @@ -12,27 +12,34 @@ import ( const defaultBaseDir = "./data/badger" -type BadgerOpts func(*BadgerStore) +type Storer interface { + Close() error + Delete(context.Context, string) error + Get(context.Context, string) ([]byte, error) + Put(context.Context, string, []byte, time.Duration) error +} + +type BadgerOpts func(*badgerStore) func WithBaseDir(baseDir string) BadgerOpts { - return func(b *BadgerStore) { + return func(b *badgerStore) { b.baseDir = baseDir } } func WithSubDir(subDir string) BadgerOpts { - return func(b *BadgerStore) { + return func(b *badgerStore) { b.subDir = subDir } } -type BadgerStore struct { +type badgerStore struct { baseDir string subDir string db *badger.DB } -func (b *BadgerStore) Put(ctx context.Context, key string, val []byte, ttl time.Duration) error { +func (b *badgerStore) Put(ctx context.Context, key string, val []byte, ttl time.Duration) error { done := make(chan error, 1) go func() { @@ -51,7 +58,7 @@ func (b *BadgerStore) Put(ctx context.Context, key string, val []byte, ttl time. } } -func (b *BadgerStore) Get(ctx context.Context, key string) ([]byte, error) { +func (b *badgerStore) Get(ctx context.Context, key string) ([]byte, error) { done := make(chan struct { val []byte err error @@ -81,7 +88,7 @@ func (b *BadgerStore) Get(ctx context.Context, key string) ([]byte, error) { } } -func (b *BadgerStore) Delete(ctx context.Context, key string) error { +func (b *badgerStore) Delete(ctx context.Context, key string) error { done := make(chan error, 1) go func() { @@ -99,11 +106,11 @@ func (b *BadgerStore) Delete(ctx context.Context, key string) error { } } -func (b *BadgerStore) Close() error { +func (b *badgerStore) Close() error { return b.db.Close() } -func (b *BadgerStore) dir() string { +func (b *badgerStore) dir() string { if b.baseDir == "" { b.baseDir = defaultBaseDir } @@ -115,8 +122,8 @@ func (b *BadgerStore) dir() string { return filepath.Join(b.baseDir, b.subDir) } -func NewBadgerStore(opts ...BadgerOpts) (*BadgerStore, error) { - s := &BadgerStore{} +func NewBadgerStore(opts ...BadgerOpts) (*badgerStore, error) { + s := &badgerStore{} for _, opt := range opts { opt(s) diff --git a/internal/badger_test.go b/internal/badger_test.go index ae6bae6..132c2ea 100644 --- a/internal/badger_test.go +++ b/internal/badger_test.go @@ -8,7 +8,7 @@ import ( "time" ) -func newTestStore(t *testing.T) *BadgerStore { +func newTestStore(t *testing.T) *badgerStore { t.Helper() dir := filepath.Join(os.TempDir(), "badger_test", time.Now().Format("20060102150405")) @@ -40,11 +40,11 @@ func runSubtests(t *testing.T, subs []subtest) { func TestBadgerStore(t *testing.T) { tests := []struct { name string - test func(t *testing.T, store *BadgerStore) + test func(t *testing.T, store *badgerStore) }{ { name: "Put/Get/Delete roundtrip", - test: func(t *testing.T, store *BadgerStore) { + test: func(t *testing.T, store *badgerStore) { ctx := context.Background() key := "foo" val := []byte("bar") @@ -92,7 +92,7 @@ func TestBadgerStore(t *testing.T) { }, { name: "PutWithTTL expires", - test: func(t *testing.T, store *BadgerStore) { + test: func(t *testing.T, store *badgerStore) { ctx := context.Background() key := "ttl" val := []byte("value") @@ -117,7 +117,7 @@ func TestBadgerStore(t *testing.T) { }, { name: "ContextCancellation affects operations", - test: func(t *testing.T, store *BadgerStore) { + test: func(t *testing.T, store *badgerStore) { ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -162,17 +162,17 @@ func TestBadgerStore(t *testing.T) { func TestDirFunction(t *testing.T) { tests := []struct { name string - store *BadgerStore + store *badgerStore want string }{ { name: "default directory", - store: &BadgerStore{}, + store: &badgerStore{}, want: defaultBaseDir, }, { name: "with base and sub dir", - store: &BadgerStore{baseDir: "/tmp", subDir: "foo"}, + store: &badgerStore{baseDir: "/tmp", subDir: "foo"}, want: filepath.Join("/tmp", "foo"), }, } diff --git a/internal/route_cache.go b/internal/route_cache.go index 2e421e4..cf31f87 100644 --- a/internal/route_cache.go +++ b/internal/route_cache.go @@ -9,9 +9,15 @@ import ( const suff = "route_cache" +type RouteCacher interface { + Store(string, string) error + GetPath(string) (string, error) + All() (map[string]string, error) +} + type routeCache struct { rs map[string]string - s *BadgerStore + s Storer } var ( @@ -20,13 +26,16 @@ var ( c *routeCache ) -func NewRouteCache() (*routeCache, error) { +func NewRouteCache(store Storer) (*routeCache, error) { rcOnce.Do(func() { - store, err := NewBadgerStore(WithSubDir("route_cache")) - if err != nil { - log.Printf("failed to create badger store for route cache: %v", err) - c = nil - return + if store == nil { + st, err := NewBadgerStore(WithSubDir("route_cache")) + if err != nil { + log.Println(err) + return + } + + store = st } c = &routeCache{ @@ -53,7 +62,6 @@ func (r *routeCache) All() (map[string]string, error) { } func (r *routeCache) Store(name string, path string) error { - // TODO: implement return nil } diff --git a/internal/route_cache_test.go b/internal/route_cache_test.go new file mode 100644 index 0000000..5bf0569 --- /dev/null +++ b/internal/route_cache_test.go @@ -0,0 +1 @@ +package internal diff --git a/route_cache.go b/route_cache.go deleted file mode 100644 index 7d94d00..0000000 --- a/route_cache.go +++ /dev/null @@ -1,8 +0,0 @@ -// Package joist implements the main Joist framework. -package joist - -type RouteCacher interface { - Store(string, string) error - GetPath(string) (string, error) - All() (map[string]string, error) -} From 51f9e71c4e79d89b3063552de8a87455ccd83bdc Mon Sep 17 00:00:00 2001 From: Mark Bailey Date: Sun, 31 Aug 2025 07:09:43 -0400 Subject: [PATCH 3/5] refactor(route cache): clean and improve, add route -> name conversion and use Storer to persist route cache --- internal/route_cache.go | 71 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/internal/route_cache.go b/internal/route_cache.go index cf31f87..9524140 100644 --- a/internal/route_cache.go +++ b/internal/route_cache.go @@ -1,10 +1,17 @@ package internal import ( + "context" + "fmt" "log" + "net/http" + "regexp" + "strings" "sync" + "time" - "git.markbailey.dev/cerbervs/joist/internal/errors" + ee "git.markbailey.dev/cerbervs/joist/internal/errors" + "github.com/go-chi/chi/v5" ) const suff = "route_cache" @@ -16,6 +23,7 @@ type RouteCacher interface { } type routeCache struct { + rx chi.Router rs map[string]string s Storer } @@ -26,7 +34,7 @@ var ( c *routeCache ) -func NewRouteCache(store Storer) (*routeCache, error) { +func NewRouteCache(store Storer, router chi.Router) (*routeCache, error) { rcOnce.Do(func() { if store == nil { st, err := NewBadgerStore(WithSubDir("route_cache")) @@ -39,13 +47,14 @@ func NewRouteCache(store Storer) (*routeCache, error) { } c = &routeCache{ + rx: router, rs: make(map[string]string), s: store, } }) if c == nil { - return nil, errors.ErrCacheUninitialized + return nil, ee.ErrCacheUninitialized } rcLock.Lock() @@ -56,7 +65,7 @@ func NewRouteCache(store Storer) (*routeCache, error) { func (r *routeCache) All() (map[string]string, error) { if r.rs == nil { - return nil, errors.ErrCacheUninitialized + return nil, ee.ErrCacheUninitialized } return r.rs, nil } @@ -70,5 +79,57 @@ func (r *routeCache) GetPath(name string) (string, error) { return path, nil } - return string(""), errors.NewErrRouteNotFound(string(name)) + 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 } From 33f978a531c7e6ff88f46e3b6babd57e6300d6a7 Mon Sep 17 00:00:00 2001 From: Mark Bailey Date: Sun, 31 Aug 2025 07:18:02 -0400 Subject: [PATCH 4/5] chore(mod): mod tidy --- go.mod | 13 ++++------- go.sum | 73 ++++++++-------------------------------------------------- 2 files changed, 15 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 003abe6..a3335cc 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,18 @@ module git.markbailey.dev/cerbervs/joist go 1.24.6 require ( - github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/cespare/xxhash v1.1.0 // indirect + github.com/dgraph-io/badger/v4 v4.8.0 + github.com/go-chi/chi/v5 v5.2.3 +) + +require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dgraph-io/badger v1.6.2 // indirect - github.com/dgraph-io/badger/v4 v4.8.0 // indirect - github.com/dgraph-io/ristretto v0.0.2 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-chi/chi/v5 v5.2.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/protobuf v1.5.0 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/pkg/errors v0.8.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect diff --git a/go.sum b/go.sum index 1761cc3..7f8993a 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,15 @@ -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= -github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= -github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= 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/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -34,40 +17,16 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= @@ -76,23 +35,11 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= 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= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 0419251fd104482f80a5f17b2a1af44c100a38a5 Mon Sep 17 00:00:00 2001 From: Mark Bailey Date: Sun, 31 Aug 2025 19:30:53 -0400 Subject: [PATCH 5/5] refactor: cleanup and add stub --- internal/badger_test.go | 1 - internal/route_cache_test.go | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/badger_test.go b/internal/badger_test.go index 132c2ea..331b6e9 100644 --- a/internal/badger_test.go +++ b/internal/badger_test.go @@ -25,7 +25,6 @@ func newTestStore(t *testing.T) *badgerStore { return store } -// generic subtest runner type subtest struct { name string run func(t *testing.T) diff --git a/internal/route_cache_test.go b/internal/route_cache_test.go index 5bf0569..fccad65 100644 --- a/internal/route_cache_test.go +++ b/internal/route_cache_test.go @@ -1 +1,7 @@ package internal + +import "testing" + +func TestNewRouteCache(t *testing.T) { + // TODO: implement +}