diff --git a/.github/banner.gif b/.github/banner.gif new file mode 100644 index 000000000..dccd5516f Binary files /dev/null and b/.github/banner.gif differ diff --git a/README.md b/README.md index c9b15e35e..aa9ac3ed6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# GoFr

logo

+

+GoFr is an opinionated microservice development framework.

@@ -13,30 +14,88 @@
- -
- -GoFr is an opinionated microservice development framework. Listed in [CNCF Landscape](https://landscape.cncf.io/?selected=go-fr). - -Visit https://gofr.dev for more details and documentation. +Listed in [CNCF Landscape](https://landscape.cncf.io/?selected=go-fr). ## 🎯 Goal Even though generic applications can be written using GoFr, our main focus is to simplify the development of microservices. -We will focus ourselves towards deployment in Kubernetes and aspire to provide out-of-the-box observability. +We will focus on deployment in Kubernetes and aspire to provide out-of-the-box observability. -## 💡 Advantages/Features +## 💡 Key Features 1. Simple API syntax 2. REST Standards by default 3. Configuration management -4. Inbuilt Middlewares -5. [gRPC support](https://gofr.dev/docs/advanced-guide/grpc) -6. [HTTP service](https://gofr.dev/docs/advanced-guide/http-communication) with support for [Circuit Breaker](https://gofr.dev/docs/advanced-guide/circuit-breaker) and -[Health Check](https://gofr.dev/docs/advanced-guide/monitoring-service-health) +4. [Observability](https://gofr.dev/docs/quick-start/observability) (Logs, Traces, Metrics) +5. Inbuilt [Auth Middleware](https://gofr.dev/docs/advanced-guide/http-authentication) & Support for [Custom Middleware](https://gofr.dev/docs/advanced-guide/middlewares) +6. [gRPC support](https://gofr.dev/docs/advanced-guide/grpc) +7. [HTTP service](https://gofr.dev/docs/advanced-guide/http-communication) with support for [Circuit Breaker](https://gofr.dev/docs/advanced-guide/circuit-breaker) +8. [Pub/Sub](https://gofr.dev/docs/advanced-guide/using-publisher-subscriber) +9. [Health Check](https://gofr.dev/docs/advanced-guide/monitoring-service-health) by default for all datasources. +10. [Database Migration](https://gofr.dev/docs/advanced-guide/handling-data-migrations) +11. [Cron Jobs](https://gofr.dev/docs/advanced-guide/using-cron) +12. Support for [changing Log Level](https://gofr.dev/docs/advanced-guide/remote-log-level-change) without restarting the application. +13. [Swagger Rendering](https://gofr.dev/docs/advanced-guide/swagger-documentation) +14. [Abstracted File Systems](https://gofr.dev/docs/advanced-guide/handling-file) +15. [Websockets](https://gofr.dev/docs/advanced-guide/handling-file) + +![banner.gif](.github/banner.gif) + +## Getting started +### Prerequisites +GoFr requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. + +### Getting GoFr +With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code: + +```sh +import "github.com/gofr-dev/gofr" +``` + +Alternatively, use `go get`: + +```sh +go get -u github.com/gofr-dev/gofr +``` +### Running GoFr +A basic example: +```go +package main + +import "gofr.dev/pkg/gofr" + +func main() { + app := gofr.New() + + app.GET("/greet", func(ctx *gofr.Context) (interface{}, error) { + + return "Hello World!", nil + }) + + app.Run() // listen and serve on localhost:8080 +} +``` + +To run the code, use the `go run` command, like: + +```sh +$ go run main.go +``` + +Then visit [`localhost:8080/greet`](http://localhost:8080/greet) in your browser to see the response! + +### See more examples +A number of ready-to-run examples demonstrating various use cases of GoFr are available in the [GoFr examples](https://github.com/gofr-dev/gofr/tree/development/examples) directory. + +## 👩‍💻Documentation +See the [godocs](https://pkg.go.dev/gofr.dev). + +The documentation is also available on [gofr.dev](https://gofr.dev/docs). ## 👍 Contribute If you want to say thank you and/or support the active development of GoFr: -1. Add a [GitHub Star](https://github.com/gofr-dev/gofr/stargazers) to the project. +1. [Star](https://docs.github.com/en/get-started/exploring-projects-on-github/saving-repositories-with-stars) the repo. 2. Write a review or tutorial on [Medium](https://medium.com/), [Dev.to](https://dev.to/) or personal blog. -3. Visit [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. After your PR is merged to the repo, fill the [Google Form](https://forms.gle/R1Yz7ZzY3U5WWTgy5), and we will send you a GoFr T-Shirt and Stickers as a token of appreciation. +3. Visit [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. + +If your PR is merged or you have written an article or contributed in someway to development or spreading the word about GoFr, fill the [Google Form](https://forms.gle/R1Yz7ZzY3U5WWTgy5), and we will send you a GoFr T-Shirt and Stickers as a token of appreciation. \ No newline at end of file diff --git a/docs/advanced-guide/key-value-store/page.md b/docs/advanced-guide/key-value-store/page.md new file mode 100644 index 000000000..f5f09c608 --- /dev/null +++ b/docs/advanced-guide/key-value-store/page.md @@ -0,0 +1,80 @@ +# Key Value Store + +A key-value store is a type of NoSQL database that uses a simple data model: each item is stored as a pair consisting of a unique key and a value. +This simplicity offers high performance and scalability, making key-value stores ideal for applications requiring fast and efficient data retrieval and storage. + +GoFr supports BadgerDB as a key value store. Support for other key-value store will be added in the future. + +Keeping in mind the size of the application in the final build, it felt counter-productive to keep the drivers within +the framework itself. GoFr provide the following functionalities for its key-value store. + +```go +type KVStore interface { + Get(ctx context.Context, key string) (string, error) + Set(ctx context.Context, key, value string) error + Delete(ctx context.Context, key string) error +} +``` + +## BadgerDB +GoFr supports injecting BadgerDB that supports the following interface. Any driver that implements the interface can be added +using `app.AddKVStore()` method, and user's can use BadgerDB across application with `gofr.Context`. + +User's can easily inject a driver that supports this interface, this provides usability without +compromising the extensibility to use multiple databases. +### Example +```go +package main + +import ( + "fmt" + + "gofr.dev/pkg/gofr" + "gofr.dev/pkg/gofr/datasource/kv-store/badger" +) + +type User struct { + ID string + Name string + Age string +} + +func main() { + app := gofr.New() + + app.AddKVStore(badger.New(badger.Configs{DirPath: "badger-example"})) + + app.POST("/user", Post) + app.GET("/user", Get) + app.DELETE("/user", Delete) + + app.Run() +} + +func Post(ctx *gofr.Context) (interface{}, error) { + err := ctx.KVStore.Set(ctx, "name", "gofr") + if err != nil { + return nil, err + } + + return "Insertion to Key Value Store Successful", nil +} + +func Get(ctx *gofr.Context) (interface{}, error) { + value, err := ctx.KVStore.Get(ctx, "name") + if err != nil { + return nil, err + } + + return value, nil +} + +func Delete(ctx *gofr.Context) (interface{}, error) { + err := ctx.KVStore.Delete(ctx, "name") + if err != nil { + return nil, err + } + + return fmt.Sprintf("Deleted Successfully key %v from Key-Value Store", "name"), nil +} +``` diff --git a/docs/navigation.js b/docs/navigation.js index e5002df63..b7ad88c1c 100644 --- a/docs/navigation.js +++ b/docs/navigation.js @@ -27,6 +27,7 @@ export const navigation = [ { title: 'Writing gRPC Server', href: '/docs/advanced-guide/grpc' }, { title: 'Using Pub/Sub', href: '/docs/advanced-guide/using-publisher-subscriber' }, { title: 'Injecting Databases', href: '/docs/advanced-guide/injecting-databases-drivers' }, + { title: 'Key Value Store', href: '/docs/advanced-guide/key-value-store' }, { title: 'Dealing with SQL', href: '/docs/advanced-guide/dealing-with-sql' }, { title: 'Automatic SwaggerUI Rendering', href: '/docs/advanced-guide/swagger-documentation' }, { title: 'Error Handling',href: '/docs/advanced-guide/gofr-errors'}, diff --git a/docs/quick-start/observability/page.md b/docs/quick-start/observability/page.md index 43f1ad2c1..b6ace0586 100644 --- a/docs/quick-start/observability/page.md +++ b/docs/quick-start/observability/page.md @@ -189,8 +189,7 @@ DB_PORT=3306 # tracing configs TRACE_EXPORTER=zipkin -TRACER_HOST=localhost -TRACER_PORT=2005 +TRACER_URL=http://localhost:2005/api/v2/spans LOG_LEVEL=DEBUG ``` @@ -220,8 +219,7 @@ Add Jaeger Tracer configs in `.env` file, your .env will be updated to # tracing configs TRACE_EXPORTER=jaeger -TRACER_HOST=localhost -TRACER_PORT=14317 +TRACER_URL=localhost:14317 ``` Open {% new-tab-link title="zipkin" href="http://localhost:16686/trace/" /%} and search by TraceID (correlationID) to see the trace. diff --git a/examples/using-add-rest-handlers/configs/.env b/examples/using-add-rest-handlers/configs/.env index 3d354ee17..22e1d1d6c 100644 --- a/examples/using-add-rest-handlers/configs/.env +++ b/examples/using-add-rest-handlers/configs/.env @@ -9,5 +9,4 @@ DB_PORT=2001 DB_DIALECT=mysql TRACE_EXPORTER=zipkin -TRACER_HOST=localhost -TRACER_PORT=2005 +TRACER_URL=http://localhost:2005/api/v2/spans diff --git a/examples/using-migrations/configs/.env b/examples/using-migrations/configs/.env index 869815bab..8d58388a2 100644 --- a/examples/using-migrations/configs/.env +++ b/examples/using-migrations/configs/.env @@ -16,5 +16,4 @@ PUBSUB_BROKER=localhost:9092 CONSUMER_ID=test TRACE_EXPORTER=zipkin -TRACER_HOST=localhost -TRACER_PORT=2005 +TRACER_URL=http://localhost:2005/api/v2/spans diff --git a/go.mod b/go.mod index 0829af5b6..8b1b5c69e 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.1 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 - github.com/redis/go-redis/v9 v9.5.1 + github.com/redis/go-redis/v9 v9.5.4 github.com/segmentio/kafka-go v0.4.47 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0 @@ -38,18 +38,18 @@ require ( golang.org/x/oauth2 v0.21.0 golang.org/x/term v0.22.0 golang.org/x/text v0.16.0 - google.golang.org/api v0.187.0 + google.golang.org/api v0.188.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 - modernc.org/sqlite v1.30.1 + modernc.org/sqlite v1.30.2 ) require ( cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.6.1 // indirect + cloud.google.com/go/auth v0.7.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.8 // indirect + cloud.google.com/go/compute/metadata v0.4.0 // indirect + cloud.google.com/go/iam v1.1.10 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -88,14 +88,14 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect go.opentelemetry.io/proto/otlp v1.2.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.52.1 // indirect diff --git a/go.sum b/go.sum index f8146f86c..948dadfda 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= -cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= +cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts= +cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/kms v1.18.0 h1:pqNdaVmZJFP+i8OVLocjfpdTWETTYa20FWOegSCdrRo= -cloud.google.com/go/kms v1.18.0/go.mod h1:DyRBeWD/pYBMeyiaXFa/DGNyxMDL3TslIKb8o/JkLkw= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c= +cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= +cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI= +cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= +cloud.google.com/go/kms v1.18.2 h1:EGgD0B9k9tOOkbPhYW1PHo2W0teamAUYMOUIcDRMfPk= +cloud.google.com/go/kms v1.18.2/go.mod h1:YFz1LYrnGsXARuRePL729oINmN5J/5e7nYijgvfiIeY= +cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k= +cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= cloud.google.com/go/pubsub v1.40.0 h1:0LdP+zj5XaPAGtWr2V6r88VXJlmtaB/+fde1q3TU8M0= cloud.google.com/go/pubsub v1.40.0/go.mod h1:BVJI4sI2FyXp36KFKvFwcfDRDfR8MiLT8mMhmIhdAeA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -177,8 +177,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= -github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.5.4 h1:vOFYDKKVgrI5u++QvnMT7DksSMYg7Aw/Np4vLJLKLwY= +github.com/redis/go-redis/v9 v9.5.4/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -255,8 +255,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -282,8 +282,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -347,20 +347,20 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= -google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= +google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= +google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= -google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b h1:dSTjko30weBaMj3eERKc0ZVXW4GudCswM3m+P++ukU0= +google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -416,8 +416,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= -modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk= +modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/pkg/gofr/config/godotenv.go b/pkg/gofr/config/godotenv.go index 10a5d7798..af9097f86 100644 --- a/pkg/gofr/config/godotenv.go +++ b/pkg/gofr/config/godotenv.go @@ -1,7 +1,9 @@ package config import ( + "errors" "fmt" + "io/fs" "os" "github.com/joho/godotenv" @@ -20,6 +22,7 @@ type logger interface { Warnf(format string, a ...interface{}) Infof(format string, a ...interface{}) Debugf(format string, a ...interface{}) + Fatalf(format string, a ...interface{}) } func NewEnvFile(configFolder string, logger logger) Config { @@ -38,33 +41,28 @@ func (e *EnvLoader) read(folder string) { err := godotenv.Load(defaultFile) if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + e.logger.Fatalf("Failed to load config from file: %v, Err: %v", defaultFile, err) + } + e.logger.Warnf("Failed to load config from file: %v, Err: %v", defaultFile, err) } else { e.logger.Infof("Loaded config from file: %v", defaultFile) } - switch env { - case "": - // If 'APP_ENV' is not set, then GoFr will read '.env' from configs directory, and then it will be overwritten - // by configs present in file '.local.env' - err = godotenv.Overload(overrideFile) - if err != nil { - e.logger.Debugf("Failed to load config from file: %v, Err: %v", overrideFile, err) - } else { - e.logger.Infof("Loaded config from file: %v", overrideFile) - } - - default: + if env != "" { // If 'APP_ENV' is set to x, then GoFr will read '.env' from configs directory, and then it will be overwritten // by configs present in file '.x.env' overrideFile = fmt.Sprintf("%s/.%s.env", folder, env) + } - err = godotenv.Overload(overrideFile) - if err != nil { - e.logger.Warnf("Failed to load config from file: %v, Err: %v", overrideFile, err) - } else { - e.logger.Infof("Loaded config from file: %v", overrideFile) + err = godotenv.Overload(overrideFile) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + e.logger.Fatalf("Failed to load config from file: %v, Err: %v", overrideFile, err) } + } else { + e.logger.Infof("Loaded config from file: %v", overrideFile) } } diff --git a/pkg/gofr/config/godotenv_test.go b/pkg/gofr/config/godotenv_test.go index 9d42b853f..7ea6d0c31 100644 --- a/pkg/gofr/config/godotenv_test.go +++ b/pkg/gofr/config/godotenv_test.go @@ -104,29 +104,32 @@ func Test_EnvFailureWithHyphen(t *testing.T) { t.Error(err) } - // Call the function to create the .env file - createEnvFile(t, ".env", envData) - defer os.RemoveAll("configs") - env := NewEnvFile("configs", logger) + configFiles := []string{".env", ".local.env"} - assert.Equal(t, "test", env.GetOrDefault("KEY-WITH-HYPHEN", "test"), "TEST Failed.\n godotenv failure with hyphen") - assert.Equal(t, "", env.Get("UNABLE_TO_LOAD"), "TEST Failed.\n godotenv failure with hyphen") + for _, file := range configFiles { + createEnvFile(t, file, envData) + + env := NewEnvFile("configs", logger) + + assert.Equal(t, "test", env.GetOrDefault("KEY-WITH-HYPHEN", "test"), "TEST Failed.\n godotenv failure with hyphen") + assert.Equal(t, "", env.Get("UNABLE_TO_LOAD"), "TEST Failed.\n godotenv failure with hyphen") + } } func createEnvFile(t *testing.T, fileName string, envData map[string]string) { t.Helper() - // Create or open the .env file for writing + // Create or open the env file for writing envFile, err := os.Create("configs/" + fileName) if err != nil { - t.Fatalf("error creating .env file: %v", err) + t.Fatalf("error creating %s file: %v", fileName, err) } defer envFile.Close() - // Write data to the .env file + // Write data to the env file for key, value := range envData { _, err := fmt.Fprintf(envFile, "%s=%s\n", key, value) if err != nil { diff --git a/pkg/gofr/container/container.go b/pkg/gofr/container/container.go index 056ce47cf..e298829af 100644 --- a/pkg/gofr/container/container.go +++ b/pkg/gofr/container/container.go @@ -43,6 +43,8 @@ type Container struct { Clickhouse Clickhouse Mongo Mongo + KVStore KVStore + File datasource.FileSystem } diff --git a/pkg/gofr/container/datasources.go b/pkg/gofr/container/datasources.go index d1bed6525..2f44034ca 100644 --- a/pkg/gofr/container/datasources.go +++ b/pkg/gofr/container/datasources.go @@ -10,6 +10,8 @@ import ( gofrSQL "gofr.dev/pkg/gofr/datasource/sql" ) +//go:generate go run go.uber.org/mock/mockgen -source=datasources.go -destination=mock_datasources.go -package=container + type DB interface { Query(query string, args ...interface{}) (*sql.Rows, error) QueryRow(query string, args ...interface{}) *sql.Row @@ -199,3 +201,17 @@ type HealthChecker interface { // It is done to avoid adding packages which are not being used. HealthCheck(context.Context) (any, error) } + +type KVStore interface { + Get(ctx context.Context, key string) (string, error) + Set(ctx context.Context, key, value string) error + Delete(ctx context.Context, key string) error + + HealthChecker +} + +type KVStoreProvider interface { + KVStore + + provider +} diff --git a/pkg/gofr/container/health.go b/pkg/gofr/container/health.go index d65cd9e41..307a24eec 100644 --- a/pkg/gofr/container/health.go +++ b/pkg/gofr/container/health.go @@ -84,6 +84,15 @@ func checkExternalDBHealth(ctx context.Context, c *Container, healthMap map[stri healthMap["clickHouse"] = health } + if !isNil(c.KVStore) { + health, err := c.KVStore.HealthCheck(ctx) + if err != nil { + downCount++ + } + + healthMap["kv-store"] = health + } + return downCount } diff --git a/pkg/gofr/container/health_test.go b/pkg/gofr/container/health_test.go index 1c331353b..2228db855 100644 --- a/pkg/gofr/container/health_test.go +++ b/pkg/gofr/container/health_test.go @@ -34,6 +34,12 @@ func TestContainer_Health(t *testing.T) { for i, tc := range tests { expected := map[string]interface{}{ + "kv-store": datasource.Health{ + Status: tc.datasourceHealth, Details: map[string]interface{}{ + "host": "localhost:1234", + "error": "kv-store not connected", + }, + }, "redis": datasource.Health{ Status: tc.datasourceHealth, Details: map[string]interface{}{ "host": "localhost:6379", @@ -141,4 +147,12 @@ func registerMocks(mocks Mocks, health string) { "error": "clickhouse not connected", }, }, nil) + + mocks.KVStore.EXPECT().HealthCheck(context.Background()).Return(datasource.Health{ + Status: health, + Details: map[string]interface{}{ + "host": "localhost:1234", + "error": "kv-store not connected", + }, + }, nil) } diff --git a/pkg/gofr/container/mock_container.go b/pkg/gofr/container/mock_container.go index 84f6e4bce..608081cf2 100644 --- a/pkg/gofr/container/mock_container.go +++ b/pkg/gofr/container/mock_container.go @@ -19,6 +19,7 @@ type Mocks struct { Clickhouse *MockClickhouse Cassandra *MockCassandra Mongo *MockMongo + KVStore *MockKVStore } func NewMockContainer(t *testing.T) (*Container, Mocks) { @@ -44,12 +45,16 @@ func NewMockContainer(t *testing.T) (*Container, Mocks) { mongoMock := NewMockMongo(ctrl) container.Mongo = mongoMock + kvStoreMock := NewMockKVStore(ctrl) + container.KVStore = kvStoreMock + mocks := Mocks{ Redis: redisMock, SQL: sqlMock, Clickhouse: clickhouseMock, Cassandra: cassandraMock, Mongo: mongoMock, + KVStore: kvStoreMock, } mockMetrics := NewMockMetrics(ctrl) diff --git a/pkg/gofr/container/mock_datasources.go b/pkg/gofr/container/mock_datasources.go index fe01a93b2..a7f40a443 100644 --- a/pkg/gofr/container/mock_datasources.go +++ b/pkg/gofr/container/mock_datasources.go @@ -17,7 +17,6 @@ import ( redis "github.com/redis/go-redis/v9" gomock "go.uber.org/mock/gomock" - datasource "gofr.dev/pkg/gofr/datasource" sql0 "gofr.dev/pkg/gofr/datasource/sql" ) @@ -806,6 +805,25 @@ func (mr *MockRedisMockRecorder) BitField(ctx, key any, values ...any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BitField", reflect.TypeOf((*MockRedis)(nil).BitField), varargs...) } +// BitFieldRO mocks base method. +func (m *MockRedis) BitFieldRO(ctx context.Context, key string, values ...any) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range values { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BitFieldRO", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// BitFieldRO indicates an expected call of BitFieldRO. +func (mr *MockRedisMockRecorder) BitFieldRO(ctx, key any, values ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, values...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BitFieldRO", reflect.TypeOf((*MockRedis)(nil).BitFieldRO), varargs...) +} + // BitOpAnd mocks base method. func (m *MockRedis) BitOpAnd(ctx context.Context, destKey string, keys ...string) *redis.IntCmd { m.ctrl.T.Helper() @@ -2214,6 +2232,413 @@ func (mr *MockRedisMockRecorder) FCallRo(ctx, function, keys any, args ...any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FCallRo", reflect.TypeOf((*MockRedis)(nil).FCallRo), varargs...) } +// FTAggregate mocks base method. +func (m *MockRedis) FTAggregate(ctx context.Context, index, query string) *redis.MapStringInterfaceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAggregate", ctx, index, query) + ret0, _ := ret[0].(*redis.MapStringInterfaceCmd) + return ret0 +} + +// FTAggregate indicates an expected call of FTAggregate. +func (mr *MockRedisMockRecorder) FTAggregate(ctx, index, query any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAggregate", reflect.TypeOf((*MockRedis)(nil).FTAggregate), ctx, index, query) +} + +// FTAggregateWithArgs mocks base method. +func (m *MockRedis) FTAggregateWithArgs(ctx context.Context, index, query string, options *redis.FTAggregateOptions) *redis.AggregateCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAggregateWithArgs", ctx, index, query, options) + ret0, _ := ret[0].(*redis.AggregateCmd) + return ret0 +} + +// FTAggregateWithArgs indicates an expected call of FTAggregateWithArgs. +func (mr *MockRedisMockRecorder) FTAggregateWithArgs(ctx, index, query, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAggregateWithArgs", reflect.TypeOf((*MockRedis)(nil).FTAggregateWithArgs), ctx, index, query, options) +} + +// FTAliasAdd mocks base method. +func (m *MockRedis) FTAliasAdd(ctx context.Context, index, alias string) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAliasAdd", ctx, index, alias) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTAliasAdd indicates an expected call of FTAliasAdd. +func (mr *MockRedisMockRecorder) FTAliasAdd(ctx, index, alias any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAliasAdd", reflect.TypeOf((*MockRedis)(nil).FTAliasAdd), ctx, index, alias) +} + +// FTAliasDel mocks base method. +func (m *MockRedis) FTAliasDel(ctx context.Context, alias string) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAliasDel", ctx, alias) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTAliasDel indicates an expected call of FTAliasDel. +func (mr *MockRedisMockRecorder) FTAliasDel(ctx, alias any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAliasDel", reflect.TypeOf((*MockRedis)(nil).FTAliasDel), ctx, alias) +} + +// FTAliasUpdate mocks base method. +func (m *MockRedis) FTAliasUpdate(ctx context.Context, index, alias string) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAliasUpdate", ctx, index, alias) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTAliasUpdate indicates an expected call of FTAliasUpdate. +func (mr *MockRedisMockRecorder) FTAliasUpdate(ctx, index, alias any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAliasUpdate", reflect.TypeOf((*MockRedis)(nil).FTAliasUpdate), ctx, index, alias) +} + +// FTAlter mocks base method. +func (m *MockRedis) FTAlter(ctx context.Context, index string, skipInitalScan bool, definition []any) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTAlter", ctx, index, skipInitalScan, definition) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTAlter indicates an expected call of FTAlter. +func (mr *MockRedisMockRecorder) FTAlter(ctx, index, skipInitalScan, definition any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTAlter", reflect.TypeOf((*MockRedis)(nil).FTAlter), ctx, index, skipInitalScan, definition) +} + +// FTConfigGet mocks base method. +func (m *MockRedis) FTConfigGet(ctx context.Context, option string) *redis.MapMapStringInterfaceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTConfigGet", ctx, option) + ret0, _ := ret[0].(*redis.MapMapStringInterfaceCmd) + return ret0 +} + +// FTConfigGet indicates an expected call of FTConfigGet. +func (mr *MockRedisMockRecorder) FTConfigGet(ctx, option any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTConfigGet", reflect.TypeOf((*MockRedis)(nil).FTConfigGet), ctx, option) +} + +// FTConfigSet mocks base method. +func (m *MockRedis) FTConfigSet(ctx context.Context, option string, value any) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTConfigSet", ctx, option, value) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTConfigSet indicates an expected call of FTConfigSet. +func (mr *MockRedisMockRecorder) FTConfigSet(ctx, option, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTConfigSet", reflect.TypeOf((*MockRedis)(nil).FTConfigSet), ctx, option, value) +} + +// FTCreate mocks base method. +func (m *MockRedis) FTCreate(ctx context.Context, index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *redis.StatusCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, index, options} + for _, a := range schema { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FTCreate", varargs...) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTCreate indicates an expected call of FTCreate. +func (mr *MockRedisMockRecorder) FTCreate(ctx, index, options any, schema ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, index, options}, schema...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTCreate", reflect.TypeOf((*MockRedis)(nil).FTCreate), varargs...) +} + +// FTCursorDel mocks base method. +func (m *MockRedis) FTCursorDel(ctx context.Context, index string, cursorId int) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTCursorDel", ctx, index, cursorId) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTCursorDel indicates an expected call of FTCursorDel. +func (mr *MockRedisMockRecorder) FTCursorDel(ctx, index, cursorId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTCursorDel", reflect.TypeOf((*MockRedis)(nil).FTCursorDel), ctx, index, cursorId) +} + +// FTCursorRead mocks base method. +func (m *MockRedis) FTCursorRead(ctx context.Context, index string, cursorId, count int) *redis.MapStringInterfaceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTCursorRead", ctx, index, cursorId, count) + ret0, _ := ret[0].(*redis.MapStringInterfaceCmd) + return ret0 +} + +// FTCursorRead indicates an expected call of FTCursorRead. +func (mr *MockRedisMockRecorder) FTCursorRead(ctx, index, cursorId, count any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTCursorRead", reflect.TypeOf((*MockRedis)(nil).FTCursorRead), ctx, index, cursorId, count) +} + +// FTDictAdd mocks base method. +func (m *MockRedis) FTDictAdd(ctx context.Context, dict string, term ...any) *redis.IntCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, dict} + for _, a := range term { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FTDictAdd", varargs...) + ret0, _ := ret[0].(*redis.IntCmd) + return ret0 +} + +// FTDictAdd indicates an expected call of FTDictAdd. +func (mr *MockRedisMockRecorder) FTDictAdd(ctx, dict any, term ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, dict}, term...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTDictAdd", reflect.TypeOf((*MockRedis)(nil).FTDictAdd), varargs...) +} + +// FTDictDel mocks base method. +func (m *MockRedis) FTDictDel(ctx context.Context, dict string, term ...any) *redis.IntCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, dict} + for _, a := range term { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FTDictDel", varargs...) + ret0, _ := ret[0].(*redis.IntCmd) + return ret0 +} + +// FTDictDel indicates an expected call of FTDictDel. +func (mr *MockRedisMockRecorder) FTDictDel(ctx, dict any, term ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, dict}, term...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTDictDel", reflect.TypeOf((*MockRedis)(nil).FTDictDel), varargs...) +} + +// FTDictDump mocks base method. +func (m *MockRedis) FTDictDump(ctx context.Context, dict string) *redis.StringSliceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTDictDump", ctx, dict) + ret0, _ := ret[0].(*redis.StringSliceCmd) + return ret0 +} + +// FTDictDump indicates an expected call of FTDictDump. +func (mr *MockRedisMockRecorder) FTDictDump(ctx, dict any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTDictDump", reflect.TypeOf((*MockRedis)(nil).FTDictDump), ctx, dict) +} + +// FTDropIndex mocks base method. +func (m *MockRedis) FTDropIndex(ctx context.Context, index string) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTDropIndex", ctx, index) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTDropIndex indicates an expected call of FTDropIndex. +func (mr *MockRedisMockRecorder) FTDropIndex(ctx, index any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTDropIndex", reflect.TypeOf((*MockRedis)(nil).FTDropIndex), ctx, index) +} + +// FTDropIndexWithArgs mocks base method. +func (m *MockRedis) FTDropIndexWithArgs(ctx context.Context, index string, options *redis.FTDropIndexOptions) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTDropIndexWithArgs", ctx, index, options) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTDropIndexWithArgs indicates an expected call of FTDropIndexWithArgs. +func (mr *MockRedisMockRecorder) FTDropIndexWithArgs(ctx, index, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTDropIndexWithArgs", reflect.TypeOf((*MockRedis)(nil).FTDropIndexWithArgs), ctx, index, options) +} + +// FTExplain mocks base method. +func (m *MockRedis) FTExplain(ctx context.Context, index, query string) *redis.StringCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTExplain", ctx, index, query) + ret0, _ := ret[0].(*redis.StringCmd) + return ret0 +} + +// FTExplain indicates an expected call of FTExplain. +func (mr *MockRedisMockRecorder) FTExplain(ctx, index, query any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTExplain", reflect.TypeOf((*MockRedis)(nil).FTExplain), ctx, index, query) +} + +// FTExplainWithArgs mocks base method. +func (m *MockRedis) FTExplainWithArgs(ctx context.Context, index, query string, options *redis.FTExplainOptions) *redis.StringCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTExplainWithArgs", ctx, index, query, options) + ret0, _ := ret[0].(*redis.StringCmd) + return ret0 +} + +// FTExplainWithArgs indicates an expected call of FTExplainWithArgs. +func (mr *MockRedisMockRecorder) FTExplainWithArgs(ctx, index, query, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTExplainWithArgs", reflect.TypeOf((*MockRedis)(nil).FTExplainWithArgs), ctx, index, query, options) +} + +// FTInfo mocks base method. +func (m *MockRedis) FTInfo(ctx context.Context, index string) *redis.FTInfoCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTInfo", ctx, index) + ret0, _ := ret[0].(*redis.FTInfoCmd) + return ret0 +} + +// FTInfo indicates an expected call of FTInfo. +func (mr *MockRedisMockRecorder) FTInfo(ctx, index any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTInfo", reflect.TypeOf((*MockRedis)(nil).FTInfo), ctx, index) +} + +// FTSearch mocks base method. +func (m *MockRedis) FTSearch(ctx context.Context, index, query string) *redis.FTSearchCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSearch", ctx, index, query) + ret0, _ := ret[0].(*redis.FTSearchCmd) + return ret0 +} + +// FTSearch indicates an expected call of FTSearch. +func (mr *MockRedisMockRecorder) FTSearch(ctx, index, query any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSearch", reflect.TypeOf((*MockRedis)(nil).FTSearch), ctx, index, query) +} + +// FTSearchWithArgs mocks base method. +func (m *MockRedis) FTSearchWithArgs(ctx context.Context, index, query string, options *redis.FTSearchOptions) *redis.FTSearchCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSearchWithArgs", ctx, index, query, options) + ret0, _ := ret[0].(*redis.FTSearchCmd) + return ret0 +} + +// FTSearchWithArgs indicates an expected call of FTSearchWithArgs. +func (mr *MockRedisMockRecorder) FTSearchWithArgs(ctx, index, query, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSearchWithArgs", reflect.TypeOf((*MockRedis)(nil).FTSearchWithArgs), ctx, index, query, options) +} + +// FTSpellCheck mocks base method. +func (m *MockRedis) FTSpellCheck(ctx context.Context, index, query string) *redis.FTSpellCheckCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSpellCheck", ctx, index, query) + ret0, _ := ret[0].(*redis.FTSpellCheckCmd) + return ret0 +} + +// FTSpellCheck indicates an expected call of FTSpellCheck. +func (mr *MockRedisMockRecorder) FTSpellCheck(ctx, index, query any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSpellCheck", reflect.TypeOf((*MockRedis)(nil).FTSpellCheck), ctx, index, query) +} + +// FTSpellCheckWithArgs mocks base method. +func (m *MockRedis) FTSpellCheckWithArgs(ctx context.Context, index, query string, options *redis.FTSpellCheckOptions) *redis.FTSpellCheckCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSpellCheckWithArgs", ctx, index, query, options) + ret0, _ := ret[0].(*redis.FTSpellCheckCmd) + return ret0 +} + +// FTSpellCheckWithArgs indicates an expected call of FTSpellCheckWithArgs. +func (mr *MockRedisMockRecorder) FTSpellCheckWithArgs(ctx, index, query, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSpellCheckWithArgs", reflect.TypeOf((*MockRedis)(nil).FTSpellCheckWithArgs), ctx, index, query, options) +} + +// FTSynDump mocks base method. +func (m *MockRedis) FTSynDump(ctx context.Context, index string) *redis.FTSynDumpCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSynDump", ctx, index) + ret0, _ := ret[0].(*redis.FTSynDumpCmd) + return ret0 +} + +// FTSynDump indicates an expected call of FTSynDump. +func (mr *MockRedisMockRecorder) FTSynDump(ctx, index any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSynDump", reflect.TypeOf((*MockRedis)(nil).FTSynDump), ctx, index) +} + +// FTSynUpdate mocks base method. +func (m *MockRedis) FTSynUpdate(ctx context.Context, index string, synGroupId any, terms []any) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSynUpdate", ctx, index, synGroupId, terms) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTSynUpdate indicates an expected call of FTSynUpdate. +func (mr *MockRedisMockRecorder) FTSynUpdate(ctx, index, synGroupId, terms any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSynUpdate", reflect.TypeOf((*MockRedis)(nil).FTSynUpdate), ctx, index, synGroupId, terms) +} + +// FTSynUpdateWithArgs mocks base method. +func (m *MockRedis) FTSynUpdateWithArgs(ctx context.Context, index string, synGroupId any, options *redis.FTSynUpdateOptions, terms []any) *redis.StatusCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTSynUpdateWithArgs", ctx, index, synGroupId, options, terms) + ret0, _ := ret[0].(*redis.StatusCmd) + return ret0 +} + +// FTSynUpdateWithArgs indicates an expected call of FTSynUpdateWithArgs. +func (mr *MockRedisMockRecorder) FTSynUpdateWithArgs(ctx, index, synGroupId, options, terms any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTSynUpdateWithArgs", reflect.TypeOf((*MockRedis)(nil).FTSynUpdateWithArgs), ctx, index, synGroupId, options, terms) +} + +// FTTagVals mocks base method. +func (m *MockRedis) FTTagVals(ctx context.Context, index, field string) *redis.StringSliceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FTTagVals", ctx, index, field) + ret0, _ := ret[0].(*redis.StringSliceCmd) + return ret0 +} + +// FTTagVals indicates an expected call of FTTagVals. +func (mr *MockRedisMockRecorder) FTTagVals(ctx, index, field any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FTTagVals", reflect.TypeOf((*MockRedis)(nil).FTTagVals), ctx, index, field) +} + +// FT_List mocks base method. +func (m *MockRedis) FT_List(ctx context.Context) *redis.StringSliceCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FT_List", ctx) + ret0, _ := ret[0].(*redis.StringSliceCmd) + return ret0 +} + +// FT_List indicates an expected call of FT_List. +func (mr *MockRedisMockRecorder) FT_List(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FT_List", reflect.TypeOf((*MockRedis)(nil).FT_List), ctx) +} + // FlushAll mocks base method. func (m *MockRedis) FlushAll(ctx context.Context) *redis.StatusCmd { m.ctrl.T.Helper() @@ -2670,30 +3095,125 @@ func (m *MockRedis) HDel(ctx context.Context, key string, fields ...string) *red for _, a := range fields { varargs = append(varargs, a) } - ret := m.ctrl.Call(m, "HDel", varargs...) - ret0, _ := ret[0].(*redis.IntCmd) + ret := m.ctrl.Call(m, "HDel", varargs...) + ret0, _ := ret[0].(*redis.IntCmd) + return ret0 +} + +// HDel indicates an expected call of HDel. +func (mr *MockRedisMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedis)(nil).HDel), varargs...) +} + +// HExists mocks base method. +func (m *MockRedis) HExists(ctx context.Context, key, field string) *redis.BoolCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HExists", ctx, key, field) + ret0, _ := ret[0].(*redis.BoolCmd) + return ret0 +} + +// HExists indicates an expected call of HExists. +func (mr *MockRedisMockRecorder) HExists(ctx, key, field any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExists", reflect.TypeOf((*MockRedis)(nil).HExists), ctx, key, field) +} + +// HExpire mocks base method. +func (m *MockRedis) HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, expiration} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HExpire", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HExpire indicates an expected call of HExpire. +func (mr *MockRedisMockRecorder) HExpire(ctx, key, expiration any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, expiration}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpire", reflect.TypeOf((*MockRedis)(nil).HExpire), varargs...) +} + +// HExpireAt mocks base method. +func (m *MockRedis) HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, tm} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HExpireAt", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HExpireAt indicates an expected call of HExpireAt. +func (mr *MockRedisMockRecorder) HExpireAt(ctx, key, tm any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, tm}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireAt", reflect.TypeOf((*MockRedis)(nil).HExpireAt), varargs...) +} + +// HExpireAtWithArgs mocks base method. +func (m *MockRedis) HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, tm, expirationArgs} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HExpireAtWithArgs", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HExpireAtWithArgs indicates an expected call of HExpireAtWithArgs. +func (mr *MockRedisMockRecorder) HExpireAtWithArgs(ctx, key, tm, expirationArgs any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, tm, expirationArgs}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireAtWithArgs", reflect.TypeOf((*MockRedis)(nil).HExpireAtWithArgs), varargs...) +} + +// HExpireTime mocks base method. +func (m *MockRedis) HExpireTime(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HExpireTime", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) return ret0 } -// HDel indicates an expected call of HDel. -func (mr *MockRedisMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call { +// HExpireTime indicates an expected call of HExpireTime. +func (mr *MockRedisMockRecorder) HExpireTime(ctx, key any, fields ...any) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]any{ctx, key}, fields...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedis)(nil).HDel), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireTime", reflect.TypeOf((*MockRedis)(nil).HExpireTime), varargs...) } -// HExists mocks base method. -func (m *MockRedis) HExists(ctx context.Context, key, field string) *redis.BoolCmd { +// HExpireWithArgs mocks base method. +func (m *MockRedis) HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HExists", ctx, key, field) - ret0, _ := ret[0].(*redis.BoolCmd) + varargs := []any{ctx, key, expiration, expirationArgs} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HExpireWithArgs", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) return ret0 } -// HExists indicates an expected call of HExists. -func (mr *MockRedisMockRecorder) HExists(ctx, key, field any) *gomock.Call { +// HExpireWithArgs indicates an expected call of HExpireWithArgs. +func (mr *MockRedisMockRecorder) HExpireWithArgs(ctx, key, expiration, expirationArgs any, fields ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExists", reflect.TypeOf((*MockRedis)(nil).HExists), ctx, key, field) + varargs := append([]any{ctx, key, expiration, expirationArgs}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireWithArgs", reflect.TypeOf((*MockRedis)(nil).HExpireWithArgs), varargs...) } // HGet mocks base method. @@ -2818,6 +3338,139 @@ func (mr *MockRedisMockRecorder) HMSet(ctx, key any, values ...any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HMSet", reflect.TypeOf((*MockRedis)(nil).HMSet), varargs...) } +// HPExpire mocks base method. +func (m *MockRedis) HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, expiration} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPExpire", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPExpire indicates an expected call of HPExpire. +func (mr *MockRedisMockRecorder) HPExpire(ctx, key, expiration any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, expiration}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpire", reflect.TypeOf((*MockRedis)(nil).HPExpire), varargs...) +} + +// HPExpireAt mocks base method. +func (m *MockRedis) HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, tm} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPExpireAt", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPExpireAt indicates an expected call of HPExpireAt. +func (mr *MockRedisMockRecorder) HPExpireAt(ctx, key, tm any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, tm}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireAt", reflect.TypeOf((*MockRedis)(nil).HPExpireAt), varargs...) +} + +// HPExpireAtWithArgs mocks base method. +func (m *MockRedis) HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, tm, expirationArgs} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPExpireAtWithArgs", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPExpireAtWithArgs indicates an expected call of HPExpireAtWithArgs. +func (mr *MockRedisMockRecorder) HPExpireAtWithArgs(ctx, key, tm, expirationArgs any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, tm, expirationArgs}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireAtWithArgs", reflect.TypeOf((*MockRedis)(nil).HPExpireAtWithArgs), varargs...) +} + +// HPExpireTime mocks base method. +func (m *MockRedis) HPExpireTime(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPExpireTime", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPExpireTime indicates an expected call of HPExpireTime. +func (mr *MockRedisMockRecorder) HPExpireTime(ctx, key any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireTime", reflect.TypeOf((*MockRedis)(nil).HPExpireTime), varargs...) +} + +// HPExpireWithArgs mocks base method. +func (m *MockRedis) HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key, expiration, expirationArgs} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPExpireWithArgs", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPExpireWithArgs indicates an expected call of HPExpireWithArgs. +func (mr *MockRedisMockRecorder) HPExpireWithArgs(ctx, key, expiration, expirationArgs any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, expiration, expirationArgs}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireWithArgs", reflect.TypeOf((*MockRedis)(nil).HPExpireWithArgs), varargs...) +} + +// HPTTL mocks base method. +func (m *MockRedis) HPTTL(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPTTL", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPTTL indicates an expected call of HPTTL. +func (mr *MockRedisMockRecorder) HPTTL(ctx, key any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPTTL", reflect.TypeOf((*MockRedis)(nil).HPTTL), varargs...) +} + +// HPersist mocks base method. +func (m *MockRedis) HPersist(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HPersist", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HPersist indicates an expected call of HPersist. +func (mr *MockRedisMockRecorder) HPersist(ctx, key any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPersist", reflect.TypeOf((*MockRedis)(nil).HPersist), varargs...) +} + // HRandField mocks base method. func (m *MockRedis) HRandField(ctx context.Context, key string, count int) *redis.StringSliceCmd { m.ctrl.T.Helper() @@ -2860,6 +3513,20 @@ func (mr *MockRedisMockRecorder) HScan(ctx, key, cursor, match, count any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HScan", reflect.TypeOf((*MockRedis)(nil).HScan), ctx, key, cursor, match, count) } +// HScanNoValues mocks base method. +func (m *MockRedis) HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HScanNoValues", ctx, key, cursor, match, count) + ret0, _ := ret[0].(*redis.ScanCmd) + return ret0 +} + +// HScanNoValues indicates an expected call of HScanNoValues. +func (mr *MockRedisMockRecorder) HScanNoValues(ctx, key, cursor, match, count any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HScanNoValues", reflect.TypeOf((*MockRedis)(nil).HScanNoValues), ctx, key, cursor, match, count) +} + // HSet mocks base method. func (m *MockRedis) HSet(ctx context.Context, key string, values ...any) *redis.IntCmd { m.ctrl.T.Helper() @@ -2893,6 +3560,25 @@ func (mr *MockRedisMockRecorder) HSetNX(ctx, key, field, value any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSetNX", reflect.TypeOf((*MockRedis)(nil).HSetNX), ctx, key, field, value) } +// HTTL mocks base method. +func (m *MockRedis) HTTL(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { + m.ctrl.T.Helper() + varargs := []any{ctx, key} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HTTL", varargs...) + ret0, _ := ret[0].(*redis.IntSliceCmd) + return ret0 +} + +// HTTL indicates an expected call of HTTL. +func (mr *MockRedisMockRecorder) HTTL(ctx, key any, fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTL", reflect.TypeOf((*MockRedis)(nil).HTTL), varargs...) +} + // HVals mocks base method. func (m *MockRedis) HVals(ctx context.Context, key string) *redis.StringSliceCmd { m.ctrl.T.Helper() @@ -8385,3 +9071,201 @@ func (mr *MockHealthCheckerMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockHealthChecker)(nil).HealthCheck), arg0) } + +// MockKVStore is a mock of KVStore interface. +type MockKVStore struct { + ctrl *gomock.Controller + recorder *MockKVStoreMockRecorder +} + +// MockKVStoreMockRecorder is the mock recorder for MockKVStore. +type MockKVStoreMockRecorder struct { + mock *MockKVStore +} + +// NewMockKVStore creates a new mock instance. +func NewMockKVStore(ctrl *gomock.Controller) *MockKVStore { + mock := &MockKVStore{ctrl: ctrl} + mock.recorder = &MockKVStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKVStore) EXPECT() *MockKVStoreMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockKVStore) Delete(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockKVStoreMockRecorder) Delete(ctx, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockKVStore)(nil).Delete), ctx, key) +} + +// Get mocks base method. +func (m *MockKVStore) Get(ctx context.Context, key string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, key) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockKVStoreMockRecorder) Get(ctx, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockKVStore)(nil).Get), ctx, key) +} + +// HealthCheck mocks base method. +func (m *MockKVStore) HealthCheck(arg0 context.Context) (any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0) + ret0, _ := ret[0].(any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockKVStoreMockRecorder) HealthCheck(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockKVStore)(nil).HealthCheck), arg0) +} + +// Set mocks base method. +func (m *MockKVStore) Set(ctx context.Context, key, value string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Set", ctx, key, value) + ret0, _ := ret[0].(error) + return ret0 +} + +// Set indicates an expected call of Set. +func (mr *MockKVStoreMockRecorder) Set(ctx, key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockKVStore)(nil).Set), ctx, key, value) +} + +// MockKVStoreProvider is a mock of KVStoreProvider interface. +type MockKVStoreProvider struct { + ctrl *gomock.Controller + recorder *MockKVStoreProviderMockRecorder +} + +// MockKVStoreProviderMockRecorder is the mock recorder for MockKVStoreProvider. +type MockKVStoreProviderMockRecorder struct { + mock *MockKVStoreProvider +} + +// NewMockKVStoreProvider creates a new mock instance. +func NewMockKVStoreProvider(ctrl *gomock.Controller) *MockKVStoreProvider { + mock := &MockKVStoreProvider{ctrl: ctrl} + mock.recorder = &MockKVStoreProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKVStoreProvider) EXPECT() *MockKVStoreProviderMockRecorder { + return m.recorder +} + +// Connect mocks base method. +func (m *MockKVStoreProvider) Connect() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Connect") +} + +// Connect indicates an expected call of Connect. +func (mr *MockKVStoreProviderMockRecorder) Connect() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockKVStoreProvider)(nil).Connect)) +} + +// Delete mocks base method. +func (m *MockKVStoreProvider) Delete(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockKVStoreProviderMockRecorder) Delete(ctx, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockKVStoreProvider)(nil).Delete), ctx, key) +} + +// Get mocks base method. +func (m *MockKVStoreProvider) Get(ctx context.Context, key string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, key) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockKVStoreProviderMockRecorder) Get(ctx, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockKVStoreProvider)(nil).Get), ctx, key) +} + +// HealthCheck mocks base method. +func (m *MockKVStoreProvider) HealthCheck(arg0 context.Context) (any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0) + ret0, _ := ret[0].(any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockKVStoreProviderMockRecorder) HealthCheck(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockKVStoreProvider)(nil).HealthCheck), arg0) +} + +// Set mocks base method. +func (m *MockKVStoreProvider) Set(ctx context.Context, key, value string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Set", ctx, key, value) + ret0, _ := ret[0].(error) + return ret0 +} + +// Set indicates an expected call of Set. +func (mr *MockKVStoreProviderMockRecorder) Set(ctx, key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockKVStoreProvider)(nil).Set), ctx, key, value) +} + +// UseLogger mocks base method. +func (m *MockKVStoreProvider) UseLogger(logger any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "UseLogger", logger) +} + +// UseLogger indicates an expected call of UseLogger. +func (mr *MockKVStoreProviderMockRecorder) UseLogger(logger any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseLogger", reflect.TypeOf((*MockKVStoreProvider)(nil).UseLogger), logger) +} + +// UseMetrics mocks base method. +func (m *MockKVStoreProvider) UseMetrics(metrics any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "UseMetrics", metrics) +} + +// UseMetrics indicates an expected call of UseMetrics. +func (mr *MockKVStoreProviderMockRecorder) UseMetrics(metrics any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseMetrics", reflect.TypeOf((*MockKVStoreProvider)(nil).UseMetrics), metrics) +} diff --git a/pkg/gofr/datasource/kv-store/badger/badger.go b/pkg/gofr/datasource/kv-store/badger/badger.go new file mode 100644 index 000000000..7cc3b4818 --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/badger.go @@ -0,0 +1,161 @@ +package badger + +import ( + "context" + "errors" + "strings" + "time" + + "github.com/dgraph-io/badger/v4" +) + +type Configs struct { + DirPath string +} + +type client struct { + db *badger.DB + configs Configs + logger Logger + metrics Metrics +} + +func New(configs Configs) *client { + return &client{configs: configs} +} + +// UseLogger sets the logger for the BadgerDB client which asserts the Logger interface. +func (c *client) UseLogger(logger any) { + if l, ok := logger.(Logger); ok { + c.logger = l + } +} + +// UseMetrics sets the metrics for the BadgerDB client which asserts the Metrics interface. +func (c *client) UseMetrics(metrics any) { + if m, ok := metrics.(Metrics); ok { + c.metrics = m + } +} + +// Connect establishes a connection to BadgerDB and registers metrics using the provided configuration when the client was Created. +func (c *client) Connect() { + c.logger.Infof("connecting to BadgerDB at %v", c.configs.DirPath) + + badgerBuckets := []float64{.05, .075, .1, .125, .15, .2, .3, .5, .75, 1, 2, 3, 4, 5, 7.5, 10} + c.metrics.NewHistogram("app_badger_stats", "Response time of Badger queries in milliseconds.", badgerBuckets...) + + db, err := badger.Open(badger.DefaultOptions(c.configs.DirPath)) + if err != nil { + c.logger.Errorf("error while connecting to BadgerDB: %v", err) + } + + c.db = db +} + +func (c *client) Get(_ context.Context, key string) (string, error) { + defer c.logQueryAndSendMetrics(time.Now(), "GET", key, "") + + var value []byte + + // transaction is set to false as we don't want to make any changes to data. + txn := c.db.NewTransaction(false) + defer txn.Discard() + + item, err := txn.Get([]byte(key)) + if err != nil { + c.logger.Debugf("error while fetching data for key: %v, error: %v", key, err) + + return "", err + } + + value, err = item.ValueCopy(nil) + if err != nil { + c.logger.Debugf("error while reading value for key: %v, error: %v", key, err) + + return "", err + } + + err = txn.Commit() + if err != nil { + c.logger.Debugf("error while commiting transaction: %v", err) + + return "", err + } + + return string(value), nil +} + +func (c *client) Set(_ context.Context, key, value string) error { + defer c.logQueryAndSendMetrics(time.Now(), "SET", key, value) + + return c.useTransaction(func(txn *badger.Txn) error { + return txn.Set([]byte(key), []byte(value)) + }) +} + +func (c *client) Delete(_ context.Context, key string) error { + defer c.logQueryAndSendMetrics(time.Now(), "DELETE", key, "") + + return c.useTransaction(func(txn *badger.Txn) error { + return txn.Delete([]byte(key)) + }) +} + +func (c *client) useTransaction(f func(txn *badger.Txn) error) error { + txn := c.db.NewTransaction(true) + defer txn.Discard() + + err := f(txn) + if err != nil { + c.logger.Debugf("error while executing transaction: %v", err) + + return err + } + + err = txn.Commit() + if err != nil { + c.logger.Debugf("error while commiting transaction: %v", err) + + return err + } + + return nil +} + +func (c *client) logQueryAndSendMetrics(start time.Time, methodType string, kv ...string) { + duration := time.Since(start).Milliseconds() + + c.logger.Debug(&Log{ + Type: methodType, + Duration: duration, + Key: strings.Join(kv, " "), + }) + + c.metrics.RecordHistogram(context.Background(), "app_badger_stats", float64(duration), "database", c.configs.DirPath, + "type", methodType) +} + +type Health struct { + Status string `json:"status,omitempty"` + Details map[string]any `json:"details,omitempty"` +} + +func (c *client) HealthCheck(context.Context) (any, error) { + h := Health{ + Details: make(map[string]any), + } + + h.Details["location"] = c.configs.DirPath + + closed := c.db.IsClosed() + if closed { + h.Status = "DOWN" + + return &h, errors.New("status down") + } + + h.Status = "UP" + + return &h, nil +} diff --git a/pkg/gofr/datasource/kv-store/badger/badger_test.go b/pkg/gofr/datasource/kv-store/badger/badger_test.go new file mode 100644 index 000000000..66b9f7736 --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/badger_test.go @@ -0,0 +1,77 @@ +package badger + +import ( + "bytes" + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func setupDB(t *testing.T) *client { + t.Helper() + cl := New(Configs{DirPath: t.TempDir()}) + + var logs []byte + + ctrl := gomock.NewController(t) + mockMetrics := NewMockMetrics(ctrl) + + mockMetrics.EXPECT().NewHistogram("app_badger_stats", "Response time of Badger queries in milliseconds.", gomock.Any()) + + mockMetrics.EXPECT().RecordHistogram(gomock.Any(), "app_badger_stats", gomock.Any(), "database", cl.configs.DirPath, + "type", gomock.Any()).AnyTimes() + + cl.UseLogger(NewMockLogger(DEBUG, bytes.NewBuffer(logs))) + cl.UseMetrics(mockMetrics) + cl.Connect() + + return cl +} + +func Test_ClientSet(t *testing.T) { + cl := setupDB(t) + + err := cl.Set(context.Background(), "lkey", "lvalue") + + assert.NoError(t, err) +} + +func Test_ClientGet(t *testing.T) { + cl := setupDB(t) + + err := cl.Set(context.Background(), "lkey", "lvalue") + + val, err := cl.Get(context.Background(), "lkey") + + assert.NoError(t, err) + assert.Equal(t, "lvalue", val) +} + +func Test_ClientGetError(t *testing.T) { + cl := setupDB(t) + + val, err := cl.Get(context.Background(), "lkey") + + assert.EqualError(t, err, "Key not found") + assert.Empty(t, val) +} + +func Test_ClientDeleteSuccessError(t *testing.T) { + cl := setupDB(t) + + err := cl.Delete(context.Background(), "lkey") + + assert.NoError(t, err) +} + +func Test_ClientHealthCheck(t *testing.T) { + cl := setupDB(t) + + val, err := cl.HealthCheck(context.Background()) + + assert.NoError(t, err) + assert.Contains(t, fmt.Sprint(val), "UP") +} diff --git a/pkg/gofr/datasource/kv-store/badger/go.mod b/pkg/gofr/datasource/kv-store/badger/go.mod new file mode 100644 index 000000000..fad00618b --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/go.mod @@ -0,0 +1,31 @@ +module gofr.dev/pkg/gofr/datasource/kv-store/badger + +go 1.22 + +require ( + github.com/dgraph-io/badger/v4 v4.2.0 + github.com/stretchr/testify v1.9.0 + go.uber.org/mock v0.4.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/pkg/gofr/datasource/kv-store/badger/go.sum b/pkg/gofr/datasource/kv-store/badger/go.sum new file mode 100644 index 000000000..425187314 --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/gofr/datasource/kv-store/badger/logger.go b/pkg/gofr/datasource/kv-store/badger/logger.go new file mode 100644 index 000000000..073df9f4c --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/logger.go @@ -0,0 +1,28 @@ +package badger + +import ( + "fmt" + "io" + "strings" +) + +type Logger interface { + Debug(args ...any) + Debugf(pattern string, args ...any) + Info(args ...any) + Infof(pattern string, args ...any) + Error(args ...any) + Errorf(patter string, args ...any) +} + +type Log struct { + Type string `json:"type"` + Duration int64 `json:"duration"` + Key string `json:"key"` + Value string `json:"value,omitempty"` +} + +func (l *Log) PrettyPrint(writer io.Writer) { + fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;162m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s \n", + l.Type, "BADGR", l.Duration, strings.Join([]string{l.Key, l.Value}, " ")) +} diff --git a/pkg/gofr/datasource/kv-store/badger/logger_test.go b/pkg/gofr/datasource/kv-store/badger/logger_test.go new file mode 100644 index 000000000..c6d71bf8a --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/logger_test.go @@ -0,0 +1,22 @@ +package badger + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_PrettyPrint(t *testing.T) { + queryLog := Log{ + Type: "GET", + Duration: 12345, + } + expected := "GET" + + var buf bytes.Buffer + + queryLog.PrettyPrint(&buf) + + assert.Contains(t, buf.String(), expected) +} diff --git a/pkg/gofr/datasource/kv-store/badger/metrics.go b/pkg/gofr/datasource/kv-store/badger/metrics.go new file mode 100644 index 000000000..950f78a16 --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/metrics.go @@ -0,0 +1,9 @@ +package badger + +import "context" + +type Metrics interface { + NewHistogram(name, desc string, buckets ...float64) + + RecordHistogram(ctx context.Context, name string, value float64, labels ...string) +} diff --git a/pkg/gofr/datasource/kv-store/badger/mock_logger.go b/pkg/gofr/datasource/kv-store/badger/mock_logger.go new file mode 100644 index 000000000..e6527ad6e --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/mock_logger.go @@ -0,0 +1,74 @@ +package badger + +import ( + "bytes" + "fmt" + "io" +) + +// Level represents different logging levels. +type Level int + +const ( + DEBUG Level = iota + 1 + INFO + ERROR +) + +type MockLogger struct { + level Level + out io.Writer + errOut io.Writer +} + +func NewMockLogger(level Level, b *bytes.Buffer) Logger { + return &MockLogger{ + level: level, + out: b, + errOut: b, + } +} + +func (m *MockLogger) Debugf(pattern string, args ...interface{}) { + m.logf(DEBUG, pattern, args...) +} + +func (m *MockLogger) Debug(args ...interface{}) { + m.log(DEBUG, args...) +} + +func (m *MockLogger) Infof(pattern string, args ...interface{}) { + m.logf(INFO, pattern, args...) +} + +func (m *MockLogger) Info(args ...interface{}) { + m.log(INFO, args...) +} + +func (m *MockLogger) Errorf(patter string, args ...interface{}) { + m.logf(ERROR, patter, args...) +} + +func (m *MockLogger) Error(args ...interface{}) { + m.log(ERROR, args...) +} + +func (m *MockLogger) logf(level Level, format string, args ...interface{}) { + out := m.out + if level == ERROR { + out = m.errOut + } + + fmt.Fprintf(out, format+"\n", args...) +} + +func (m *MockLogger) log(level Level, args ...interface{}) { + out := m.out + if level == ERROR { + out = m.errOut + } + + message := fmt.Sprint(args...) + + fmt.Fprintf(out, "%v\n", message) +} diff --git a/pkg/gofr/datasource/kv-store/badger/mock_metrics.go b/pkg/gofr/datasource/kv-store/badger/mock_metrics.go new file mode 100644 index 000000000..643e7c16e --- /dev/null +++ b/pkg/gofr/datasource/kv-store/badger/mock_metrics.go @@ -0,0 +1,74 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: metrics.go +// +// Generated by this command: +// +// mockgen -source=metrics.go -destination=mock_metrics.go -package=badger +// + +// Package badger is a generated GoMock package. +package badger + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockMetrics is a mock of Metrics interface. +type MockMetrics struct { + ctrl *gomock.Controller + recorder *MockMetricsMockRecorder +} + +// MockMetricsMockRecorder is the mock recorder for MockMetrics. +type MockMetricsMockRecorder struct { + mock *MockMetrics +} + +// NewMockMetrics creates a new mock instance. +func NewMockMetrics(ctrl *gomock.Controller) *MockMetrics { + mock := &MockMetrics{ctrl: ctrl} + mock.recorder = &MockMetricsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMetrics) EXPECT() *MockMetricsMockRecorder { + return m.recorder +} + +// NewHistogram mocks base method. +func (m *MockMetrics) NewHistogram(name, desc string, buckets ...float64) { + m.ctrl.T.Helper() + varargs := []any{name, desc} + for _, a := range buckets { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "NewHistogram", varargs...) +} + +// NewHistogram indicates an expected call of NewHistogram. +func (mr *MockMetricsMockRecorder) NewHistogram(name, desc any, buckets ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{name, desc}, buckets...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewHistogram", reflect.TypeOf((*MockMetrics)(nil).NewHistogram), varargs...) +} + +// RecordHistogram mocks base method. +func (m *MockMetrics) RecordHistogram(ctx context.Context, name string, value float64, labels ...string) { + m.ctrl.T.Helper() + varargs := []any{ctx, name, value} + for _, a := range labels { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "RecordHistogram", varargs...) +} + +// RecordHistogram indicates an expected call of RecordHistogram. +func (mr *MockMetricsMockRecorder) RecordHistogram(ctx, name, value any, labels ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, name, value}, labels...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordHistogram", reflect.TypeOf((*MockMetrics)(nil).RecordHistogram), varargs...) +} diff --git a/pkg/gofr/external_db.go b/pkg/gofr/external_db.go index e12b78c80..1c9f7825a 100644 --- a/pkg/gofr/external_db.go +++ b/pkg/gofr/external_db.go @@ -39,3 +39,13 @@ func (a *App) AddCassandra(db container.CassandraProvider) { a.container.Cassandra = db } + +// AddKVStore sets the KV-Store datasource in the app's container. +func (a *App) AddKVStore(db container.KVStoreProvider) { + db.UseLogger(a.Logger()) + db.UseMetrics(a.Metrics()) + + db.Connect() + + a.container.KVStore = db +} diff --git a/pkg/gofr/gofr.go b/pkg/gofr/gofr.go index 0140be63a..1b16bba47 100644 --- a/pkg/gofr/gofr.go +++ b/pkg/gofr/gofr.go @@ -295,18 +295,8 @@ func (a *App) Migrate(migrationsMap map[int64]migration.Migrate) { migration.Run(migrationsMap, a.container) } +//nolint:gocyclo // once deprecated configs are removed, multiple if conditions will be removed and complexity will decrease func (a *App) initTracer() { - traceExporter := a.Config.Get("TRACE_EXPORTER") - tracerURL := a.Config.Get("TRACER_URL") - - // deprecated : tracer_host and tracer_port is deprecated and will be removed in upcoming versions - tracerHost := a.Config.Get("TRACER_HOST") - tracerPort := a.Config.GetOrDefault("TRACER_PORT", "9411") - - if tracerURL == "" && tracerHost != "" && tracerPort != "" { - a.Logger().Warn("TRACER_HOST and TRACER_PORT are deprecated, use TRACER_URL instead") - } - tp := sdktrace.NewTracerProvider( sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, @@ -317,6 +307,28 @@ func (a *App) initTracer() { otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) otel.SetErrorHandler(&otelErrorHandler{logger: a.container.Logger}) + traceExporter := a.Config.Get("TRACE_EXPORTER") + tracerURL := a.Config.Get("TRACER_URL") + + // deprecated : tracer_host and tracer_port is deprecated and will be removed in upcoming versions + tracerHost := a.Config.Get("TRACER_HOST") + tracerPort := a.Config.GetOrDefault("TRACER_PORT", "9411") + + if tracerURL != "" && traceExporter == "" { + a.Logger().Error("missing TRACE_EXPORTER config, should be provided with TRACER_URL to enable tracing") + return + } + + //nolint:revive // early-return is not possible here, as below is the intentional logging flow + if tracerURL == "" && traceExporter != "" && !strings.EqualFold(traceExporter, "gofr") { + if tracerHost != "" && tracerPort != "" { + a.Logger().Warn("TRACER_HOST and TRACER_PORT are deprecated, use TRACER_URL instead") + } else { + a.Logger().Error("missing TRACER_URL config, should be provided with TRACE_EXPORTER to enable tracing") + return + } + } + if (traceExporter != "" && tracerHost != "") || tracerURL != "" || traceExporter == gofrTraceExporter { exporter, err := a.getExporter(traceExporter, tracerHost, tracerPort, tracerURL) diff --git a/pkg/gofr/gofr_test.go b/pkg/gofr/gofr_test.go index 44f0e1e1b..cf03fa07c 100644 --- a/pkg/gofr/gofr_test.go +++ b/pkg/gofr/gofr_test.go @@ -323,14 +323,12 @@ func Test_AddRESTHandlers(t *testing.T) { func Test_initTracer(t *testing.T) { mockConfig1 := config.NewMockConfig(map[string]string{ "TRACE_EXPORTER": "zipkin", - "TRACER_HOST": "localhost", - "TRACER_PORT": "2005", + "TRACER_URL": "http://localhost:2005/api/v2/spans", }) mockConfig2 := config.NewMockConfig(map[string]string{ "TRACE_EXPORTER": "jaeger", - "TRACER_HOST": "localhost", - "TRACER_PORT": "2005", + "TRACER_URL": "localhost:2005", }) mockConfig3 := config.NewMockConfig(map[string]string{ @@ -339,15 +337,13 @@ func Test_initTracer(t *testing.T) { mockConfig4 := config.NewMockConfig(map[string]string{ "TRACE_EXPORTER": "zipkin", - "TRACER_HOST": "localhost", - "TRACER_PORT": "2005", + "TRACER_URL": "http://localhost:2005/api/v2/spans", "TRACER_AUTH_KEY": "valid-token", }) mockConfig5 := config.NewMockConfig(map[string]string{ "TRACE_EXPORTER": "jaeger", - "TRACER_HOST": "localhost", - "TRACER_PORT": "2005", + "TRACER_URL": "localhost:2005", "TRACER_AUTH_KEY": "valid-token", }) @@ -394,8 +390,7 @@ func Test_initTracer(t *testing.T) { func Test_initTracer_invalidConfig(t *testing.T) { mockConfig := config.NewMockConfig(map[string]string{ "TRACE_EXPORTER": "abc", - "TRACER_HOST": "localhost", - "TRACER_PORT": "2005", + "TRACER_URL": "localhost:2005", }) errLogMessage := testutil.StderrOutputForFunc(func() { diff --git a/pkg/gofr/logging/logger_test.go b/pkg/gofr/logging/logger_test.go index f009d1e09..72981a415 100644 --- a/pkg/gofr/logging/logger_test.go +++ b/pkg/gofr/logging/logger_test.go @@ -6,10 +6,12 @@ import ( "fmt" "io" "os" + "os/exec" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/term" "gofr.dev/pkg/gofr/testutil" @@ -104,29 +106,60 @@ func TestLogger_LevelWarn(t *testing.T) { infoLog := testutil.StdoutOutputForFunc(printLog) errLog := testutil.StderrOutputForFunc(printLog) - if strings.ContainsAny(infoLog, "NOTICE|INFO|DEBUG") && !strings.Contains(errLog, "ERROR") { - // Warn Log Level will not contain DEBUG,INFO, NOTICE logs - t.Errorf("TestLogger_LevelDebug Failed!") + levels := []Level{DEBUG, INFO, NOTICE} + + for i, l := range levels { + assert.NotContainsf(t, infoLog, l.String(), "TEST[%d], Failed.\nunexpected %s log", i, l) } assertMessageInJSONLog(t, errLog, "Test Error Log") } func TestLogger_LevelFatal(t *testing.T) { - printLog := func() { + // running the failing part only when a specific env variable is set + if os.Getenv("GOFR_EXITER") == "1" { logger := NewLogger(FATAL) + logger.Debugf("%s", "Test Debug Log") logger.Infof("%s", "Test Info Log") + logger.Logf("%s", "Test Log") logger.Noticef("%s", "Test Notice Log") logger.Warnf("%s", "Test Warn Log") logger.Errorf("%s", "Test Error Log") + logger.Fatalf("%s", "Test Fatal Log") + + return } - infoLog := testutil.StdoutOutputForFunc(printLog) - errLog := testutil.StderrOutputForFunc(printLog) + //nolint:gosec // starting the actual test in a different subprocess + cmd := exec.Command(os.Args[0], "-test.run=TestLogger_LevelFatal") + cmd.Env = append(os.Environ(), "GOFR_EXITER=1") + + stdout, err := cmd.StderrPipe() + require.NoError(t, err) + + require.NoError(t, cmd.Start()) + + logBytes, err := io.ReadAll(stdout) + require.NoError(t, err) + + log := string(logBytes) + + levels := []Level{DEBUG, INFO, NOTICE, WARN, ERROR} // levels which should not be present in case of FATAL log_level + + for i, l := range levels { + assert.NotContainsf(t, log, l.String(), "TEST[%d], Failed.\nunexpected %s log", i, l) + } + + assertMessageInJSONLog(t, log, "Test Fatal Log") + + // Check that the program exited + err = cmd.Wait() + + var e *exec.ExitError - assert.Equal(t, "", infoLog, "TestLogger_LevelFatal Failed!") - assert.Equal(t, "", errLog, "TestLogger_LevelFatal Failed") + require.ErrorAs(t, err, &e) + assert.False(t, e.Success()) } func assertMessageInJSONLog(t *testing.T, logLine, expectation string) { diff --git a/pkg/gofr/version/version.go b/pkg/gofr/version/version.go index 284c7b80f..4093a46ff 100644 --- a/pkg/gofr/version/version.go +++ b/pkg/gofr/version/version.go @@ -1,3 +1,3 @@ package version -const Framework = "v1.14.1" +const Framework = "v1.15.0"