diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71f6a2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Dependency directories +vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +build/ +.task/ diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..273e2af --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,39 @@ +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - copyloopvar + - dupl + - err113 + - errname + - exptostd + - fatcontext + - funlen + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - gosec + - perfsprint + - testifylint + +linters-settings: + testifylint: + enable-all: true + disable: + - require-error + gocognit: + min-complexity: 5 + gocyclo: + min-complexity: 5 + gocritic: + enable-all: true + settings: + hugeParam: + sizeThreshold: 255 diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..ef9f12e --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,62 @@ +version: '3' + +tasks: + default: + cmds: + - task: fmt + - task: test + - task: lint + + fmt: + desc: Format go code + sources: + - '**/*.go' + cmds: + - go fmt ./... + - go mod tidy + + gen: + desc: Generate files + sources: + - '**/*.go' + cmds: + - go generate ./... + + lint: + desc: Do static analysis + sources: + - '**/*.go' + cmds: + - golangci-lint run + + test: + desc: Run unit tests + deps: [fmt] + sources: + - '**/*.go' + generates: + - build/cover.out + cmds: + - go test -race -cover -coverprofile build/cover.out ./... + + coverage-report: + desc: Build coverage report + deps: [test] + sources: + - build/cover.out + generates: + - build/cover.html + cmds: + - go tool cover -html=build/cover.out -o build/cover.html + + serve-report: + desc: Serve the coverage report + deps: [coverage-report] + cmds: + - ip addr list | grep inet + - php -S 0.0.0.0:3434 -t build + + serve-docs: + desc: Serve the current docs + cmds: + - godoc -http=0.0.0.0:3434 -play diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..720bebd --- /dev/null +++ b/cache.go @@ -0,0 +1,31 @@ +package ezcache + +import ( + "errors" + "time" +) + +// ErrInvalidFetcher is returned by Cache.SetFetcher if the fetcher is invalid. +// This is probably only going to happen if it's nil. +var ErrInvalidFetcher = errors.New("invalid fetcher") + +// ErrInvalidDuration is returned by Cache.SetExpiry if the duration is invalid. +// This is usually if it is <= 0. +var ErrInvalidDuration = errors.New("invalid duration") + +type Fetcher[K comparable, V any] func(K) (V, error) + +// Cache represents a Cache for values. +type Cache[K comparable, V any] interface { + // Get will fetch the value for key. If in the cache, it will fetch the cached value. + // If not in the cache, it will use the Fetcher. + // It may return [ErrInvalidFetcher], but if an error is returned, it's probably returned by the + // [Fetcher] itself. + Get(key K) (V, error) + + // SetFetcher sets the fetcher for this [Cache]. + SetFetcher(f Fetcher[K, V]) error + + // SetExpiry sets the expiry for this [Cache]. + SetExpiry(d time.Duration) error +} diff --git a/ezcache.go b/ezcache.go new file mode 100644 index 0000000..667be56 --- /dev/null +++ b/ezcache.go @@ -0,0 +1,52 @@ +package ezcache + +import ( + "errors" + "time" +) + +type ezc[K comparable, V any] struct { + fetch Fetcher[K, V] + exp time.Duration + cache map[K]V + setTime map[K]time.Time +} + +func New[K comparable, V any](fetcher Fetcher[K, V], exp time.Duration) (Cache[K, V], error) { + c := &ezc[K, V]{} + err := c.SetFetcher(fetcher) + if err != nil { + return nil, err + } + err = c.SetExpiry(exp) + if err != nil { + return nil, err + } + + c.cache = make(map[K]V) + c.setTime = make(map[K]time.Time) + return c, nil +} + +var errUnimpl = errors.New("unimplemented") + +func (c *ezc[K, V]) Get(key K) (V, error) { + var val V + return val, errUnimpl +} + +func (c *ezc[K, V]) SetFetcher(f Fetcher[K, V]) error { + if f == nil { + return ErrInvalidFetcher + } + c.fetch = f + return nil +} + +func (c *ezc[K, V]) SetExpiry(exp time.Duration) error { + if exp <= 0 { + return ErrInvalidDuration + } + c.exp = exp + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a4a9bfa --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module codeberg.org/danjones000/ezcache + +go 1.23.7