[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches:\n      - main\n      - develop\n  pull_request:\n    branches:\n      - main\n\njobs:\n  test:\n    strategy:\n      matrix:\n        go-version:\n          - '1.21.x'\n          - '1.22.x'\n          - '1.23.x'\n        platform: [ubuntu-latest]\n\n    name: test\n    runs-on: ${{ matrix.platform }}\n\n    services:\n      memcached:\n        image: memcached:alpine\n        ports:\n          - 11211:11211\n      redis:\n        image: redis:alpine\n        ports:\n          - 6379:6379\n      mongodb:\n        image: mongo:3.6\n        ports:\n          - 27017:27017\n\n    steps:\n      - name: checkout the code\n        uses: actions/checkout@v4\n\n      - name: setup go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go-version }}\n\n      - name: unshallow\n        run: git fetch --prune --unshallow\n\n      - name: golanci-linter\n        uses: golangci/golangci-lint-action@v6\n        with:\n          version: v1.64.7 \n\n      - name: run unit tests\n        run: make test\n\n      - name: upload code coverage\n        uses: codecov/codecov-action@v5\n        if: contains(github.ref, 'main')\n        with:\n          file: ./cover.out\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\n*.swo\ncover*\ncache-dir\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "---\nrun:\n  timeout: \"240s\"\n\noutput:\n  formats:\n    - format: \"colored-line-number\"\n\nlinters:\n  enable:\n    - gocyclo\n    - unconvert\n    - goimports\n    - unused\n    - misspell\n    - nakedret\n    - errcheck\n    - revive\n    - ineffassign\n    - goconst\n    - govet\n    - unparam\n    - gofumpt\n    - prealloc\n    - mnd\n    - gocritic\n\n\nlinters-settings:\n  revive:\n    rules:\n      - name: package-comments\n        disabled: true\n\nissues:\n  exclude-use-default: false\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nBy participating to this project, you agree to abide our [code of conduct](/CODE_OF_CONDUCT.md).\n\n## Setup your machine\n\n`cachego` is written in [Go](https://golang.org/).\n\nPrerequisites:\n\n* `make`\n* [Go 1.21+](https://golang.org/doc/install)\n\nClone `cachego` from source into `$GOPATH`:\n\n```sh\n$ mkdir -p $GOPATH/src/github.com/faabiosr\n$ cd $_\n$ git clone git@github.com:faabiosr/cachego.git\n$ cd cachego\n```\n\nInstall the build and lint dependencies:\n```console\n$ make depend\n```\n\nA good way of making sure everything is all right is running the test suite:\n```console\n$ make test\n```\n\n## Formatting the code\nFormat the code running:\n```console\n$ make fmt\n```\n\n## Create a commit\n\nCommit messages should be well formatted.\nStart your commit message with the type. Choose one of the following:\n`feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `revert`, `add`, `remove`, `move`, `bump`, `update`, `release`\n\nAfter a colon, you should give the message a title, starting with uppercase and ending without a dot.\nKeep the width of the text at 72 chars.\nThe title must be followed with a newline, then a more detailed description.\n\nPlease reference any GitHub issues on the last line of the commit message (e.g. `See #123`, `Closes #123`, `Fixes #123`).\n\nAn example:\n\n```\ndocs: Add example for --release-notes flag\n\nI added an example to the docs of the `--release-notes` flag to make\nthe usage more clear.  The example is an realistic use case and might\nhelp others to generate their own changelog.\n\nSee #284\n```\n\n## Submit a pull request\n\nPush your branch to your `cachego` fork and open a pull request against the\nmain branch.\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Fábio da Silva Ribeiro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".DEFAULT_GOAL := test\n\n# Clean up\nclean:\n\t@rm -fR ./vendor/ ./cover.*\n.PHONY: clean\n\n# Run tests and generates html coverage file\ncover: test\n\t@go tool cover -html=./cover.text -o ./cover.html\n\t@test -f ./cover.out && rm ./cover.out;\n.PHONY: cover\n\n# Up the docker container for testing\ndocker:\n\t@docker-compose up -d\n.PHONY: docker\n\n# Format all go files\nfmt:\n\t@gofmt -s -w -l $(shell go list -f {{.Dir}} ./...)\n.PHONY: fmt\n\n# Run linters\nlint:\n\t@golangci-lint run ./...\n.PHONY: lint\n\n# Run tests\ntest:\n\t@go test -v -race -coverprofile=./cover.text -covermode=atomic $(shell go list ./...)\n.PHONY: test\n"
  },
  {
    "path": "README.md",
    "content": "# Cachego\n\n[![Codecov branch](https://img.shields.io/codecov/c/github/faabiosr/cachego/main.svg?style=flat-square)](https://codecov.io/gh/faabiosr/cachego)\n[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://pkg.go.dev/github.com/faabiosr/cachego)\n[![Go Report Card](https://goreportcard.com/badge/github.com/faabiosr/cachego?style=flat-square)](https://goreportcard.com/report/github.com/faabiosr/cachego)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/faabiosr/cachego/blob/main/LICENSE)\n\nSimple interface for caching\n\n## Installation\n\nCachego requires Go 1.21 or later.\n\n```\ngo get github.com/faabiosr/cachego\n```\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego/sync\"\n)\n\nfunc main() {\n\tcache := sync.New()\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n\n\tkeys := cache.FetchMulti([]string{\"user_id\", \"user_name\"})\n\n\tfor k, v := range keys {\n\t\tlog.Printf(\"%s: %s\\n\", k, v)\n\t}\n\n\tif cache.Contains(\"user_name\") {\n\t\tcache.Delete(\"user_name\")\n\t}\n\n\tif _, err := cache.Fetch(\"user_name\"); err != nil {\n\t\tlog.Printf(\"%v\\n\", err)\n\t}\n\n\tif err := cache.Flush(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\n```\n\n## Supported drivers\n\n- [Bolt](/bolt)\n- [Chain](/chain)\n- [File](/file)\n- [Memcached](/memcached)\n- [Mongo](/mongo)\n- [Redis](/redis)\n- [Sqlite3](/sqlite3)\n- [Sync](/sync)\n\n\n## Documentation\n\nRead the full documentation at [https://pkg.go.dev/github.com/faabiosr/cachego](https://pkg.go.dev/github.com/faabiosr/cachego).\n\n## Development\n\n### Requirements\n\n- Install [docker](https://docs.docker.com/install/)\n- Install [docker-compose](https://docs.docker.com/compose/install/)\n\n### Makefile\n```sh\n// Clean up\n$ make clean\n\n//Run tests and generates html coverage file\n$ make cover\n\n// Up the docker containers for testing\n$ make docker\n\n// Format all go files\n$ make fmt\n\n//Run linters\n$ make lint\n\n// Run tests\n$ make test\n```\n\n## License\n\nThis project is released under the MIT licence. See [LICENSE](https://github.com/faabiosr/cachego/blob/main/LICENSE) for more details.\n"
  },
  {
    "path": "bolt/README.md",
    "content": "# Cachego - BoltDB driver\nThe drivers uses [etcd-io/bbolt](https://github.com/etcd-io/bbolt) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\tbt \"go.etcd.io/bbolt\"\n\n\t\"github.com/faabiosr/cachego/bolt\"\n)\n\nfunc main() {\n\tdb, err := bt.Open(\"cache.db\", 0600, nil)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcache := bolt.New(db)\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "bolt/bolt.go",
    "content": "// Package bolt providers a cache driver that stores the cache using BoltDB.\npackage bolt\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"time\"\n\n\tbt \"go.etcd.io/bbolt\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\nvar boltBucket = []byte(\"cachego\")\n\ntype (\n\tbolt struct {\n\t\tdb *bt.DB\n\t}\n\n\tboltContent struct {\n\t\tDuration int64  `json:\"duration\"`\n\t\tData     string `json:\"data,omitempty\"`\n\t}\n)\n\n// New creates an instance of BoltDB cache\nfunc New(db *bt.DB) cachego.Cache {\n\treturn &bolt{db}\n}\n\nfunc (b *bolt) read(key string) (*boltContent, error) {\n\tvar value []byte\n\n\terr := b.db.View(func(tx *bt.Tx) error {\n\t\tif bucket := tx.Bucket(boltBucket); bucket != nil {\n\t\t\tvalue = bucket.Get([]byte(key))\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errors.New(\"bucket not found\")\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcontent := &boltContent{}\n\tif err := json.Unmarshal(value, content); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif content.Duration == 0 {\n\t\treturn content, nil\n\t}\n\n\tif content.Duration <= time.Now().Unix() {\n\t\t_ = b.Delete(key)\n\t\treturn nil, cachego.ErrCacheExpired\n\t}\n\n\treturn content, nil\n}\n\n// Contains checks if the cached key exists into the BoltDB storage\nfunc (b *bolt) Contains(key string) bool {\n\t_, err := b.read(key)\n\treturn err == nil\n}\n\n// Delete the cached key from BoltDB storage\nfunc (b *bolt) Delete(key string) error {\n\treturn b.db.Update(func(tx *bt.Tx) error {\n\t\tif bucket := tx.Bucket(boltBucket); bucket != nil {\n\t\t\treturn bucket.Delete([]byte(key))\n\t\t}\n\n\t\treturn errors.New(\"bucket not found\")\n\t})\n}\n\n// Fetch retrieves the cached value from key of the BoltDB storage\nfunc (b *bolt) Fetch(key string) (string, error) {\n\tcontent, err := b.read(key)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn content.Data, nil\n}\n\n// FetchMulti retrieve multiple cached values from keys of the BoltDB storage\nfunc (b *bolt) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tfor _, key := range keys {\n\t\tif value, err := b.Fetch(key); err == nil {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the BoltDB storage\nfunc (b *bolt) Flush() error {\n\treturn b.db.Update(func(tx *bt.Tx) error {\n\t\treturn tx.DeleteBucket(boltBucket)\n\t})\n}\n\n// Save a value in BoltDB storage by key\nfunc (b *bolt) Save(key string, value string, lifeTime time.Duration) error {\n\tduration := int64(0)\n\n\tif lifeTime > 0 {\n\t\tduration = time.Now().Unix() + int64(lifeTime.Seconds())\n\t}\n\n\tcontent := &boltContent{duration, value}\n\n\tdata, err := json.Marshal(content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.db.Update(func(tx *bt.Tx) error {\n\t\tbucket, err := tx.CreateBucketIfNotExists(boltBucket)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn bucket.Put([]byte(key), data)\n\t})\n}\n"
  },
  {
    "path": "bolt/bolt_test.go",
    "content": "package bolt\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tbt \"go.etcd.io/bbolt\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestBolt(t *testing.T) {\n\tdir, err := os.MkdirTemp(\"\", t.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := bt.Open(fmt.Sprintf(\"%s/cachego.db\", dir), 0o600, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Cleanup(func() {\n\t\t_ = db.Close()\n\t\t_ = os.RemoveAll(dir)\n\t})\n\n\tc := New(db)\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err == nil {\n\t\tt.Errorf(\"flush failed: expected error, got %v\", err)\n\t}\n\n\tif err := c.Delete(testKey); err == nil {\n\t\tt.Errorf(\"delete failed: expected error, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n}\n"
  },
  {
    "path": "cache.go",
    "content": "package cachego\n\nimport (\n\t\"time\"\n)\n\ntype (\n\t// Cache is the top-level cache interface\n\tCache interface {\n\t\t// Contains check if a cached key exists\n\t\tContains(key string) bool\n\n\t\t// Delete remove the cached key\n\t\tDelete(key string) error\n\n\t\t// Fetch retrieve the cached key value\n\t\tFetch(key string) (string, error)\n\n\t\t// FetchMulti retrieve multiple cached keys value\n\t\tFetchMulti(keys []string) map[string]string\n\n\t\t// Flush remove all cached keys\n\t\tFlush() error\n\n\t\t// Save cache a value by key\n\t\tSave(key string, value string, lifeTime time.Duration) error\n\t}\n)\n"
  },
  {
    "path": "chain/README.md",
    "content": "# Cachego - Chain driver\nThe chain driver deals with multiple driver at same time, it could save the key in multiple drivers\nand for fetching the driver will call the first one, if fails it will try the next until fail.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\tbt \"go.etcd.io/bbolt\"\n\n\t\"github.com/faabiosr/cachego/bolt\"\n\t\"github.com/faabiosr/cachego/chain\"\n\t\"github.com/faabiosr/cachego/sync\"\n)\n\nfunc main() {\n\tdb, err := bt.Open(\"cache.db\", 0600, nil)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcache := chain.New(\n\t\tbolt.New(db),\n\t\tsync.New(),\n\t)\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "chain/chain.go",
    "content": "// Package chain provides chaining cache drivers operations, in case of failure\n// the driver try to apply using the next driver informed, until fail.\npackage chain\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype (\n\tchain struct {\n\t\tdrivers []cachego.Cache\n\t}\n)\n\n// New creates an instance of Chain cache driver\nfunc New(drivers ...cachego.Cache) cachego.Cache {\n\treturn &chain{drivers}\n}\n\n// Contains checks if the cached key exists in one of the cache storages\nfunc (c *chain) Contains(key string) bool {\n\tfor _, driver := range c.drivers {\n\t\tif driver.Contains(key) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Delete the cached key in all cache storages\nfunc (c *chain) Delete(key string) error {\n\tfor _, driver := range c.drivers {\n\t\tif err := driver.Delete(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Fetch retrieves the value of one of the registred cache storages\nfunc (c *chain) Fetch(key string) (string, error) {\n\tfor _, driver := range c.drivers {\n\t\tvalue, err := driver.Fetch(key)\n\n\t\tif err == nil {\n\t\t\treturn value, nil\n\t\t}\n\t}\n\n\treturn \"\", errors.New(\"key not found in cache chain\")\n}\n\n// FetchMulti retrieves multiple cached values from one of the registred cache storages\nfunc (c *chain) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tfor _, key := range keys {\n\t\tif value, err := c.Fetch(key); err == nil {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the registered cache storages\nfunc (c *chain) Flush() error {\n\tfor _, driver := range c.drivers {\n\t\tif err := driver.Flush(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Save a value in all cache storages by key\nfunc (c *chain) Save(key string, value string, lifeTime time.Duration) error {\n\tfor _, driver := range c.drivers {\n\t\tif err := driver.Save(key, value, lifeTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "chain/chain_test.go",
    "content": "package chain\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bradfitz/gomemcache/memcache\"\n\t\"github.com/faabiosr/cachego/memcached\"\n\t\"github.com/faabiosr/cachego/sync\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestChain(t *testing.T) {\n\tc := New(sync.New())\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Delete(testKey); err != nil {\n\t\tt.Errorf(\"delete failed: expected nil, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n\n\tc = New(\n\t\tsync.New(),\n\t\tmemcached.New(memcache.New(\"127.0.0.1:22222\")),\n\t)\n\n\tif err := c.Save(testKey, testValue, 0); err == nil {\n\t\tt.Errorf(\"save failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Delete(testKey); err == nil {\n\t\tt.Errorf(\"delete failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err == nil {\n\t\tt.Errorf(\"flush failed: expected an error, got %v\", err)\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Package cachego provides a simple way to use cache drivers.\n//\n// # Example Usage\n//\n// The following is a simple example using memcached driver:\n//\n//\timport (\n//\t  \"fmt\"\n//\t  \"github.com/faabiosr/cachego\"\n//\t  \"github.com/bradfitz/gomemcache/memcache\"\n//\t)\n//\n//\tfunc main() {\n//\n//\t  cache := cachego.NewMemcached(\n//\t      memcached.New(\"localhost:11211\"),\n//\t  )\n//\n//\t  cache.Save(\"foo\", \"bar\")\n//\n//\t  fmt.Println(cache.Fetch(\"foo\"))\n//\t}\npackage cachego\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "---\nservices:\n  memcached:\n      image: \"memcached:alpine\"\n      ports:\n          - \"11211:11211\"\n\n  redis:\n      image: \"redis:alpine\"\n      ports:\n          - \"6379:6379\"\n\n  mongodb:\n      image: \"mongo:3.6\"\n      ports:\n          - \"27017:27017\"\n"
  },
  {
    "path": "errors.go",
    "content": "package cachego\n\ntype err string\n\n// Error returns the string error value.\nfunc (e err) Error() string {\n\treturn string(e)\n}\n\nconst (\n\t// ErrCacheExpired returns an error when the cache key was expired.\n\tErrCacheExpired = err(\"cache expired\")\n\n\t// ErrFlush returns an error when flush fails.\n\tErrFlush = err(\"unable to flush\")\n\n\t// ErrSave returns an error when save fails.\n\tErrSave = err(\"unable to save\")\n\n\t// ErrDelete returns an error when deletion fails.\n\tErrDelete = err(\"unable to delete\")\n\n\t// ErrDecode returns an errors when decode fails.\n\tErrDecode = err(\"unable to decode\")\n)\n"
  },
  {
    "path": "errors_test.go",
    "content": "package cachego\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestError(t *testing.T) {\n\texpect := \"failed\"\n\ter := err(expect)\n\n\tif r := fmt.Sprint(er); r != expect {\n\t\tt.Errorf(\"invalid string: expect %s, got %s\", expect, r)\n\t}\n}\n"
  },
  {
    "path": "file/README.md",
    "content": "# Cachego - File driver\nThe driver stores the cache data in file.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego/file\"\n)\n\nfunc main() {\n\tcache := file.New(\"./cache-dir/\")\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "file/file.go",
    "content": "// Package file providers a cache driver that stores the cache content in files.\npackage file\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype (\n\tfile struct {\n\t\tdir string\n\t\tsync.RWMutex\n\t}\n\n\tfileContent struct {\n\t\tDuration int64  `json:\"duration\"`\n\t\tData     string `json:\"data,omitempty\"`\n\t}\n)\n\nconst perm = 0o666\n\n// New creates an instance of File cache\nfunc New(dir string) cachego.Cache {\n\treturn &file{dir: dir}\n}\n\nfunc (f *file) createName(key string) string {\n\th := sha256.New()\n\t_, _ = h.Write([]byte(key))\n\thash := hex.EncodeToString(h.Sum(nil))\n\n\treturn filepath.Join(f.dir, fmt.Sprintf(\"%s.cachego\", hash))\n}\n\nfunc (f *file) read(key string) (*fileContent, error) {\n\tf.RLock()\n\tdefer f.RUnlock()\n\n\tvalue, err := os.ReadFile(f.createName(key))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcontent := &fileContent{}\n\tif err := json.Unmarshal(value, content); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif content.Duration == 0 {\n\t\treturn content, nil\n\t}\n\n\treturn content, nil\n}\n\n// Contains checks if the cached key exists into the File storage\nfunc (f *file) Contains(key string) bool {\n\tcontent, err := f.read(key)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif f.isExpired(content) {\n\t\t_ = f.Delete(key)\n\t\treturn false\n\t}\n\treturn true\n}\n\n// Delete the cached key from File storage\nfunc (f *file) Delete(key string) error {\n\tf.Lock()\n\tdefer f.Unlock()\n\n\t_, err := os.Stat(f.createName(key))\n\tif err != nil && os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\n\treturn os.Remove(f.createName(key))\n}\n\n// Fetch retrieves the cached value from key of the File storage\nfunc (f *file) Fetch(key string) (string, error) {\n\tcontent, err := f.read(key)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif f.isExpired(content) {\n\t\t_ = f.Delete(key)\n\t\treturn \"\", cachego.ErrCacheExpired\n\t}\n\n\treturn content.Data, nil\n}\n\nfunc (f *file) isExpired(content *fileContent) bool {\n\treturn content.Duration > 0 && content.Duration <= time.Now().Unix()\n}\n\n// FetchMulti retrieve multiple cached values from keys of the File storage\nfunc (f *file) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tfor _, key := range keys {\n\t\tif value, err := f.Fetch(key); err == nil {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the File storage\nfunc (f *file) Flush() error {\n\tf.Lock()\n\tdefer f.Unlock()\n\n\tdir, err := os.Open(f.dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = dir.Close()\n\t}()\n\n\tnames, _ := dir.Readdirnames(-1)\n\n\tfor _, name := range names {\n\t\t_ = os.Remove(filepath.Join(f.dir, name))\n\t}\n\n\treturn nil\n}\n\n// Save a value in File storage by key\nfunc (f *file) Save(key string, value string, lifeTime time.Duration) error {\n\tf.Lock()\n\tdefer f.Unlock()\n\n\tduration := int64(0)\n\tif lifeTime > 0 {\n\t\tduration = time.Now().Unix() + int64(lifeTime.Seconds())\n\t}\n\n\tcontent := &fileContent{duration, value}\n\tdata, err := json.Marshal(content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn os.WriteFile(f.createName(key), data, perm)\n}\n"
  },
  {
    "path": "file/file_test.go",
    "content": "package file\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestFile(t *testing.T) {\n\tdir, err := os.MkdirTemp(\"\", t.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc := New(dir)\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n\n\tc = New(\"./test/\")\n\n\tif err := c.Save(testKey, testValue, 0); err == nil {\n\t\tt.Errorf(\"save failed: expected an error, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch failed: expected and error, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err == nil {\n\t\tt.Errorf(\"flush failed: expected an error, got %v\", err)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/faabiosr/cachego\n\ngo 1.21\n\nrequire (\n\tgithub.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874\n\tgithub.com/mattn/go-sqlite3 v1.14.24\n\tgithub.com/redis/go-redis/v9 v9.7.3\n\tgo.etcd.io/bbolt v1.3.10\n\tgo.mongodb.org/mongo-driver/v2 v2.0.1\n)\n\nrequire (\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/klauspost/compress v1.16.7 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgolang.org/x/crypto v0.33.0 // indirect\n\tgolang.org/x/sync v0.11.0 // indirect\n\tgolang.org/x/sys v0.30.0 // indirect\n\tgolang.org/x/text v0.22.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=\ngithub.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=\ngithub.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=\ngithub.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=\ngithub.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=\ngithub.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=\ngithub.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=\ngo.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=\ngo.mongodb.org/mongo-driver/v2 v2.0.1 h1:mhB/ZJkLSv6W6LGzY7sEjpZif47+JdfEEXjlLCIv7Qc=\ngo.mongodb.org/mongo-driver/v2 v2.0.1/go.mod h1:w7iFnTcQDMXtdXwcvyG3xljYpoBa1ErkI0yOzbkZ9b8=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=\ngolang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=\ngolang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "memcached/README.md",
    "content": "# Cachego - Memcached driver\nThe drivers uses [gomemcache](https://github.com/bradfitz/gomemcache) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/bradfitz/gomemcache/memcache\"\n\n\t\"github.com/faabiosr/cachego/memcached\"\n)\n\nfunc main() {\n\tcache := memcached.New(\n\t\tmemcache.New(\"localhost:11211\"),\n\t)\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "memcached/memcached.go",
    "content": "// Package memcached providers a cache driver that stores the cache in Memcached.\npackage memcached\n\nimport (\n\t\"time\"\n\n\t\"github.com/bradfitz/gomemcache/memcache\"\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype memcached struct {\n\tdriver *memcache.Client\n}\n\n// New creates an instance of Memcached cache driver\nfunc New(driver *memcache.Client) cachego.Cache {\n\treturn &memcached{driver}\n}\n\n// Contains checks if cached key exists in Memcached storage\nfunc (m *memcached) Contains(key string) bool {\n\t_, err := m.Fetch(key)\n\treturn err == nil\n}\n\n// Delete the cached key from Memcached storage\nfunc (m *memcached) Delete(key string) error {\n\treturn m.driver.Delete(key)\n}\n\n// Fetch retrieves the cached value from key of the Memcached storage\nfunc (m *memcached) Fetch(key string) (string, error) {\n\titem, err := m.driver.Get(key)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(item.Value), nil\n}\n\n// FetchMulti retrieves multiple cached value from keys of the Memcached storage\nfunc (m *memcached) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\titems, err := m.driver.GetMulti(keys)\n\tif err != nil {\n\t\treturn result\n\t}\n\n\tfor _, i := range items {\n\t\tresult[i.Key] = string(i.Value)\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the Memcached storage\nfunc (m *memcached) Flush() error {\n\treturn m.driver.FlushAll()\n}\n\n// Save a value in Memcached storage by key\nfunc (m *memcached) Save(key string, value string, lifeTime time.Duration) error {\n\treturn m.driver.Set(\n\t\t&memcache.Item{\n\t\t\tKey:        key,\n\t\t\tValue:      []byte(value),\n\t\t\tExpiration: int32(lifeTime.Seconds()),\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "memcached/memcached_test.go",
    "content": "package memcached\n\nimport (\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bradfitz/gomemcache/memcache\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestMemcached(t *testing.T) {\n\taddress := \"localhost:11211\"\n\tconn := memcache.New(address)\n\n\tif _, err := net.Dial(\"tcp\", address); err != nil {\n\t\tt.Skip(err)\n\t}\n\n\tc := New(conn)\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(\"bar\"); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n\n\tc = New(memcache.New(\"localhost:22222\"))\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) != 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 0, len(values))\n\t}\n}\n"
  },
  {
    "path": "mongo/README.md",
    "content": "# Cachego - Mongo driver\nThe drivers uses [go-mgo](https://github.com/go-mgo/mgo) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/v2/mongo\"\n\t\"go.mongodb.org/mongo-driver/v2/mongo/options\"\n\n\t\"github.com/faabiosr/cachego/mongo\"\n)\n\nfunc main() {\n\topts := options.Client().ApplyURI(\"mongodb://localhost:27017\")\n\tclient, _ := mongo.Connect(opts)\n\n\tcache := mongo.New(\n\t    client.Database(\"cache\").Collection(\"cache\"),\n    )\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "mongo/mongo.go",
    "content": "// Package mongo providers a cache driver that stores the cache in MongoDB.\npackage mongo\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego\"\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n\t\"go.mongodb.org/mongo-driver/v2/mongo\"\n\t\"go.mongodb.org/mongo-driver/v2/mongo/options\"\n)\n\ntype (\n\tmongoCache struct {\n\t\tcollection *mongo.Collection\n\t}\n\n\tmongoContent struct {\n\t\tDuration int64\n\t\tKey      string `bson:\"_id\"`\n\t\tValue    string\n\t}\n)\n\n// New creates an instance of Mongo cache driver\nfunc New(collection *mongo.Collection) cachego.Cache {\n\treturn &mongoCache{collection}\n}\n\n// NewMongoDriver alias for New.\nfunc NewMongoDriver(collection *mongo.Collection) cachego.Cache {\n\treturn New(collection)\n}\n\nfunc (m *mongoCache) Contains(key string) bool {\n\t_, err := m.Fetch(key)\n\treturn err == nil\n}\n\n// Delete the cached key from Mongo storage\nfunc (m *mongoCache) Delete(key string) error {\n\t_, err := m.collection.DeleteOne(context.TODO(), bson.M{\"_id\": bson.M{\"$eq\": key}})\n\treturn err\n}\n\n// Fetch retrieves the cached value from key of the Mongo storage\nfunc (m *mongoCache) Fetch(key string) (string, error) {\n\tcontent := &mongoContent{}\n\tresult := m.collection.FindOne(context.TODO(), bson.M{\"_id\": bson.M{\"$eq\": key}})\n\tif result == nil {\n\t\treturn \"\", cachego.ErrCacheExpired\n\t}\n\tif result.Err() != nil {\n\t\treturn \"\", result.Err()\n\t}\n\n\terr := result.Decode(&content)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif content.Duration == 0 {\n\t\treturn content.Value, nil\n\t}\n\n\tif content.Duration <= time.Now().Unix() {\n\t\t_ = m.Delete(key)\n\t\treturn \"\", cachego.ErrCacheExpired\n\t}\n\treturn content.Value, nil\n}\n\nfunc (m *mongoCache) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tcur, err := m.collection.Find(context.TODO(), bson.M{\"_id\": bson.M{\"$in\": keys}})\n\tif err != nil {\n\t\treturn result\n\t}\n\tdefer func() {\n\t\t_ = cur.Close(context.Background())\n\t}()\n\n\tcontent := &mongoContent{}\n\n\tfor cur.Next(context.Background()) {\n\t\terr := cur.Decode(content)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tresult[content.Key] = content.Value\n\t}\n\treturn result\n}\n\n// Flush removes all cached keys of the Mongo storage\nfunc (m *mongoCache) Flush() error {\n\t_, err := m.collection.DeleteMany(context.TODO(), bson.M{})\n\treturn err\n}\n\n// Save a value in Mongo storage by key\nfunc (m *mongoCache) Save(key string, value string, lifeTime time.Duration) error {\n\tduration := int64(0)\n\n\tif lifeTime > 0 {\n\t\tduration = time.Now().Unix() + int64(lifeTime.Seconds())\n\t}\n\n\tcontent := &mongoContent{duration, key, value}\n\topts := options.Replace().SetUpsert(true)\n\t_, err := m.collection.ReplaceOne(context.TODO(), bson.M{\"_id\": bson.M{\"$eq\": key}}, content, opts)\n\treturn err\n}\n"
  },
  {
    "path": "mongo/mongo_test.go",
    "content": "package mongo\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/v2/mongo\"\n\t\"go.mongodb.org/mongo-driver/v2/mongo/options\"\n)\n\nconst (\n\ttestKeyMongo   = \"foo1\"\n\ttestValueMongo = \"bar\"\n)\n\nfunc TestMongo(t *testing.T) {\n\t// Set client options\n\tclientOptions := options.Client().ApplyURI(\"mongodb://localhost:27017\")\n\n\t// Connect to MongoDB\n\tclient, err := mongo.Connect(clientOptions)\n\tif err != nil {\n\t\tt.Skip(err)\n\t}\n\tcollection := client.Database(\"cache\").Collection(\"cache\")\n\n\tcache := New(collection)\n\n\tif err := cache.Save(testKeyMongo, testValueMongo, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif v, err := cache.Fetch(testKeyMongo); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v value %v\", err, v)\n\t}\n\n\t_ = cache.Save(testKeyMongo, testValueMongo, 10*time.Second)\n\n\tif res, _ := cache.Fetch(testKeyMongo); res != testValueMongo {\n\t\tt.Errorf(\"fetch fail, wrong value : expected %s, got %s\", testValueMongo, res)\n\t}\n\n\t_ = cache.Save(testKeyMongo, testValueMongo, 0)\n\n\tif !cache.Contains(testKeyMongo) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKeyMongo)\n\t}\n\n\t_ = cache.Save(\"bar\", testValueMongo, 0)\n\n\tif values := cache.FetchMulti([]string{testKeyMongo, \"bar\"}); len(values) != 2 {\n\t\tfmt.Println(values)\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := cache.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif cache.Contains(testKeyMongo) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKeyMongo)\n\t}\n}\n"
  },
  {
    "path": "redis/README.md",
    "content": "# Cachego - Redis driver\nThe drivers uses [go-redis](https://github.com/go-redis/redis) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\trd \"github.com/redis/go-redis/v9\"\n\n\t\"github.com/faabiosr/cachego/redis\"\n)\n\nfunc main() {\n\tcache := redis.New(\n\t\trd.NewClient(&rd.Options{\n\t\t\tAddr: \":6379\",\n\t\t}),\n\t)\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "redis/redis.go",
    "content": "// Package redis providers a cache driver that stores the cache in Redis.\npackage redis\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\trd \"github.com/redis/go-redis/v9\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype redis struct {\n\tdriver rd.Cmdable\n}\n\n// New creates an instance of Redis cache driver\nfunc New(driver rd.Cmdable) cachego.Cache {\n\treturn &redis{driver}\n}\n\n// Contains checks if cached key exists in Redis storage\nfunc (r *redis) Contains(key string) bool {\n\ti, _ := r.driver.Exists(context.Background(), key).Result()\n\treturn i > 0\n}\n\n// Delete the cached key from Redis storage\nfunc (r *redis) Delete(key string) error {\n\treturn r.driver.Del(context.Background(), key).Err()\n}\n\n// Fetch retrieves the cached value from key of the Redis storage\nfunc (r *redis) Fetch(key string) (string, error) {\n\treturn r.driver.Get(context.Background(), key).Result()\n}\n\n// FetchMulti retrieves multiple cached value from keys of the Redis storage\nfunc (r *redis) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\titems, err := r.driver.MGet(context.Background(), keys...).Result()\n\tif err != nil {\n\t\treturn result\n\t}\n\n\tfor i := 0; i < len(keys); i++ {\n\t\tif items[i] != nil {\n\t\t\tresult[keys[i]] = items[i].(string)\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the Redis storage\nfunc (r *redis) Flush() error {\n\treturn r.driver.FlushAll(context.Background()).Err()\n}\n\n// Save a value in Redis storage by key\nfunc (r *redis) Save(key string, value string, lifeTime time.Duration) error {\n\treturn r.driver.Set(context.Background(), key, value, lifeTime).Err()\n}\n"
  },
  {
    "path": "redis/redis_test.go",
    "content": "package redis\n\nimport (\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\trd \"github.com/redis/go-redis/v9\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestRedis(t *testing.T) {\n\tconn := rd.NewClient(&rd.Options{\n\t\tAddr: \":6379\",\n\t})\n\n\tif _, err := net.Dial(\"tcp\", \"localhost:6379\"); err != nil {\n\t\tt.Skip(err)\n\t}\n\n\tc := New(conn)\n\n\tif err := c.Save(testKey, testValue, 10*time.Second); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\tif _, err := c.Fetch(\"bar\"); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Delete(testKey); err != nil {\n\t\tt.Errorf(\"delete failed: expected nil, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n\n\tconn = rd.NewClient(&rd.Options{Addr: \":6380\"})\n\n\tc = New(conn)\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) != 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 0, len(values))\n\t}\n}\n"
  },
  {
    "path": "sqlite3/README.md",
    "content": "# Cachego - SQLite3 driver\nThe drivers uses [go-sqlite3](https://github.com/mattn/go-sqlite3) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"log\"\n\t\"time\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n\n\t\"github.com/faabiosr/cachego/sqlite3\"\n)\n\nfunc main() {\n\tdb, err := sql.Open(\"sqlite3\", \"./cache.db\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcache, err := sqlite3.New(db, \"cache\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "sqlite3/sqlite3.go",
    "content": "// Package sqlite3 providers a cache driver that stores the cache in SQLite3.\npackage sqlite3\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype (\n\tsqlite3 struct {\n\t\tdb    *sql.DB\n\t\ttable string\n\t}\n)\n\n// New creates an instance of Sqlite3 cache driver\nfunc New(db *sql.DB, table string) (cachego.Cache, error) {\n\treturn &sqlite3{db, table}, createTable(db, table)\n}\n\nfunc createTable(db *sql.DB, table string) error {\n\tstmt := `CREATE TABLE IF NOT EXISTS %s (\n        key text PRIMARY KEY,\n        value text NOT NULL,\n        lifetime integer NOT NULL\n    );`\n\n\t_, err := db.Exec(fmt.Sprintf(stmt, table))\n\treturn err\n}\n\n// Contains checks if cached key exists in Sqlite3 storage\nfunc (s *sqlite3) Contains(key string) bool {\n\t_, err := s.Fetch(key)\n\treturn err == nil\n}\n\n// Delete the cached key from Sqlite3 storage\nfunc (s *sqlite3) Delete(key string) error {\n\ttx, err := s.db.Begin()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstmt, err := tx.Prepare(fmt.Sprintf(`\n\t\tDELETE FROM %s\n\t\tWHERE key = ?\n\t`, s.table))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = stmt.Close()\n\t}()\n\n\tif _, err := stmt.Exec(key); err != nil {\n\t\t_ = tx.Rollback()\n\t\treturn err\n\t}\n\n\treturn tx.Commit()\n}\n\n// Fetch retrieves the cached value from key of the Sqlite3 storage\nfunc (s *sqlite3) Fetch(key string) (string, error) {\n\tstmt, err := s.db.Prepare(fmt.Sprintf(`\n\t\tSELECT value, lifetime\n\t\tFROM %s WHERE key = ?\n\t`, s.table))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdefer func() {\n\t\t_ = stmt.Close()\n\t}()\n\n\tvar value string\n\tvar lifetime int64\n\n\tif err := stmt.QueryRow(key).Scan(&value, &lifetime); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif lifetime == 0 {\n\t\treturn value, nil\n\t}\n\n\tif lifetime <= time.Now().Unix() {\n\t\t_ = s.Delete(key)\n\t\treturn \"\", cachego.ErrCacheExpired\n\t}\n\n\treturn value, nil\n}\n\n// FetchMulti retrieves multiple cached value from keys of the Sqlite3 storage\nfunc (s *sqlite3) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tfor _, key := range keys {\n\t\tif value, err := s.Fetch(key); err == nil {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the Sqlite3 storage\nfunc (s *sqlite3) Flush() error {\n\ttx, err := s.db.Begin()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstmt, err := tx.Prepare(fmt.Sprintf(\"DELETE FROM %s\", s.table))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = stmt.Close()\n\t}()\n\n\tif _, err := stmt.Exec(); err != nil {\n\t\t_ = tx.Rollback()\n\t\treturn err\n\t}\n\n\treturn tx.Commit()\n}\n\n// Save a value in Sqlite3 storage by key\nfunc (s *sqlite3) Save(key string, value string, lifeTime time.Duration) error {\n\tduration := int64(0)\n\n\tif lifeTime > 0 {\n\t\tduration = time.Now().Unix() + int64(lifeTime.Seconds())\n\t}\n\n\ttx, err := s.db.Begin()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstmt, err := tx.Prepare(fmt.Sprintf(`\n\t\tINSERT OR REPLACE INTO %s (key, value, lifetime)\n\t\tVALUES (?, ?, ?)\n\t`, s.table))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = stmt.Close()\n\t}()\n\n\tif _, err := stmt.Exec(key, value, duration); err != nil {\n\t\t_ = tx.Rollback()\n\t\treturn err\n\t}\n\n\treturn tx.Commit()\n}\n"
  },
  {
    "path": "sqlite3/sqlite3_test.go",
    "content": "package sqlite3\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nconst (\n\ttestKey    = \"foo\"\n\ttestValue  = \"bar\"\n\ttestTable  = \"cache\"\n\ttestDBPath = \"/cache.db\"\n)\n\nfunc TestSqlite3(t *testing.T) {\n\tdir, err := os.MkdirTemp(\"\", t.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, err := sql.Open(\"sqlite3\", dir+testDBPath)\n\tif err != nil {\n\t\tt.Skip(err)\n\t}\n\n\tt.Cleanup(func() {\n\t\t_ = os.RemoveAll(dir)\n\t})\n\n\tc, err := New(db, testTable)\n\tif err != nil {\n\t\tt.Skip(err)\n\t}\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n}\n\nfunc TestSqlite3Fail(t *testing.T) {\n\tdir, err := os.MkdirTemp(\"\", t.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdb, _ := sql.Open(\"sqlite3\", dir+testDBPath)\n\t_ = db.Close()\n\n\tt.Cleanup(func() {\n\t\t_ = os.RemoveAll(dir)\n\t})\n\n\tif _, err := New(db, testTable); err == nil {\n\t\tt.Errorf(\"constructor failed: expected an error, got %v\", err)\n\t}\n\n\tdb, _ = sql.Open(\"sqlite3\", testDBPath)\n\tc, _ := New(db, testTable)\n\t_ = db.Close()\n\n\tif err := c.Save(testKey, testValue, 0); err == nil {\n\t\tt.Errorf(\"save failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Delete(testKey); err == nil {\n\t\tt.Errorf(\"delete failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err == nil {\n\t\tt.Errorf(\"flush failed: expected an error, got %v\", err)\n\t}\n\n\tdb, _ = sql.Open(\"sqlite3\", testDBPath)\n\tc, _ = New(db, testTable)\n\n\t_, _ = db.Exec(fmt.Sprintf(\"DROP TABLE %s;\", testTable))\n\n\tif err := c.Save(testKey, testValue, 0); err == nil {\n\t\tt.Errorf(\"save failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Delete(testKey); err == nil {\n\t\tt.Errorf(\"delete failed: expected an error, got %v\", err)\n\t}\n\n\tif err := c.Flush(); err == nil {\n\t\tt.Errorf(\"flush failed: expected an error, got %v\", err)\n\t}\n}\n"
  },
  {
    "path": "sync/README.md",
    "content": "# Cachego - Sync driver\nThe drivers uses [golang sync](https://golang.org/pkg/sync/#Map) to store the cache data.\n\n## Usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego/sync\"\n)\n\nfunc main() {\n\tcache := sync.New()\n\n\tif err := cache.Save(\"user_id\", \"1\", 10*time.Second); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tid, err := cache.Fetch(\"user_id\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Printf(\"user id: %s \\n\", id)\n}\n```\n"
  },
  {
    "path": "sync/map.go",
    "content": "// Package sync providers a cache driver that uses standard golang sync.Map.\npackage sync\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/faabiosr/cachego\"\n)\n\ntype (\n\tsyncMapItem struct {\n\t\tdata     string\n\t\tduration int64\n\t}\n\n\tsyncMap struct {\n\t\tstorage *sync.Map\n\t}\n)\n\n// New creates an instance of SyncMap cache driver\nfunc New() cachego.Cache {\n\treturn &syncMap{&sync.Map{}}\n}\n\nfunc (sm *syncMap) read(key string) (*syncMapItem, error) {\n\tv, ok := sm.storage.Load(key)\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found\")\n\t}\n\n\titem := v.(*syncMapItem)\n\n\tif item.duration == 0 {\n\t\treturn item, nil\n\t}\n\n\tif item.duration <= time.Now().Unix() {\n\t\t_ = sm.Delete(key)\n\t\treturn nil, cachego.ErrCacheExpired\n\t}\n\n\treturn item, nil\n}\n\n// Contains checks if cached key exists in SyncMap storage\nfunc (sm *syncMap) Contains(key string) bool {\n\t_, err := sm.Fetch(key)\n\treturn err == nil\n}\n\n// Delete the cached key from SyncMap storage\nfunc (sm *syncMap) Delete(key string) error {\n\tsm.storage.Delete(key)\n\treturn nil\n}\n\n// Fetch retrieves the cached value from key of the SyncMap storage\nfunc (sm *syncMap) Fetch(key string) (string, error) {\n\titem, err := sm.read(key)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn item.data, nil\n}\n\n// FetchMulti retrieves multiple cached value from keys of the SyncMap storage\nfunc (sm *syncMap) FetchMulti(keys []string) map[string]string {\n\tresult := make(map[string]string)\n\n\tfor _, key := range keys {\n\t\tif value, err := sm.Fetch(key); err == nil {\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\n\treturn result\n}\n\n// Flush removes all cached keys of the SyncMap storage\nfunc (sm *syncMap) Flush() error {\n\tsm.storage = &sync.Map{}\n\treturn nil\n}\n\n// Save a value in SyncMap storage by key\nfunc (sm *syncMap) Save(key string, value string, lifeTime time.Duration) error {\n\tduration := int64(0)\n\n\tif lifeTime > 0 {\n\t\tduration = time.Now().Unix() + int64(lifeTime.Seconds())\n\t}\n\n\tsm.storage.Store(key, &syncMapItem{value, duration})\n\treturn nil\n}\n"
  },
  {
    "path": "sync/map_test.go",
    "content": "package sync\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\ttestKey   = \"foo\"\n\ttestValue = \"bar\"\n)\n\nfunc TestSyncMap(t *testing.T) {\n\tc := New()\n\n\tif err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {\n\t\tt.Errorf(\"save fail: expected nil, got %v\", err)\n\t}\n\n\tif _, err := c.Fetch(testKey); err == nil {\n\t\tt.Errorf(\"fetch fail: expected an error, got %v\", err)\n\t}\n\n\t_ = c.Save(testKey, testValue, 10*time.Second)\n\n\tif res, _ := c.Fetch(testKey); res != testValue {\n\t\tt.Errorf(\"fetch fail, wrong value: expected %s, got %s\", testValue, res)\n\t}\n\n\t_ = c.Save(testKey, testValue, 0)\n\n\tif !c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should be exist\", testKey)\n\t}\n\n\t_ = c.Save(\"bar\", testValue, 0)\n\n\tif values := c.FetchMulti([]string{testKey, \"bar\"}); len(values) == 0 {\n\t\tt.Errorf(\"fetch multi failed: expected %d, got %d\", 2, len(values))\n\t}\n\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"flush failed: expected nil, got %v\", err)\n\t}\n\n\tif c.Contains(testKey) {\n\t\tt.Errorf(\"contains failed: the key %s should not be exist\", testKey)\n\t}\n}\n"
  }
]