├── .codeclimate.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
├── banner.gif
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── go.yml
│ ├── typos.yml
│ └── website.yml
├── .gitignore
├── .golangci.yml
├── .ls-lint.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── docs
├── Dockerfile
├── advanced-guide
│ ├── circuit-breaker
│ │ └── page.md
│ ├── custom-spans-in-tracing
│ │ └── page.md
│ ├── dealing-with-sql
│ │ └── page.md
│ ├── debugging
│ │ └── page.md
│ ├── gofr-errors
│ │ └── page.md
│ ├── grpc
│ │ └── page.md
│ ├── handling-data-migrations
│ │ └── page.md
│ ├── handling-file
│ │ └── page.md
│ ├── http-authentication
│ │ └── page.md
│ ├── http-communication
│ │ └── page.md
│ ├── injecting-databases-drivers
│ │ └── page.md
│ ├── key-value-store
│ │ └── page.md
│ ├── middlewares
│ │ └── page.md
│ ├── monitoring-service-health
│ │ └── page.md
│ ├── overriding-default
│ │ └── page.md
│ ├── publishing-custom-metrics
│ │ └── page.md
│ ├── remote-log-level-change
│ │ └── page.md
│ ├── serving-static-files
│ │ └── page.md
│ ├── setting-custom-response-headers
│ │ └── page.md
│ ├── swagger-documentation
│ │ └── page.md
│ ├── using-cron
│ │ └── page.md
│ ├── using-publisher-subscriber
│ │ └── page.md
│ └── websocket
│ │ └── page.md
├── datasources
│ ├── arangodb
│ │ └── page.md
│ ├── cassandra
│ │ └── page.md
│ ├── clickhouse
│ │ └── page.md
│ ├── dgraph
│ │ └── page.md
│ ├── elasticsearch
│ │ └── page.md
│ ├── getting-started
│ │ └── page.md
│ ├── mongodb
│ │ └── page.md
│ ├── opentsdb
│ │ └── page.md
│ ├── scylladb
│ │ └── page.md
│ ├── solr
│ │ └── page.md
│ └── surrealdb
│ │ └── page.md
├── events.json
├── navigation.js
├── page.md
├── public
│ ├── events
│ │ ├── OpenSourceIndia.jpeg
│ │ ├── WorkShop.jpeg
│ │ ├── gopherconAfrica.jpeg
│ │ └── hackathon.jpg
│ ├── jaeger-traces.png
│ ├── metrics-dashboard.png
│ ├── quick-start-logs.png
│ ├── quick-start-trace.png
│ └── reviewers
│ │ ├── aditya_joshi.webp
│ │ ├── ayush_mishra.webp
│ │ ├── christophe_colombier.webp
│ │ ├── manosh_malai.webp
│ │ ├── praveen_kumar.webp
│ │ ├── shridhar_vijay.webp
│ │ ├── vignesh.webp
│ │ └── vineet_dwivedi.webp
├── quick-start
│ ├── add-rest-handlers
│ │ └── page.md
│ ├── configuration
│ │ └── page.md
│ ├── connecting-mysql
│ │ └── page.md
│ ├── connecting-redis
│ │ └── page.md
│ ├── introduction
│ │ └── page.md
│ └── observability
│ │ └── page.md
├── references
│ ├── configs
│ │ └── page.md
│ ├── context
│ │ └── page.md
│ ├── gofr-cli
│ │ └── page.md
│ ├── gofrcli
│ │ └── page.md
│ └── testing
│ │ └── page.md
└── testimonials.json
├── examples
├── grpc
│ ├── grpc-streaming-client
│ │ ├── README.md
│ │ ├── client
│ │ │ ├── chat.pb.go
│ │ │ ├── chat.proto
│ │ │ ├── chat_grpc.pb.go
│ │ │ ├── chatservice_client.go
│ │ │ └── health_client.go
│ │ ├── configs
│ │ │ └── .env
│ │ └── main.go
│ ├── grpc-streaming-server
│ │ ├── README.md
│ │ ├── configs
│ │ │ └── .env
│ │ ├── main.go
│ │ ├── main_test.go
│ │ └── server
│ │ │ ├── chat.pb.go
│ │ │ ├── chat.proto
│ │ │ ├── chat_grpc.pb.go
│ │ │ ├── chatservice_gofr.go
│ │ │ ├── chatservice_server.go
│ │ │ ├── health_gofr.go
│ │ │ └── request_gofr.go
│ ├── grpc-unary-client
│ │ ├── README.md
│ │ ├── client
│ │ │ ├── health_client.go
│ │ │ ├── hello.pb.go
│ │ │ ├── hello.proto
│ │ │ ├── hello_client.go
│ │ │ └── hello_grpc.pb.go
│ │ ├── configs
│ │ │ └── .env
│ │ └── main.go
│ └── grpc-unary-server
│ │ ├── README.md
│ │ ├── configs
│ │ └── .env
│ │ ├── main.go
│ │ ├── main_test.go
│ │ └── server
│ │ ├── health_gofr.go
│ │ ├── hello.pb.go
│ │ ├── hello.proto
│ │ ├── hello_gofr.go
│ │ ├── hello_grpc.pb.go
│ │ ├── hello_server.go
│ │ ├── hello_server_test.go
│ │ └── request_gofr.go
├── http-server-using-redis
│ ├── Dockerfile
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── main.go
│ └── main_test.go
├── http-server
│ ├── Dockerfile
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── docker
│ │ ├── docker-compose.yaml
│ │ ├── prometheus
│ │ │ └── prometheus.yml
│ │ └── provisioning
│ │ │ ├── dashboards
│ │ │ ├── dashboards.yaml
│ │ │ └── gofr-dashboard
│ │ │ │ └── dashboards.json
│ │ │ └── datasources
│ │ │ └── datasource.yaml
│ ├── main.go
│ ├── main_test.go
│ └── static
│ │ └── openapi.json
├── sample-cmd
│ ├── README.md
│ ├── configs
│ │ └── .test.env
│ ├── main.go
│ └── main_test.go
├── using-add-filestore
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
├── using-add-rest-handlers
│ ├── Dockerfile
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── main.go
│ ├── main_test.go
│ └── migrations
│ │ ├── 1721816030_create_user_table.go
│ │ ├── 1721816030_create_user_table_test.go
│ │ ├── all.go
│ │ └── all_test.go
├── using-cron-jobs
│ ├── configs
│ │ └── .env
│ ├── main.go
│ └── main_test.go
├── using-custom-metrics
│ ├── Dockerfile
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── main.go
│ └── main_test.go
├── using-file-bind
│ ├── Dockerfile
│ ├── README.md
│ ├── configs
│ │ └── .env
│ ├── main.go
│ └── main_test.go
├── using-html-template
│ ├── main.go
│ ├── main_test.go
│ ├── static
│ │ ├── 404.html
│ │ ├── index.html
│ │ └── style.css
│ └── templates
│ │ └── todo.html
├── using-http-service
│ ├── Dockerfile
│ ├── configs
│ │ └── .env
│ ├── main.go
│ ├── main_test.go
│ └── readme.md
├── using-migrations
│ ├── Dockerfile
│ ├── configs
│ │ └── .env
│ ├── go.sum
│ ├── main.go
│ ├── main_test.go
│ ├── migrations
│ │ ├── 1722507126_create_employee_table.go
│ │ ├── 1722507126_create_employee_table_test.go
│ │ ├── 1722507180_redis_add_employee_name.go
│ │ ├── 1722507180_redis_add_employee_name_test.go
│ │ ├── all.go
│ │ └── all_test.go
│ └── readme.md
├── using-publisher
│ ├── Dockerfile
│ ├── configs
│ │ └── .env
│ ├── main.go
│ ├── main_test.go
│ ├── migrations
│ │ ├── 1721801313_create_topics.go
│ │ ├── all.go
│ │ └── all_test.go
│ └── readme.md
├── using-subscriber
│ ├── Dockerfile
│ ├── configs
│ │ └── .env
│ ├── main.go
│ ├── main_test.go
│ ├── migrations
│ │ ├── 1721800255_create_topics.go
│ │ ├── all.go
│ │ └── all_test.go
│ └── readme.md
└── using-web-socket
│ ├── configs
│ └── .env
│ ├── main.go
│ └── main_test.go
├── go.mod
├── go.sum
├── go.work
├── go.work.sum
├── pkg
└── gofr
│ ├── auth.go
│ ├── cmd.go
│ ├── cmd
│ ├── request.go
│ ├── request_test.go
│ ├── responder.go
│ ├── responder_test.go
│ └── terminal
│ │ ├── colors.go
│ │ ├── output.go
│ │ ├── output_test.go
│ │ ├── printers.go
│ │ ├── printers_test.go
│ │ ├── progress.go
│ │ ├── progress_test.go
│ │ ├── spinner.go
│ │ └── spinner_test.go
│ ├── cmd_test.go
│ ├── config
│ ├── config.go
│ ├── godotenv.go
│ ├── godotenv_test.go
│ ├── mock_config.go
│ └── mock_config_test.go
│ ├── constants.go
│ ├── container
│ ├── container.go
│ ├── container_test.go
│ ├── datasources.go
│ ├── health.go
│ ├── health_test.go
│ ├── metrics.go
│ ├── mock_container.go
│ ├── mock_datasources.go
│ ├── mock_logger.go
│ ├── mock_metrics.go
│ ├── mockcontainer_test.go
│ └── sql_mock.go
│ ├── context.go
│ ├── context_test.go
│ ├── cron.go
│ ├── cron_test.go
│ ├── crud_handlers.go
│ ├── crud_handlers_test.go
│ ├── crud_helpers.go
│ ├── datasource
│ ├── README.md
│ ├── arangodb
│ │ ├── arango.go
│ │ ├── arango_db.go
│ │ ├── arango_db_test.go
│ │ ├── arango_document.go
│ │ ├── arango_document_test.go
│ │ ├── arango_graph.go
│ │ ├── arango_graph_test.go
│ │ ├── arango_helper.go
│ │ ├── arango_helper_test.go
│ │ ├── arango_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interface.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── mock_collection.go
│ │ ├── mock_database.go
│ │ ├── mock_graph.go
│ │ ├── mock_interfaces.go
│ │ ├── mock_logger.go
│ │ ├── mock_metrics.go
│ │ └── mock_user.go
│ ├── cassandra
│ │ ├── cassandra.go
│ │ ├── cassandra_batch.go
│ │ ├── cassandra_batch_test.go
│ │ ├── cassandra_test.go
│ │ ├── errors.go
│ │ ├── errors_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interfaces.go
│ │ ├── internal.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── mock_interfaces.go
│ │ ├── mock_logger.go
│ │ └── mock_metrics.go
│ ├── clickhouse
│ │ ├── clickhouse.go
│ │ ├── clickhouse_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interface.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── mock_interface.go
│ │ ├── mock_logger.go
│ │ └── mock_metrics.go
│ ├── datasource.go
│ ├── dgraph
│ │ ├── dgraph.go
│ │ ├── dgraph_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interfaces.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── mock_interfaces.go
│ │ ├── mock_logger.go
│ │ └── mock_metrics.go
│ ├── elasticsearch
│ │ ├── documents.go
│ │ ├── elasticsearch.go
│ │ ├── elasticsearch_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── logger.go
│ │ ├── metrics.go
│ │ ├── mock_logger.go
│ │ └── mock_metrics.go
│ ├── errors.go
│ ├── errors_test.go
│ ├── file
│ │ ├── file.go
│ │ ├── fs.go
│ │ ├── fs_test.go
│ │ ├── ftp
│ │ │ ├── file.go
│ │ │ ├── file_test.go
│ │ │ ├── fs.go
│ │ │ ├── fs_dir.go
│ │ │ ├── fs_test.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── interface.go
│ │ │ ├── logger.go
│ │ │ ├── logger_test.go
│ │ │ └── mock_interface.go
│ │ ├── interface.go
│ │ ├── mock_interface.go
│ │ ├── s3
│ │ │ ├── file.go
│ │ │ ├── file_parse.go
│ │ │ ├── fs.go
│ │ │ ├── fs_dir.go
│ │ │ ├── fs_test.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── interface.go
│ │ │ ├── logger.go
│ │ │ ├── logger_test.go
│ │ │ └── mock_interface.go
│ │ └── sftp
│ │ │ ├── file.go
│ │ │ ├── fs.go
│ │ │ ├── fs_test.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── interface.go
│ │ │ ├── logger.go
│ │ │ ├── logger_test.go
│ │ │ └── mock_interface.go
│ ├── health.go
│ ├── interface.go
│ ├── kv-store
│ │ ├── badger
│ │ │ ├── badger.go
│ │ │ ├── badger_test.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── logger.go
│ │ │ ├── logger_test.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_logger.go
│ │ │ └── mock_metrics.go
│ │ └── nats
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── interface.go
│ │ │ ├── logger.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_interface.go
│ │ │ ├── mock_logger.go
│ │ │ ├── mock_metrics.go
│ │ │ ├── nats.go
│ │ │ └── nats_test.go
│ ├── logger.go
│ ├── mongo
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── mock_logger.go
│ │ ├── mock_metrics.go
│ │ ├── mongo.go
│ │ └── mongo_test.go
│ ├── opentsdb
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interface.go
│ │ ├── mock_interface.go
│ │ ├── observability.go
│ │ ├── opentsdb.go
│ │ ├── opentsdb_test.go
│ │ ├── preprocess.go
│ │ └── response.go
│ ├── pubsub
│ │ ├── eventhub
│ │ │ ├── eventhub.go
│ │ │ ├── eventhub_test.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── logger.go
│ │ │ ├── logger_test.go
│ │ │ ├── message.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_logger.go
│ │ │ └── mock_metrics.go
│ │ ├── google
│ │ │ ├── google.go
│ │ │ ├── google_test.go
│ │ │ ├── health.go
│ │ │ ├── interfaces.go
│ │ │ ├── message.go
│ │ │ ├── message_test.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_interfaces.go
│ │ │ └── mock_metrics.go
│ │ ├── interface.go
│ │ ├── kafka
│ │ │ ├── conn.go
│ │ │ ├── errors.go
│ │ │ ├── health.go
│ │ │ ├── health_test.go
│ │ │ ├── helper.go
│ │ │ ├── interfaces.go
│ │ │ ├── kafka.go
│ │ │ ├── kafka_sasl.go
│ │ │ ├── kafka_sasl_test.go
│ │ │ ├── kafka_test.go
│ │ │ ├── message.go
│ │ │ ├── message_test.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_interfaces.go
│ │ │ ├── mock_metrics.go
│ │ │ ├── tls.go
│ │ │ └── tls_test.go
│ │ ├── log.go
│ │ ├── message.go
│ │ ├── message_test.go
│ │ ├── mqtt
│ │ │ ├── default_client.go
│ │ │ ├── interface.go
│ │ │ ├── message.go
│ │ │ ├── message_test.go
│ │ │ ├── mock_client.go
│ │ │ ├── mock_interfaces.go
│ │ │ ├── mock_token.go
│ │ │ ├── mqtt.go
│ │ │ └── mqtt_test.go
│ │ └── nats
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── committer.go
│ │ │ ├── committer_test.go
│ │ │ ├── config.go
│ │ │ ├── connection_manager.go
│ │ │ ├── connection_manager_test.go
│ │ │ ├── connectors.go
│ │ │ ├── connectors_test.go
│ │ │ ├── errors.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── health.go
│ │ │ ├── health_test.go
│ │ │ ├── interfaces.go
│ │ │ ├── message.go
│ │ │ ├── message_test.go
│ │ │ ├── metrics.go
│ │ │ ├── mock_client.go
│ │ │ ├── mock_jetstream.go
│ │ │ ├── mock_metrics.go
│ │ │ ├── mock_tracer.go
│ │ │ ├── pubsub_wrapper.go
│ │ │ ├── stream_manager.go
│ │ │ ├── stream_manager_test.go
│ │ │ ├── subscription_manager.go
│ │ │ ├── subscription_manager_test.go
│ │ │ └── wrapper_test.go
│ ├── redis
│ │ ├── health.go
│ │ ├── health_test.go
│ │ ├── hook.go
│ │ ├── hook_test.go
│ │ ├── metrics.go
│ │ ├── metrics_interface.go
│ │ ├── redis.go
│ │ └── redis_test.go
│ ├── scylladb
│ │ ├── errors.go
│ │ ├── errors_test.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interface.go
│ │ ├── internal.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── mock_interface.go
│ │ ├── mock_logger.go
│ │ ├── scylladb.go
│ │ └── scylladb_test.go
│ ├── solr
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── logger.go
│ │ ├── metrics.go
│ │ ├── mock_logger.go
│ │ ├── mock_metrics.go
│ │ ├── solr.go
│ │ └── solr_test.go
│ ├── sql
│ │ ├── bind.go
│ │ ├── bind_test.go
│ │ ├── db.go
│ │ ├── db_test.go
│ │ ├── health.go
│ │ ├── health_test.go
│ │ ├── metrics.go
│ │ ├── mock_metrics.go
│ │ ├── query_builder.go
│ │ ├── query_builder_test.go
│ │ ├── sql.go
│ │ ├── sql_mock.go
│ │ ├── sql_test.go
│ │ ├── supabase.go
│ │ └── supabase_test.go
│ └── surrealdb
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── interface.go
│ │ ├── mock_interface.go
│ │ ├── surrealdb.go
│ │ ├── surrealdb_test.go
│ │ └── utils.go
│ ├── default.go
│ ├── exporter.go
│ ├── exporter_test.go
│ ├── external_db.go
│ ├── external_db_test.go
│ ├── factory.go
│ ├── file
│ ├── file.go
│ ├── file_test.go
│ ├── zip.go
│ └── zip_test.go
│ ├── gofr.go
│ ├── gofr_test.go
│ ├── grpc.go
│ ├── grpc
│ ├── log.go
│ └── log_test.go
│ ├── grpc_test.go
│ ├── handler.go
│ ├── handler_test.go
│ ├── http
│ ├── errors.go
│ ├── errors_test.go
│ ├── form_data_binder.go
│ ├── metrics.go
│ ├── middleware
│ │ ├── apikey_auth.go
│ │ ├── apikey_auth_test.go
│ │ ├── basic_auth.go
│ │ ├── basic_auth_test.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── cors.go
│ │ ├── cors_test.go
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ ├── metrics.go
│ │ ├── metrics_test.go
│ │ ├── oauth.go
│ │ ├── oauth_test.go
│ │ ├── tracer.go
│ │ ├── tracer_test.go
│ │ ├── validate.go
│ │ ├── validate_test.go
│ │ ├── web_socket.go
│ │ └── web_socket_test.go
│ ├── multipart_file_bind.go
│ ├── multipart_file_bind_test.go
│ ├── request.go
│ ├── request_test.go
│ ├── responder.go
│ ├── responder_test.go
│ ├── response
│ │ ├── file.go
│ │ ├── raw.go
│ │ ├── redirect.go
│ │ ├── response.go
│ │ └── template.go
│ ├── router.go
│ └── router_test.go
│ ├── http_server.go
│ ├── http_server_test.go
│ ├── logging
│ ├── ctx_logger.go
│ ├── ctx_logger_test.go
│ ├── level.go
│ ├── level_test.go
│ ├── logger.go
│ ├── logger_test.go
│ ├── mock_logger.go
│ ├── mock_logger_test.go
│ └── remotelogger
│ │ ├── dynamic_level_logger.go
│ │ └── dynamic_level_logger_test.go
│ ├── metrics
│ ├── errors.go
│ ├── exporters
│ │ └── exporter.go
│ ├── handler.go
│ ├── handler_test.go
│ ├── register.go
│ ├── register_test.go
│ └── store.go
│ ├── metrics_server.go
│ ├── migration
│ ├── arango.go
│ ├── arango_test.go
│ ├── cassandra.go
│ ├── cassandra_test.go
│ ├── clickhouse.go
│ ├── clickhouse_test.go
│ ├── datasource.go
│ ├── datasource_test.go
│ ├── dgraph.go
│ ├── dgraph_test.go
│ ├── interface.go
│ ├── logger.go
│ ├── migration.go
│ ├── migration_test.go
│ ├── mock_interface.go
│ ├── mongo.go
│ ├── mongo_test.go
│ ├── redis.go
│ ├── redis_test.go
│ ├── sql.go
│ ├── sql_test.go
│ ├── surreal_db.go
│ └── surreal_db_test.go
│ ├── otel.go
│ ├── request.go
│ ├── responder.go
│ ├── rest.go
│ ├── run.go
│ ├── service
│ ├── apikey_auth.go
│ ├── apikey_auth_test.go
│ ├── basic_auth.go
│ ├── basic_auth_test.go
│ ├── circuit_breaker.go
│ ├── circuit_breaker_test.go
│ ├── custom_header.go
│ ├── custom_header_test.go
│ ├── health.go
│ ├── health_config.go
│ ├── health_test.go
│ ├── logger.go
│ ├── logger_test.go
│ ├── metrics.go
│ ├── mock_http_service.go
│ ├── mock_metrics.go
│ ├── new.go
│ ├── new_test.go
│ ├── oauth.go
│ ├── oauth_test.go
│ ├── options.go
│ ├── response.go
│ ├── response_test.go
│ ├── retry.go
│ └── retry_test.go
│ ├── shutdown.go
│ ├── shutdown_test.go
│ ├── static
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── files.go
│ ├── index.css
│ ├── index.html
│ ├── oauth2-redirect.html
│ ├── swagger-ui-bundle.js
│ ├── swagger-ui-standalone-preset.js
│ ├── swagger-ui.css
│ └── swagger-ui.js
│ ├── subscriber.go
│ ├── subscriber_test.go
│ ├── swagger.go
│ ├── swagger_test.go
│ ├── telemetry.go
│ ├── testutil
│ ├── error.go
│ ├── error_test.go
│ ├── os.go
│ ├── os_test.go
│ ├── port.go
│ ├── port_test.go
│ └── test.zip
│ ├── version
│ └── version.go
│ ├── websocket.go
│ ├── websocket
│ ├── interfaces.go
│ ├── mock_interfaces.go
│ ├── options.go
│ ├── websocket.go
│ └── websocket_test.go
│ └── websocket_test.go
└── typos.toml
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | checks:
4 | argument-count:
5 | enabled: true
6 | config:
7 | threshold: 6
8 | complex-logic:
9 | enabled: true
10 | config:
11 | threshold: 10
12 | file-lines:
13 | enabled: true
14 | config:
15 | threshold: 300
16 | method-complexity:
17 | enabled: true
18 | config:
19 | threshold: 12
20 | method-count:
21 | enabled: true
22 | config:
23 | threshold: 25
24 | method-lines:
25 | enabled: true
26 | config:
27 | threshold: 100
28 | nested-control-flow:
29 | enabled: true
30 | config:
31 | threshold: 4
32 | return-statements:
33 | enabled: true
34 | config:
35 | threshold: 8
36 | similar-code:
37 | enabled: false
38 | identical-code:
39 | enabled: false
40 |
41 | # plugins:
42 | # eslint:
43 | # enabled: true
44 | # channel: "eslint-6"
45 |
46 | exclude_patterns:
47 | - "**/mock_*"
48 | - "**/*_test.go"
49 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior, if applicable:
15 |
16 | 1. The code is
17 |
18 | ```go
19 |
20 | ```
21 |
22 | 2. The error is
23 |
24 | ```
25 |
26 | ```
27 |
28 | **Expected behavior**
29 | A clear and concise description of what you expected to happen.
30 |
31 | **Screenshots**
32 | If applicable, add screenshots to help explain your problem.
33 |
34 | **Environments (please complete the following information):**
35 | - OS: [e.g. Linux]
36 | - gofr version [e.g. v1.5.0]
37 | - go version [e.g. 1.21]
38 |
39 | **More description**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question on using gofr
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/banner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/.github/banner.gif
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: "gomod"
5 | open-pull-requests-limit: 10 # avoid spam, if no one reacts
6 | directories:
7 | - "/"
8 | - "/examples/*"
9 | - "/pkg/gofr/datasource/*"
10 | - "/pkg/gofr/datasource/file/*"
11 | - "/pkg/gofr/datasource/kv-store/*"
12 | - "/pkg/gofr/datasource/pubsub/*"
13 | schedule:
14 | interval: "weekly"
15 |
16 | - package-ecosystem: "github-actions"
17 | open-pull-requests-limit: 10 # avoid spam, if no one reacts
18 | directory: "/"
19 | schedule:
20 | # Check for updates to GitHub Actions every week
21 | interval: "weekly"
22 | groups:
23 | actions:
24 | update-types:
25 | - "minor"
26 | - "patch"
27 |
28 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Pull Request Template
2 |
3 |
4 | **Description:**
5 |
6 | - Provide a concise explanation of the changes made.
7 | - Mention the issue number(s) this PR addresses (if applicable).
8 | - Highlight the motivation behind the changes and the expected benefits.
9 |
10 |
11 | **Breaking Changes (if applicable):**
12 |
13 | - List any breaking changes introduced by this PR.
14 | - Explain the rationale behind these changes and how they will impact users.
15 |
16 | **Additional Information:**
17 |
18 | - Mention any relevant dependencies or external libraries used.
19 | - Include screenshots or code snippets (if necessary) to clarify the changes.
20 |
21 | **Checklist:**
22 |
23 | - [ ] I have formatted my code using `goimport` and `golangci-lint`.
24 | - [ ] All new code is covered by unit tests.
25 | - [ ] This PR does not decrease the overall code coverage.
26 | - [ ] I have reviewed the code comments and documentation for clarity.
27 |
28 | **Thank you for your contribution!**
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/typos.yml:
--------------------------------------------------------------------------------
1 | name: Typos Check
2 | on:
3 | push:
4 | pull_request:
5 | jobs:
6 | typos:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | contents: read
10 | steps:
11 | - name: Checkout Code
12 | uses: actions/checkout@v4
13 | - name: typos-action
14 | uses: crate-ci/typos@v1.33.1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 |
12 | # Output of the go coverage tool, specifically when used with LiteIDE
13 | *.out
14 | coverage.txt
15 |
16 | # Dependency directories (remove the comment below to include it)
17 | vendor/
18 |
19 | .idea
20 | *.impl
21 |
22 | # IDE Cache
23 | .vscode
24 | .DS_Store
25 |
--------------------------------------------------------------------------------
/.ls-lint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | ls:
3 | .go: snake_case
4 | .pb.go: snake_case
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir -p /go/src/gofr.dev
4 | WORKDIR /go/src/gofr.dev
5 | COPY . .
6 |
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a examples/http-server/main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /go/src/gofr.dev/main /main
12 | EXPOSE 8000
13 | CMD ["/main"]
14 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Following versions are being supported for security updates.
6 |
7 | | Version | Supported |
8 | | ------- | ------------------ |
9 | | 1.0.x | :white_check_mark: |
10 |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | To report a vulnerability, please file an issue on this repo and add "security" label. Security related issues will be prioritized over others. We strive to triage the issue within a single working day.
15 |
--------------------------------------------------------------------------------
/docs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/gofr-dev/website:latest AS builder
2 |
3 | WORKDIR /app
4 |
5 | COPY docs/quick-start /app/src/app/docs/quick-start
6 | COPY docs/public/ /app/public
7 | COPY docs/advanced-guide /app/src/app/docs/advanced-guide
8 | COPY docs/datasources /app/src/app/docs/datasources
9 | COPY docs/references /app/src/app/docs/references
10 | COPY docs/page.md /app/src/app/docs
11 | COPY docs/navigation.js /app/src/lib
12 | COPY docs/events.json /app/src/app/events
13 | COPY docs/testimonials.json /app/utils
14 |
15 | ENV NODE_ENV production
16 |
17 | RUN npm install
18 | RUN npm run build
19 |
20 | # Stage 2: Serve with Static Server
21 | FROM zopdev/static-server:v0.0.5
22 |
23 | # Copy static files from the builder stage
24 | COPY --from=builder /app/out static
25 |
26 | # Expose the port server is running on
27 | EXPOSE 8000
28 |
29 | # Start go server
30 | CMD ["/main"]
--------------------------------------------------------------------------------
/docs/advanced-guide/custom-spans-in-tracing/page.md:
--------------------------------------------------------------------------------
1 | # Custom Spans In Tracing
2 |
3 | GoFr's built-in tracing provides valuable insights into application's behavior. However, sometimes we might need
4 | even more granular details about specific operations within your application. This is where `custom spans` can be used.
5 |
6 | ## How it helps?
7 | By adding custom spans in traces to our requests, we can:
8 |
9 | - **Gain granular insights:** Custom spans allows us to track specific operations or functions within your application,
10 | providing detailed performance data.
11 | - **Identify bottlenecks:** Analyzing custom spans helps to pinpoint areas of your code that may be causing
12 | performance bottlenecks or inefficiencies.
13 | - **Improve debugging:** Custom spans enhance the ability to debug issues by providing visibility into the execution
14 | flow of an application.
15 |
16 | ## Usage
17 |
18 | To add a custom trace to a request, GoFr context provides `Trace()` method, which takes the name of the span as an argument
19 | and returns a trace.Span.
20 |
21 | ```go
22 | func MyHandler(c context.Context) error {
23 | span := c.Trace("my-custom-span")
24 | defer span.Close()
25 |
26 | // Do some work here
27 | return nil
28 | }
29 | ```
30 |
31 | In this example, **my-custom-span** is the name of the custom span that is added to the request.
32 | The defer statement ensures that the span is closed even if an error occurs to ensure that the trace is properly recorded.
33 |
34 | > ##### Check out the example of creating a custom span in GoFr: [Visit GitHub](https://github.com/gofr-dev/gofr/blob/main/examples/http-server/main.go#L58)
35 |
--------------------------------------------------------------------------------
/docs/advanced-guide/dealing-with-sql/page.md:
--------------------------------------------------------------------------------
1 | # Dealing with SQL
2 |
3 | GoFr simplifies the process of connecting to SQL databases where one needs to add respective configs in .env,
4 | which allows connecting to different SQL dialects(MySQL, PostgreSQL, SQLite) without going into complexity of configuring connections.
5 |
6 | With GoFr, connecting to different SQL databases is as straightforward as setting the DB_DIALECT environment variable to the respective dialect.
7 |
8 | ## Usage for PostgreSQL and MySQL
9 | To connect with PostgreSQL, set `DB_DIALECT` to `postgres`. Similarly, To connect with MySQL, simply set `DB_DIALECT` to `mysql`.
10 |
11 | ```dotenv
12 | DB_HOST=localhost
13 | DB_USER=root
14 | DB_PASSWORD=root123
15 | DB_NAME=test_db
16 | DB_PORT=3306
17 |
18 | DB_DIALECT=postgres
19 | ```
20 |
21 | ## Usage for SQLite
22 | To connect with SQLite, set `DB_DIALECT` to `sqlite` and `DB_NAME` to the name of your DB File. If the DB file already exists then it will be used otherwise a new one will be created.
23 |
24 | ```dotenv
25 | DB_NAME=test.db
26 |
27 | DB_DIALECT=sqlite
28 | ```
29 |
30 | ## Setting Max open and Idle Connections
31 |
32 | To set max open and idle connection for any MySQL, PostgreSQL, SQLite.
33 | Add the following configs in `.env` file.
34 |
35 | ```dotenv
36 | DB_MAX_IDLE_CONNECTION=5 // Default 2
37 | DB_MAX_OPEN_CONNECTION=5 // Default unlimited
38 | ```
39 | > ##### Check out the example on how to add configuration for SQL in GoFr: [Visit GitHub](https://github.com/gofr-dev/gofr/blob/main/examples/http-server/configs/.env)
40 |
--------------------------------------------------------------------------------
/docs/advanced-guide/swagger-documentation/page.md:
--------------------------------------------------------------------------------
1 | # Rendering OpenAPI Documentation in GoFr
2 |
3 | GoFr supports automatic rendering of OpenAPI (also known as Swagger) documentation. This feature allows you to
4 | easily provide interactive API documentation for your users.
5 |
6 | ## What is OpenAPI/Swagger Documentation?
7 |
8 | OpenAPI, also known as Swagger, is a specification for building APIs. An OpenAPI file allows you to describe your entire API, including:
9 |
10 | - Available endpoints (/users) and operations on each endpoint (GET /users, DELETE /users/{id})
11 | - Operation parameters, input, and output for each operation
12 | - Authentication methods
13 | - Contact information, license, terms of use, and other information.
14 |
15 | API specifications can be written in YAML or JSON. The format is easy to learn and readable to both humans and machines.
16 | The complete OpenAPI Specification can be found on the official [Swagger website](https://swagger.io/).
17 |
18 | ## Enabling GoFr to render your openapi.json file
19 |
20 | To allow GoFr to render your OpenAPI documentation, simply place your `openapi.json` file inside the `static` directory of your project.
21 | GoFr will automatically render the Swagger documentation at the `/.well-known/swagger` endpoint.
22 |
23 | Here are the steps:
24 |
25 | - Create an `openapi.json` file that describes your API according to the OpenAPI specification.
26 | - Place the `openapi.json` file inside the `static` directory in your project.
27 | - Start your GoFr server.
28 | - Navigate to `/.well-known/swagger` on your server’s URL.
29 |
30 | You should now see a beautifully rendered, interactive documentation for your API that users can use to understand and interact with your API.
31 |
--------------------------------------------------------------------------------
/docs/page.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | GoFr is Opinionated Web Framework written in Go (Golang). It helps in building robust and scalable applications. This framework is designed to offer a user-friendly and familiar abstraction for all the developers. We prioritize simplicity over complexity.
4 |
5 | In this section we will walk through what GoFr is, what problems it solves, and how it can help in building your project.
6 |
7 | {% quick-links %}
8 |
9 | {% quick-link title="Quick Start" icon="installation" href="/docs/quick-start/introduction" description="Step-by-step guides to setting up your system and installing the library." /%}
10 |
11 | {% quick-link title="Examples" icon="plugins" href="https://github.com/gofr-dev/gofr/tree/main/examples" description="Our guides break down how to perform common tasks in GoFr." /%}
12 |
13 | {% /quick-links %}
14 |
15 | ## Key Features
16 |
17 | - Logging
18 | - Support for various response types such as JSON, FILE.
19 | - Health check and Readiness monitoring for checking continuous service availability.
20 | - Metrics exposure for monitoring and analysis using Prometheus.
21 | - Tracing capability to track user request progress with traceable spans.
22 | - Level-based logging support for effective debugging and monitoring.
23 |
24 | ## Principles
25 |
26 | - Promote simple and clean code.
27 | - Favor compile-time checked code over dynamic code.
28 | - Create a solid foundation for the integration of application modules.
29 | - Encourage a more functional way of programming.
30 | - Avoid code duplication.
31 | - Log and store data for analysis purposes.
32 |
--------------------------------------------------------------------------------
/docs/public/events/OpenSourceIndia.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/events/OpenSourceIndia.jpeg
--------------------------------------------------------------------------------
/docs/public/events/WorkShop.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/events/WorkShop.jpeg
--------------------------------------------------------------------------------
/docs/public/events/gopherconAfrica.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/events/gopherconAfrica.jpeg
--------------------------------------------------------------------------------
/docs/public/events/hackathon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/events/hackathon.jpg
--------------------------------------------------------------------------------
/docs/public/jaeger-traces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/jaeger-traces.png
--------------------------------------------------------------------------------
/docs/public/metrics-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/metrics-dashboard.png
--------------------------------------------------------------------------------
/docs/public/quick-start-logs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/quick-start-logs.png
--------------------------------------------------------------------------------
/docs/public/quick-start-trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/quick-start-trace.png
--------------------------------------------------------------------------------
/docs/public/reviewers/aditya_joshi.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/aditya_joshi.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/ayush_mishra.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/ayush_mishra.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/christophe_colombier.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/christophe_colombier.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/manosh_malai.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/manosh_malai.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/praveen_kumar.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/praveen_kumar.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/shridhar_vijay.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/shridhar_vijay.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/vignesh.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/vignesh.webp
--------------------------------------------------------------------------------
/docs/public/reviewers/vineet_dwivedi.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/docs/public/reviewers/vineet_dwivedi.webp
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-client/README.md:
--------------------------------------------------------------------------------
1 | # gRPC Streaming Client Example
2 |
3 | This GoFr example demonstrates a simple gRPC streaming server that communicates with another gRPC service hosted on a different machine. It serves as a client for another gRPC example included in this examples folder.
4 | Refer to the documentation to setup
5 |
6 | ### Steps to Run the Example
7 |
8 | 1. First, start the corresponding `grpc-streaming-server` example, which is located at the relative path: `../grpc-streaming-server`.
9 | Use the following command to start it:
10 | ```console
11 | go run main.go
12 | ```
13 |
14 | 2. Once the `grpc-streaming-server` is running, start this server using a similar command:
15 | ```console
16 | go run main.go
17 | ```
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-client/client/chat.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = "gofr.dev/examples/grpc/grpc-streaming-client/client";
3 |
4 | message Request {
5 | string message = 1;
6 | }
7 |
8 | message Response {
9 | string message = 1;
10 | }
11 |
12 | service ChatService {
13 | rpc ServerStream(Request) returns (stream Response);
14 | rpc ClientStream(stream Request) returns (Response);
15 | rpc BiDiStream(stream Request) returns (stream Response);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-client/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=grpc-client-example
2 |
3 | METRICS_PORT=2020
4 | HTTP_PORT=8080
5 |
6 | GRPC_SERVER_HOST=localhost:9000
7 |
8 | LOG_LEVEL=DEBUG
9 | TRACE_EXPORTER=gofr
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-server/README.md:
--------------------------------------------------------------------------------
1 | # GRPC Server Example
2 |
3 | This GoFr example showcases a basic gRPC streaming server implementation. For detailed instructions on setting up gRPC handlers with GoFr’s context support—enabling built-in tracing and database integration within your gRPC handlers—please refer to our [official documentation](https://gofr.dev/docs/advanced-guide/grpc).
4 |
5 | ### To run the example use the command below:
6 | ```console
7 | go run main.go
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-server/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=grpc-server-example
2 |
3 | GRPC_PORT=9000
4 | HTTP_PORT=8081
5 |
6 | LOG_LEVEL=DEBUG
7 | TRACE_EXPORTER=gofr
8 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/examples/grpc/grpc-streaming-server/server"
5 | "gofr.dev/pkg/gofr"
6 | )
7 |
8 | func main() {
9 | app := gofr.New()
10 |
11 | server.RegisterChatServiceServerWithGofr(app, server.NewChatServiceGoFrServer())
12 |
13 | app.Run()
14 | }
15 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-server/server/chat.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = "gofr.dev/examples/grpc/grpc-streaming-server/server";
3 |
4 | message Request {
5 | string message = 1;
6 | }
7 |
8 | message Response {
9 | string message = 1;
10 | }
11 |
12 | service ChatService {
13 | rpc ServerStream(Request) returns (stream Response);
14 | rpc ClientStream(stream Request) returns (Response);
15 | rpc BiDiStream(stream Request) returns (stream Response);
16 | }
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-streaming-server/server/request_gofr.go:
--------------------------------------------------------------------------------
1 | // Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
2 | // versions:
3 | // gofr-cli v0.7.0
4 | // gofr.dev v1.39.0
5 | // source: chat.proto
6 |
7 | package server
8 |
9 | import (
10 | "context"
11 | "fmt"
12 | "reflect"
13 | )
14 |
15 | // Request Wrappers
16 | type RequestWrapper struct {
17 | ctx context.Context
18 | *Request
19 | }
20 |
21 | func (h *RequestWrapper) Context() context.Context {
22 | return h.ctx
23 | }
24 |
25 | func (h *RequestWrapper) Param(s string) string {
26 | return ""
27 | }
28 |
29 | func (h *RequestWrapper) PathParam(s string) string {
30 | return ""
31 | }
32 |
33 | func (h *RequestWrapper) Bind(p interface{}) error {
34 | ptr := reflect.ValueOf(p)
35 | if ptr.Kind() != reflect.Ptr {
36 | return fmt.Errorf("expected a pointer, got %T", p)
37 | }
38 |
39 | hValue := reflect.ValueOf(h.Request).Elem()
40 | ptrValue := ptr.Elem()
41 |
42 | for i := 0; i < hValue.NumField(); i++ {
43 | field := hValue.Type().Field(i)
44 | if field.Name == "state" || field.Name == "sizeCache" || field.Name == "unknownFields" {
45 | continue
46 | }
47 |
48 | if field.IsExported() {
49 | ptrValue.Field(i).Set(hValue.Field(i))
50 | }
51 | }
52 |
53 | return nil
54 | }
55 |
56 | func (h *RequestWrapper) HostName() string {
57 | return ""
58 | }
59 |
60 | func (h *RequestWrapper) Params(s string) []string {
61 | return nil
62 | }
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-client/README.md:
--------------------------------------------------------------------------------
1 | # gRPC Unary Client Example
2 |
3 | This GoFr example demonstrates a simple gRPC unary client that communicates with another gRPC service hosted on a different machine. It serves as a client for another gRPC example included in this examples folder.
4 | Refer to the documentation to setup
5 |
6 | ### Steps to Run the Example
7 |
8 | 1. First, start the corresponding `grpc-unary-server` example, which is located at the relative path: `../grpc-unary-server`.
9 | Use the following command to start it:
10 | ```console
11 | go run main.go
12 | ```
13 |
14 | 2. Once the `grpc-unary-server` is running, start this server using a similar command:
15 | ```console
16 | go run main.go
17 | ```
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-client/client/hello.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = "gofr.dev/examples/grpc/grpc-unary-client/client";
3 |
4 | message HelloRequest {
5 | string name = 1;
6 | }
7 |
8 | message HelloResponse {
9 | string message = 1;
10 | }
11 |
12 | service Hello {
13 | rpc SayHello(HelloRequest) returns (HelloResponse) {}
14 | }
15 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-client/client/hello_client.go:
--------------------------------------------------------------------------------
1 | // Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
2 | // versions:
3 | // gofr-cli v0.7.0
4 | // gofr.dev v1.39.0
5 | // source: hello.proto
6 |
7 | package client
8 |
9 | import (
10 | "gofr.dev/pkg/gofr"
11 | "gofr.dev/pkg/gofr/metrics"
12 | "google.golang.org/grpc"
13 | )
14 |
15 | type HelloGoFrClient interface {
16 | SayHello(ctx *gofr.Context, req *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
17 | HealthClient
18 | }
19 |
20 | type HelloClientWrapper struct {
21 | client HelloClient
22 | HealthClient
23 | }
24 |
25 | func NewHelloGoFrClient(host string, metrics metrics.Manager, dialOptions ...grpc.DialOption) (HelloGoFrClient, error) {
26 | conn, err := createGRPCConn(host, "Hello", dialOptions...)
27 | if err != nil {
28 | return &HelloClientWrapper{
29 | client: nil,
30 | HealthClient: &HealthClientWrapper{client: nil},
31 | }, err
32 | }
33 |
34 | metricsOnce.Do(func() {
35 | metrics.NewHistogram("app_gRPC-Client_stats", "Response time of gRPC client in milliseconds.", gRPCBuckets...)
36 | })
37 |
38 | res := NewHelloClient(conn)
39 | healthClient := NewHealthClient(conn)
40 |
41 | return &HelloClientWrapper{
42 | client: res,
43 | HealthClient: healthClient,
44 | }, nil
45 | }
46 |
47 |
48 | func (h *HelloClientWrapper) SayHello(ctx *gofr.Context, req *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
49 | result, err := invokeRPC(ctx, "/Hello/SayHello", func() (interface{}, error) {
50 | return h.client.SayHello(ctx.Context, req, opts...)
51 | }, "app_gRPC-Client_stats")
52 |
53 | if err != nil {
54 | return nil, err
55 | }
56 | return result.(*HelloResponse), nil
57 | }
58 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-client/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=grpc-client-example
2 |
3 | METRICS_PORT=2020
4 | HTTP_PORT=8080
5 |
6 | GRPC_SERVER_HOST=localhost:9000
7 |
8 | LOG_LEVEL=DEBUG
9 | TRACE_EXPORTER=gofr
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/examples/grpc/grpc-unary-client/client"
5 | "gofr.dev/pkg/gofr"
6 | )
7 |
8 | func main() {
9 | app := gofr.New()
10 |
11 | // Create a gRPC client for the Hello service
12 | helloGRPCClient, err := client.NewHelloGoFrClient(app.Config.Get("GRPC_SERVER_HOST"), app.Metrics())
13 | if err != nil {
14 | app.Logger().Errorf("Failed to create Hello gRPC client: %v", err)
15 | return
16 | }
17 |
18 | greet := NewGreetHandler(helloGRPCClient)
19 |
20 | app.GET("/hello", greet.Hello)
21 |
22 | app.Run()
23 | }
24 |
25 | type GreetHandler struct {
26 | helloGRPCClient client.HelloGoFrClient
27 | }
28 |
29 | func NewGreetHandler(helloClient client.HelloGoFrClient) *GreetHandler {
30 | return &GreetHandler{
31 | helloGRPCClient: helloClient,
32 | }
33 | }
34 |
35 | func (g GreetHandler) Hello(ctx *gofr.Context) (any, error) {
36 | userName := ctx.Param("name")
37 |
38 | if userName == "" {
39 | ctx.Log("Name parameter is empty, defaulting to 'World'")
40 | userName = "World"
41 | }
42 |
43 | // HealthCheck to SayHello Service.
44 | // res, err := g.helloGRPCClient.Check(ctx, &grpc_health_v1.HealthCheckRequest{Service: "Hello"})
45 | // if err != nil {
46 | // return nil, err
47 | // } else if res.Status == grpc_health_v1.HealthCheckResponse_NOT_SERVING {
48 | // ctx.Error("Hello Service is down")
49 | // return nil, fmt.Errorf("Hello Service is down")
50 | // }
51 |
52 | // Make a gRPC call to the Hello service
53 | helloResponse, err := g.helloGRPCClient.SayHello(ctx, &client.HelloRequest{Name: userName})
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | return helloResponse, nil
59 | }
60 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/README.md:
--------------------------------------------------------------------------------
1 | # GRPC Server Example
2 |
3 | This GoFr example showcases a basic gRPC unary server implementation. For detailed instructions on setting up gRPC handlers with GoFr’s context support—enabling built-in tracing and database integration within your gRPC handlers—please refer to our [official documentation](https://gofr.dev/docs/advanced-guide/grpc).
4 |
5 | ### To run the example use the command below:
6 | ```console
7 | go run main.go
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=grpc-server-example
2 |
3 | GRPC_PORT=9000
4 | HTTP_PORT=8081
5 |
6 | LOG_LEVEL=DEBUG
7 | TRACE_EXPORTER=gofr
8 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/examples/grpc/grpc-unary-server/server"
5 | "gofr.dev/pkg/gofr"
6 | )
7 |
8 | func main() {
9 | app := gofr.New()
10 |
11 | server.RegisterHelloServerWithGofr(app, server.NewHelloGoFrServer())
12 |
13 | app.Run()
14 | }
15 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/server/hello.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = "gofr.dev/examples/grpc/grpc-server/server";
3 |
4 | message HelloRequest {
5 | string name = 1;
6 | }
7 |
8 | message HelloResponse {
9 | string message = 1;
10 | }
11 |
12 | service Hello {
13 | rpc SayHello(HelloRequest) returns (HelloResponse) {}
14 | }
15 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/server/hello_server.go:
--------------------------------------------------------------------------------
1 | // versions:
2 | // gofr-cli v0.6.0
3 | // gofr.dev v1.37.0
4 | // source: hello.proto
5 |
6 | package server
7 |
8 | import (
9 | "fmt"
10 |
11 | "gofr.dev/pkg/gofr"
12 | )
13 |
14 | // Register the gRPC service in your app using the following code in your main.go:
15 | //
16 | // server.RegisterHelloServerWithGofr(app, &server.NewHelloGoFrServer())
17 | //
18 | // HelloGoFrServer defines the gRPC server implementation.
19 | // Customize the struct with required dependencies and fields as needed.
20 |
21 | type HelloGoFrServer struct {
22 | health *healthServer
23 | }
24 |
25 | func (s *HelloGoFrServer) SayHello(ctx *gofr.Context) (any, error) {
26 | request := HelloRequest{}
27 |
28 | err := ctx.Bind(&request)
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | name := request.Name
34 | if name == "" {
35 | name = "World"
36 | }
37 |
38 | //Performing HealthCheck
39 | //res, err := s.health.Check(ctx, &grpc_health_v1.HealthCheckRequest{
40 | // Service: "Hello",
41 | //})
42 | //ctx.Log(res.String())
43 |
44 | // Setting the serving status
45 | //s.health.SetServingStatus(ctx, "Hello", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
46 |
47 | return &HelloResponse{
48 | Message: fmt.Sprintf("Hello %s!", name),
49 | }, nil
50 | }
51 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/server/hello_server_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 |
10 | "gofr.dev/pkg/gofr"
11 | )
12 |
13 | func TestServer_SayHello(t *testing.T) {
14 | s := HelloGoFrServer{}
15 |
16 | tests := []struct {
17 | input string
18 | resp string
19 | }{
20 | {"world", "Hello world!"},
21 | {"123", "Hello 123!"},
22 | {"", "Hello World!"},
23 | }
24 |
25 | for i, tc := range tests {
26 | req := &HelloRequest{Name: tc.input}
27 |
28 | request := &HelloRequestWrapper{
29 | context.Background(),
30 | req,
31 | }
32 |
33 | ctx := &gofr.Context{
34 | Request: request,
35 | }
36 |
37 | resp, err := s.SayHello(ctx)
38 | grpcResponse, ok := resp.(*HelloResponse)
39 | require.True(t, ok)
40 |
41 | require.NoError(t, err, "TEST[%d], Failed.\n", i)
42 |
43 | assert.Equal(t, tc.resp, grpcResponse.Message, "TEST[%d], Failed.\n", i)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/grpc/grpc-unary-server/server/request_gofr.go:
--------------------------------------------------------------------------------
1 | // Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
2 | // versions:
3 | // gofr-cli v0.7.0
4 | // gofr.dev v1.39.0
5 | // source: hello.proto
6 |
7 |
8 | package server
9 |
10 | import (
11 | "context"
12 | "fmt"
13 | "reflect"
14 | )
15 |
16 | // Request Wrappers
17 | type HelloRequestWrapper struct {
18 | ctx context.Context
19 | *HelloRequest
20 | }
21 |
22 | func (h *HelloRequestWrapper) Context() context.Context {
23 | return h.ctx
24 | }
25 |
26 | func (h *HelloRequestWrapper) Param(s string) string {
27 | return ""
28 | }
29 |
30 | func (h *HelloRequestWrapper) PathParam(s string) string {
31 | return ""
32 | }
33 |
34 | func (h *HelloRequestWrapper) Bind(p interface{}) error {
35 | ptr := reflect.ValueOf(p)
36 | if ptr.Kind() != reflect.Ptr {
37 | return fmt.Errorf("expected a pointer, got %T", p)
38 | }
39 |
40 | hValue := reflect.ValueOf(h.HelloRequest).Elem()
41 | ptrValue := ptr.Elem()
42 |
43 | for i := 0; i < hValue.NumField(); i++ {
44 | field := hValue.Type().Field(i)
45 | if field.Name == "state" || field.Name == "sizeCache" || field.Name == "unknownFields" {
46 | continue
47 | }
48 |
49 | if field.IsExported() {
50 | ptrValue.Field(i).Set(hValue.Field(i))
51 | }
52 | }
53 |
54 | return nil
55 | }
56 |
57 | func (h *HelloRequestWrapper) HostName() string {
58 | return ""
59 | }
60 |
61 | func (h *HelloRequestWrapper) Params(s string) []string {
62 | return nil
63 | }
--------------------------------------------------------------------------------
/examples/http-server-using-redis/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 8000
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/http-server-using-redis/README.md:
--------------------------------------------------------------------------------
1 | # Redis Example
2 |
3 | This GoFr example demonstrates the use of Redis as datasource through a simple HTTP server.
4 |
5 | ### To run the example follow the steps below:
6 |
7 | - Run the docker image of Redis
8 | ```console
9 | docker run --name gofr-redis -p 2002:6379 -d redis:7.0.5
10 | ```
11 |
12 | - Now run the example
13 | ```console
14 | go run main.go
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/http-server-using-redis/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=sample-api
2 | HTTP_PORT=8000
3 |
4 | REDIS_HOST=localhost
5 | REDIS_PORT=2002
6 |
7 | LOG_LEVEL=DEBUG
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/http-server/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build stage
2 | FROM golang:1.24 AS build
3 |
4 | WORKDIR /src
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a -o /app/main main.go
8 |
9 | # Final stage
10 | FROM alpine:3.14
11 | RUN apk add --no-cache tzdata ca-certificates
12 | COPY --from=build /app/main /main
13 | COPY --from=build /src/configs /configs
14 | EXPOSE 9000
15 |
16 | CMD ["/main"]
17 |
--------------------------------------------------------------------------------
/examples/http-server/README.md:
--------------------------------------------------------------------------------
1 | # HTTP Server Example
2 |
3 | This GoFr example demonstrates a simple HTTP server which supports Redis and MySQL as datasources.
4 |
5 | ### To run the example follow the steps below:
6 |
7 | - Run the docker image of the application
8 | ```console
9 | docker-compose up -d
10 | ```
11 |
12 | To test the example, follow these steps:
13 |
14 | 1. Open your browser and navigate to `http://localhost:9000/hello`.
15 | 2. To view the GoFr trace, open `https://tracer.gofr.dev` and paste the traceid.
16 | 3. To view the Grafana Dashboard open `http://localhost:3000`
17 |
18 |
--------------------------------------------------------------------------------
/examples/http-server/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=sample-api
2 | HTTP_PORT=9000
3 |
4 | REDIS_HOST=localhost
5 | REDIS_PORT=2002
6 |
7 | DB_HOST=localhost
8 | DB_USER=root
9 | DB_PASSWORD=password
10 | DB_NAME=test
11 | DB_PORT=2001
12 | DB_DIALECT=mysql
13 |
14 | TRACE_EXPORTER=gofr
15 |
--------------------------------------------------------------------------------
/examples/http-server/docker/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | gofr-http-server:
5 | build:
6 | context: ../.
7 | dockerfile: Dockerfile
8 | environment:
9 | - TRACE_EXPORTER=gofr
10 | - TRACER_RATIO=0.1
11 | - REDIS_HOST=redisdb
12 | - REDIS_PORT=6379
13 | - DB_HOST=mysqldb
14 | - DB_USER=root
15 | - DB_PASSWORD=password
16 | - DB_NAME=test
17 | - DB_PORT=3306
18 | - DB_DIALECT=mysql
19 | ports:
20 | - "9000:9000"
21 | - "2121:2121"
22 | depends_on:
23 | - redisdb
24 | - mysqldb
25 | - grafana
26 | - prometheus
27 | networks:
28 | - gofr-network
29 |
30 | redisdb:
31 | image: redis:7.0.5
32 | ports:
33 | - "2002:6379"
34 | networks:
35 | - gofr-network
36 |
37 | mysqldb:
38 | image: mysql:8.0.30
39 | environment:
40 | MYSQL_ROOT_PASSWORD: password
41 | MYSQL_DATABASE: test
42 | ports:
43 | - "2001:3306"
44 | networks:
45 | - gofr-network
46 |
47 | grafana:
48 | image: grafana/grafana:latest
49 | ports:
50 | - "3000:3000"
51 | environment:
52 | - GF_SECURITY_ADMIN_USER=admin
53 | - GF_SECURITY_ADMIN_PASSWORD=password
54 | volumes:
55 | - ./provisioning:/etc/grafana/provisioning
56 | networks:
57 | - gofr-network
58 |
59 | prometheus:
60 | image: prom/prometheus:latest
61 | ports:
62 | - "9090:9090"
63 | volumes:
64 | - ./prometheus:/etc/prometheus
65 | networks:
66 | - gofr-network
67 |
68 | networks:
69 | gofr-network:
--------------------------------------------------------------------------------
/examples/http-server/docker/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
3 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
4 | # scrape_timeout is set to the global default (10s).
5 |
6 | scrape_configs:
7 | - job_name: 'prometheus'
8 | scrape_interval: 5s
9 | metrics_path: '/metrics'
10 | static_configs:
11 | - targets: ['host.docker.internal:2121']
--------------------------------------------------------------------------------
/examples/http-server/docker/provisioning/dashboards/dashboards.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: 1
2 |
3 | providers:
4 | - name: 'Gofr Dashboard'
5 | orgId: 1
6 | folder: ''
7 | type: file
8 | disableDeletion: false
9 | updateIntervalSeconds: 10
10 | options:
11 | path: /etc/grafana/provisioning/dashboards/gofr-dashboard
--------------------------------------------------------------------------------
/examples/http-server/docker/provisioning/datasources/datasource.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: 1
2 |
3 | datasources:
4 | - name: Prometheus
5 | type: prometheus
6 | access: proxy
7 | url: http://prometheus:9090
8 | isDefault: true
9 | editable: true
--------------------------------------------------------------------------------
/examples/sample-cmd/README.md:
--------------------------------------------------------------------------------
1 | # CMD Example
2 |
3 | This GoFr example demonstrates a simple CMD application.
4 |
5 | ### To run the example use the command below:
6 | ```console
7 | go run main.go
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/sample-cmd/configs/.test.env:
--------------------------------------------------------------------------------
1 | CMD_LOGS_FILE=logs.txt
--------------------------------------------------------------------------------
/examples/using-add-filestore/README.md:
--------------------------------------------------------------------------------
1 | # Add FileStore Example
2 |
3 | This GoFr example demonstrates a CMD application that can be used to interact with a remote file server using FTP or SFTP protocol
4 |
5 | ### Setting up an FTP server in local machine
6 | - https://security.appspot.com/vsftpd.html
7 | - https://pypi.org/project/pyftpdlib/
8 |
9 | Choose a library listed above and follow their respective documentation to configure an FTP server in your local machine and replace the configs/env file with correct HOST,USER_NAME,PASSWORD,PORT and REMOTE_DIR_PATH details.
10 |
11 | ### To run the example use the commands below:
12 | To print the current working directory of the configured remote file server
13 | ```console
14 | go run main.go pwd
15 | ```
16 | To get the list of all directories or files in the given path of the configured remote file server
17 |
18 | ```
19 | go run main.go ls -path=/
20 | ```
21 | To grep the list of all files and directories in the given path that is matching with the keyword provided
22 |
23 | ```
24 | go run main.go grep -keyword=fi -path=/
25 | ```
26 |
27 | To create a file in the current working directory with the provided filename
28 | ```
29 | go run main.go createfile -filename=file.txt
30 | ```
31 |
32 | To remove the file with the provided filename from the current working directory
33 | ```
34 | go run main.go rm -filename=file.txt
35 | ```
--------------------------------------------------------------------------------
/examples/using-add-filestore/configs/.env:
--------------------------------------------------------------------------------
1 | HOST=localhost
2 | USER_NAME=anonymous
3 | PASSWORD=test
4 | PORT=21
5 | REMOTE_DIR_PATH=/
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 9090
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/README.md:
--------------------------------------------------------------------------------
1 | # AddRESTHandlers Example
2 |
3 | This GoFr example demonstrates a simple HTTP server with CRUD operations which are created by GoFr using the given struct.
4 |
5 | ### To run the example follow the steps below:
6 |
7 | - Run the docker image of MySQL
8 | ```console
9 | docker run --name gofr-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=test -p 2001:3306 -d mysql:8.0.30
10 | ```
11 |
12 | - Now run the example
13 | ```console
14 | go run main.go
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=using-add-rest-handlers
2 | HTTP_PORT=9090
3 |
4 | DB_HOST=localhost
5 | DB_USER=root
6 | DB_PASSWORD=password
7 | DB_NAME=test
8 | DB_PORT=2001
9 | DB_DIALECT=mysql
10 |
11 | TRACE_EXPORTER=zipkin
12 | TRACER_URL=http://localhost:2005/api/v2/spans
13 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/examples/using-add-rest-handlers/migrations"
5 | "gofr.dev/pkg/gofr"
6 | )
7 |
8 | type user struct {
9 | Id int `json:"id"`
10 | Name string `json:"name"`
11 | Age int `json:"age"`
12 | IsEmployed bool `json:"isEmployed"`
13 | }
14 |
15 | // GetAll : User can overwrite the specific handlers by implementing them like this
16 | func (u *user) GetAll(c *gofr.Context) (any, error) {
17 | return "user GetAll called", nil
18 | }
19 |
20 | func main() {
21 | // Create a new application
22 | a := gofr.New()
23 |
24 | // Add migrations to run
25 | a.Migrate(migrations.All())
26 |
27 | // AddRESTHandlers creates CRUD handles for the given entity
28 | err := a.AddRESTHandlers(&user{})
29 | if err != nil {
30 | return
31 | }
32 |
33 | // Run the application
34 | a.Run()
35 | }
36 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 |
13 | "gofr.dev/pkg/gofr/testutil"
14 | )
15 |
16 | func TestMain(m *testing.M) {
17 | os.Setenv("GOFR_TELEMETRY", "false")
18 | m.Run()
19 | }
20 |
21 | func TestIntegration_AddRESTHandlers(t *testing.T) {
22 | configs := testutil.NewServerConfigs(t)
23 |
24 | go main()
25 | time.Sleep(100 * time.Millisecond) // Giving some time to start the server
26 |
27 | tests := []struct {
28 | desc string
29 | method string
30 | path string
31 | body []byte
32 | statusCode int
33 | }{
34 | {"empty path", http.MethodGet, "/", nil, 404},
35 | {"success Create", http.MethodPost, "/user",
36 | []byte(`{"id":10, "name":"john doe", "age":99, "isEmployed": true}`), 201},
37 | {"success GetAll", http.MethodGet, "/user", nil, 200},
38 | {"success Get", http.MethodGet, "/user/10", nil, 200},
39 | {"success Update", http.MethodPut, "/user/10",
40 | []byte(`{"name":"john doe", "age":99, "isEmployed": false}`), 200},
41 | {"success Delete", http.MethodDelete, "/user/10", nil, 204},
42 | }
43 |
44 | for i, tc := range tests {
45 | req, _ := http.NewRequest(tc.method, configs.HTTPHost+tc.path, bytes.NewReader(tc.body))
46 | req.Header.Set("content-type", "application/json")
47 |
48 | c := http.Client{}
49 | resp, err := c.Do(req)
50 |
51 | require.NoError(t, err, "TEST[%d], Failed.\n%s", i, tc.desc)
52 |
53 | assert.Equal(t, tc.statusCode, resp.StatusCode, "TEST[%d], Failed.\n%s", i, tc.desc)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/migrations/1721816030_create_user_table.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | const createTable = `CREATE TABLE IF NOT EXISTS user (
8 | id int not null primary key,
9 | name varchar(50) not null,
10 | age int not null,
11 | is_employed bool not null
12 | );`
13 |
14 | func createTableUser() migration.Migrate {
15 | return migration.Migrate{
16 | UP: func(d migration.Datasource) error {
17 | _, err := d.SQL.Exec(createTable)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | return nil
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/migrations/1721816030_create_user_table_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/DATA-DOG/go-sqlmock"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 |
11 | "gofr.dev/pkg/gofr/migration"
12 | )
13 |
14 | func TestCreateTableUser(t *testing.T) {
15 | tests := []struct {
16 | desc string
17 | mockBehaviors func(mock sqlmock.Sqlmock)
18 | expectedError error
19 | }{
20 | {"successful creation", func(mock sqlmock.Sqlmock) {
21 | mock.ExpectExec(createTable).WillReturnResult(sqlmock.NewResult(0, 1))
22 | }, nil},
23 | {"error on create table", func(mock sqlmock.Sqlmock) {
24 | mock.ExpectExec(createTable).WillReturnError(fmt.Errorf("create table error"))
25 | }, fmt.Errorf("create table error")},
26 | }
27 |
28 | for i, tc := range tests {
29 | // Create mock database and datasource
30 | db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
31 | require.NoError(t, err)
32 | defer db.Close()
33 |
34 | datasource := migration.Datasource{SQL: db}
35 |
36 | // Set mock expectations
37 | tc.mockBehaviors(mock)
38 |
39 | // Execute the migration
40 | err = createTableUser().UP(datasource)
41 |
42 | assert.Equal(t, tc.expectedError, err, "TEST[%d] Failed.\n%s", i, tc.desc)
43 | require.NoError(t, mock.ExpectationsWereMet())
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/migrations/all.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | func All() map[int64]migration.Migrate {
8 | return map[int64]migration.Migrate{
9 | 1721816030: createTableUser(),
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/using-add-rest-handlers/migrations/all_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "gofr.dev/pkg/gofr/migration"
9 | )
10 |
11 | func TestAll(t *testing.T) {
12 | // Get the map of migrations
13 | allMigrations := All()
14 |
15 | expected := map[int64]migration.Migrate{
16 | 1721816030: createTableUser(),
17 | }
18 |
19 | // Check if the length of the maps match
20 | assert.Equal(t, len(expected), len(allMigrations), "TestAll Failed!")
21 | }
22 |
--------------------------------------------------------------------------------
/examples/using-cron-jobs/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=sample-cron
2 |
3 | TRACE_EXPORTER=zipkin
4 | TRACER_URL=http://localhost:2005/api/v2/spans
--------------------------------------------------------------------------------
/examples/using-cron-jobs/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "gofr.dev/pkg/gofr"
8 | )
9 |
10 | var (
11 | n = 0
12 | mu sync.RWMutex
13 | )
14 |
15 | const duration = 3
16 |
17 | func main() {
18 | app := gofr.New()
19 |
20 | // runs every second
21 | app.AddCronJob("* * * * * *", "counter", count)
22 |
23 | // setting the maximum duration of this application
24 | time.Sleep(duration * time.Second)
25 |
26 | // not running the app to close after we have completed the crons running
27 | // since this is an example the cron will not be running forever
28 | // to run cron forever, users can start the metric server or normal HTTP server
29 | // app.Run()
30 | }
31 |
32 | func count(c *gofr.Context) {
33 | mu.Lock()
34 | defer mu.Unlock()
35 |
36 | n++
37 |
38 | c.Log("Count:", n)
39 | }
40 |
--------------------------------------------------------------------------------
/examples/using-cron-jobs/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 |
10 | "gofr.dev/pkg/gofr/testutil"
11 | )
12 |
13 | func TestMain(m *testing.M) {
14 | os.Setenv("GOFR_TELEMETRY", "false")
15 | m.Run()
16 | }
17 |
18 | func Test_UserPurgeCron(t *testing.T) {
19 | configs := testutil.NewServerConfigs(t)
20 |
21 | go main()
22 | time.Sleep(1100 * time.Millisecond)
23 |
24 | expected := 1
25 |
26 | var m int
27 |
28 | mu.Lock()
29 | m = n
30 | mu.Unlock()
31 |
32 | assert.Equal(t, expected, m)
33 | t.Logf("Metrics server running at: %s", configs.MetricsHost)
34 | }
35 |
--------------------------------------------------------------------------------
/examples/using-custom-metrics/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 9011
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-custom-metrics/README.md:
--------------------------------------------------------------------------------
1 | # Custom Metrics Example
2 |
3 | This GoFr example demonstrates the use of custom metrics through a simple HTTP server that creates and populate metrics.
4 | GoFr by default pushes metrics to port `2121` on `/metrics` endpoint.
5 |
6 | ### To run the example use the command below:
7 | ```console
8 | go run main.go
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/using-custom-metrics/configs/.env:
--------------------------------------------------------------------------------
1 | HTTP_PORT=9011
2 |
3 | APP_NAME=using-metrics
4 | APP_VERSION=v0.1.0
5 |
6 | METRICS_PORT=2120
7 |
--------------------------------------------------------------------------------
/examples/using-file-bind/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 8300
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-file-bind/README.md:
--------------------------------------------------------------------------------
1 | # Using File Bind Example
2 |
3 | This GoFr example demonstrates the use of context Bind where incoming request has multipart-form data and then binds
4 | it to the fields of the struct. GoFr currently supports zip file type and also binds the more generic [`multipart.FileHeader`](https://pkg.go.dev/mime/multipart#FileHeader)
5 |
6 | ### Usage
7 | ```go
8 | type Data struct {
9 | Compressed file.Zip `file:"upload"`
10 |
11 | FileHeader *multipart.FileHeader `file:"file_upload"`
12 | }
13 |
14 | func Handler(c *gofr.Context) (any, error) {
15 | var d Data
16 |
17 | // bind the multipart data into the variable d
18 | err := c.Bind(&d)
19 | if err != nil {
20 | return nil, err
21 | }
22 | }
23 | ```
24 |
25 | ### To run the example use the command below:
26 | ```console
27 | go run main.go
28 | ```
29 |
--------------------------------------------------------------------------------
/examples/using-file-bind/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=file-upload-api
2 | HTTP_PORT=8300
3 |
4 | LOG_LEVEL=DEBUG
--------------------------------------------------------------------------------
/examples/using-html-template/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/pkg/gofr"
5 | "gofr.dev/pkg/gofr/http/response"
6 | )
7 |
8 | func main() {
9 | app := gofr.New()
10 | app.GET("/list", listHandler)
11 | app.AddStaticFiles("/", "./static")
12 | app.Run()
13 | }
14 |
15 | type Todo struct {
16 | Title string
17 | Done bool
18 | }
19 |
20 | type TodoPageData struct {
21 | PageTitle string
22 | Todos []Todo
23 | }
24 |
25 | func listHandler(*gofr.Context) (any, error) {
26 | // Get data from somewhere
27 | data := TodoPageData{
28 | PageTitle: "My TODO list",
29 | Todos: []Todo{
30 | {Title: "Expand on Gofr documentation ", Done: false},
31 | {Title: "Add more examples", Done: true},
32 | {Title: "Write some articles", Done: false},
33 | },
34 | }
35 |
36 | return response.Template{Data: data, Name: "todo.html"}, nil
37 | }
38 |
--------------------------------------------------------------------------------
/examples/using-html-template/static/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 404 - Page Not Found
7 |
8 |
9 |
10 |
11 |
12 |
404 - Page Not Found
13 |
The requested resource could not be found.
14 |
Go Back to Home
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/using-html-template/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |
Hello HTML!
9 |
This is a simple HTML file served by the server.
10 |
Click to see template being rendered
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/using-html-template/static/style.css:
--------------------------------------------------------------------------------
1 | body,html {
2 | margin: 0;
3 | padding: 0;
4 | background: oklch(0.208 0.042 265.755);
5 | color: oklch(0.869 0.022 252.894);
6 | font-family: ui-sans-serif, system-ui, sans-serif;
7 | }
8 | * {
9 | box-sizing: border-box;
10 | }
11 | h1 {
12 | font-size: 2rem;
13 | margin: 0;
14 | padding: 1rem;
15 | background: oklch(0.208 0.042 265.755);
16 | color: oklch(0.869 0.022 252.894);
17 | }
18 |
19 | div#content {padding:80px 20px; text-align: center }
20 |
21 | a {color: oklch(0.917 0.08 205.041); text-decoration: none;}
22 | a:hover {font-weight: bold}
23 |
24 |
25 | h2 {
26 | text-align: left;
27 | }
28 | ul {
29 | text-align: left ;
30 | }
31 |
32 | li {
33 | list-style-type: circle;
34 | }
35 |
36 | li.done {
37 | list-style-type: disc;
38 | text-decoration: line-through;
39 | }
--------------------------------------------------------------------------------
/examples/using-html-template/templates/todo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
{{.PageTitle}}
9 |
10 | {{range .Todos}}
11 | {{if .Done}}
12 | - {{.Title}}
13 | {{else}}
14 | - {{.Title}}
15 | {{end}}
16 | {{end}}
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/using-http-service/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 9001
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-http-service/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=using-http-service
2 | HTTP_PORT=9001
--------------------------------------------------------------------------------
/examples/using-http-service/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "time"
7 |
8 | "gofr.dev/pkg/gofr"
9 | "gofr.dev/pkg/gofr/service"
10 | )
11 |
12 | func main() {
13 | a := gofr.New()
14 |
15 | // HTTP service with Circuit Breaker config given, uses custom health check
16 | // either of circuit breaker or health can be used as well, as both implement service.Options interface.
17 | // Note: /breeds is not an actual health check endpoint for "https://catfact.ninja"
18 | a.AddHTTPService("cat-facts", "https://catfact.ninja",
19 | &service.CircuitBreakerConfig{
20 | Threshold: 4,
21 | Interval: 1 * time.Second,
22 | },
23 | &service.HealthConfig{
24 | HealthEndpoint: "breeds",
25 | },
26 | )
27 |
28 | // service with improper health-check to test health check
29 | a.AddHTTPService("fact-checker", "https://catfact.ninja",
30 | &service.HealthConfig{
31 | HealthEndpoint: "breed",
32 | },
33 | )
34 |
35 | a.GET("/fact", Handler)
36 |
37 | a.Run()
38 | }
39 |
40 | func Handler(c *gofr.Context) (any, error) {
41 | var data = struct {
42 | Fact string `json:"fact"`
43 | Length int `json:"length"`
44 | }{}
45 |
46 | var catFacts = c.GetHTTPService("cat-facts")
47 |
48 | resp, err := catFacts.Get(c, "fact", map[string]any{
49 | "max_length": 20,
50 | })
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | b, _ := io.ReadAll(resp.Body)
56 | err = json.Unmarshal(b, &data)
57 | if err != nil {
58 | return nil, err
59 | }
60 |
61 | return data, nil
62 | }
63 |
--------------------------------------------------------------------------------
/examples/using-http-service/readme.md:
--------------------------------------------------------------------------------
1 | # Http-Service Example
2 |
3 | This GoFr example demonstrates an inter-service HTTP communication along with circuit-breaker as well as
4 | service health config addition.
5 |
6 | User can use the `AddHTTPService` method to add an HTTP service and then later get it using `GetHTTPService("service-name")`
7 |
8 | ### To run the example follow the below steps:
9 | - Make sure your other service and health endpoint is ready and up on the given address.
10 | - Now run the example using below command :
11 |
12 | ```console
13 | go run main.go
14 | ```
--------------------------------------------------------------------------------
/examples/using-migrations/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 9100
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-migrations/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=using-migrations
2 | HTTP_PORT=9100
3 |
4 | REDIS_HOST=localhost
5 | REDIS_PORT=2002
6 |
7 | DB_HOST=localhost
8 | DB_USER=root
9 | DB_PASSWORD=password
10 | DB_NAME=test
11 | DB_PORT=2001
12 | DB_DIALECT=mysql
13 |
14 | TRACE_EXPORTER=zipkin
15 | TRACER_URL=http://localhost:2005/api/v2/spans
16 |
--------------------------------------------------------------------------------
/examples/using-migrations/migrations/1722507126_create_employee_table.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | const createTable = `CREATE TABLE IF NOT EXISTS employee
8 | (
9 | id int not null
10 | primary key,
11 | name varchar(50) not null,
12 | gender varchar(6) not null,
13 | contact_number varchar(10) not null
14 | );`
15 |
16 | const employee_date = `INSERT INTO employee (id, name, gender, contact_number) VALUES (1, 'Umang', "M", "0987654321");`
17 |
18 | func createTableEmployee() migration.Migrate {
19 | return migration.Migrate{
20 | UP: func(d migration.Datasource) error {
21 | _, err := d.SQL.Exec(createTable)
22 | if err != nil {
23 | return err
24 | }
25 |
26 | _, err = d.SQL.Exec(employee_date)
27 | if err != nil {
28 | return err
29 | }
30 |
31 | _, err = d.SQL.Exec("alter table employee add dob varchar(11) null;")
32 | if err != nil {
33 | return err
34 | }
35 |
36 | return nil
37 | },
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/using-migrations/migrations/1722507180_redis_add_employee_name.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "context"
5 |
6 | "gofr.dev/pkg/gofr/migration"
7 | )
8 |
9 | func addEmployeeInRedis() migration.Migrate {
10 | return migration.Migrate{
11 | UP: func(d migration.Datasource) error {
12 | err := d.Redis.Set(context.Background(), "Umang", "0987654321", 0).Err()
13 | if err != nil {
14 | return err
15 | }
16 |
17 | return nil
18 |
19 | },
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/using-migrations/migrations/1722507180_redis_add_employee_name_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | "github.com/go-redis/redismock/v9"
8 | "github.com/stretchr/testify/require"
9 |
10 | "gofr.dev/pkg/gofr/migration"
11 | )
12 |
13 | func TestAddEmployeeInRedis(t *testing.T) {
14 | client, mock := redismock.NewClientMock()
15 | datasource := migration.Datasource{Redis: client}
16 |
17 | // Set expectations for the Set method
18 | mock.ExpectSet("Umang", "0987654321", 0).SetVal("OK")
19 |
20 | // Call the UP method of the migration
21 | err := addEmployeeInRedis().UP(datasource)
22 | require.NoError(t, err)
23 | }
24 |
25 | func TestAddEmployeeInRedis_Error(t *testing.T) {
26 | client, mock := redismock.NewClientMock()
27 | datasource := migration.Datasource{Redis: client}
28 |
29 | mock.ExpectSet("Umang", "0987654321", 0).SetErr(errors.New("redis error"))
30 |
31 | // Call the UP method of the migration
32 | err := addEmployeeInRedis().UP(datasource)
33 |
34 | if err == nil || err.Error() != "redis error" {
35 | t.Errorf("TestAddEmployeeInRedis Error failed! unexpected error: %v", err)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/using-migrations/migrations/all.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | func All() map[int64]migration.Migrate {
8 | return map[int64]migration.Migrate{
9 | 1722507126: createTableEmployee(),
10 | 1722507180: addEmployeeInRedis(),
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/using-migrations/migrations/all_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "gofr.dev/pkg/gofr/migration"
9 | )
10 |
11 | func TestAll(t *testing.T) {
12 | // Get the map of migrations
13 | allMigrations := All()
14 |
15 | expected := map[int64]migration.Migrate{
16 | 1722507126: createTableEmployee(),
17 | 1722507180: addEmployeeInRedis(),
18 | }
19 |
20 | // Check if the length of the maps match
21 | assert.Equal(t, len(expected), len(allMigrations), "TestAll Failed!")
22 | }
23 |
--------------------------------------------------------------------------------
/examples/using-migrations/readme.md:
--------------------------------------------------------------------------------
1 | # Migrations Example
2 |
3 | This GoFr example demonstrates the use of `migrations` through a simple HTTP server using MySQL, Redis and Kafka.
4 |
5 | ### To run the example follow the below steps:
6 | - Run the docker image of MySQL, Redis and Kafka
7 |
8 | ```console
9 | docker run --name gofr-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=test -p 2001:3306 -d mysql:8.0.30
10 | docker run --name gofr-redis -p 2002:6379 -d redis:7.0.5
11 | docker run --name kafka-1 -p 9092:9092 \
12 | -e KAFKA_ENABLE_KRAFT=yes \
13 | -e KAFKA_CFG_PROCESS_ROLES=broker,controller \
14 | -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
15 | -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
16 | -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
17 | -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 \
18 | -e KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true \
19 | -e KAFKA_BROKER_ID=1 \
20 | -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@127.0.0.1:9093 \
21 | -e ALLOW_PLAINTEXT_LISTENER=yes \
22 | -e KAFKA_CFG_NODE_ID=1 \
23 | -v kafka_data:/bitnami \
24 | bitnami/kafka:3.4
25 | ```
26 |
27 | - Now run the example using below command :
28 |
29 | ```console
30 | go run main.go
31 | ```
32 |
--------------------------------------------------------------------------------
/examples/using-publisher/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 8100
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-publisher/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=sample-api
2 | HTTP_PORT=8100
3 |
4 | LOG_LEVEL=DEBUG
5 |
6 | PUBSUB_BACKEND=KAFKA
7 | PUBSUB_BROKER=localhost:9092
8 | CONSUMER_ID=test
9 |
10 | # For using MQTT uncomment these configs
11 | #PUBSUB_BACKEND=MQTT
12 | #MQTT_PROTOCOL=tcp
13 | #MQTT_HOST=localhost
14 | #MQTT_PORT=8883
15 | #MQTT_CLIENT_ID_SUFFIX=test-publisher
16 |
17 | #PUBSUB_BACKEND=GOOGLE
18 | #GOOGLE_PROJECT_ID=products
19 | #GOOGLE_SUBSCRIPTION_NAME=gcp-pubs
--------------------------------------------------------------------------------
/examples/using-publisher/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "gofr.dev/examples/using-publisher/migrations"
7 | "gofr.dev/pkg/gofr"
8 | )
9 |
10 | func main() {
11 | app := gofr.New()
12 |
13 | app.Migrate(migrations.All())
14 |
15 | app.POST("/publish-order", order)
16 | app.POST("/publish-product", product)
17 |
18 | app.Run()
19 | }
20 |
21 | func order(ctx *gofr.Context) (any, error) {
22 | type orderStatus struct {
23 | OrderId string `json:"orderId"`
24 | Status string `json:"status"`
25 | }
26 |
27 | var data orderStatus
28 |
29 | err := ctx.Bind(&data)
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | msg, _ := json.Marshal(data)
35 |
36 | err = ctx.GetPublisher().Publish(ctx, "order-logs", msg)
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | return "Published", nil
42 | }
43 |
44 | func product(ctx *gofr.Context) (any, error) {
45 | type productInfo struct {
46 | ProductId string `json:"productId"`
47 | Price string `json:"price"`
48 | }
49 |
50 | var data productInfo
51 |
52 | err := ctx.Bind(&data)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | msg, _ := json.Marshal(data)
58 |
59 | err = ctx.GetPublisher().Publish(ctx, "products", msg)
60 | if err != nil {
61 | return nil, err
62 | }
63 |
64 | return "Published", nil
65 | }
66 |
--------------------------------------------------------------------------------
/examples/using-publisher/migrations/1721801313_create_topics.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "context"
5 |
6 | "gofr.dev/pkg/gofr/migration"
7 | )
8 |
9 | func createTopics() migration.Migrate {
10 | return migration.Migrate{
11 | UP: func(d migration.Datasource) error {
12 | err := d.PubSub.CreateTopic(context.Background(), "products")
13 | if err != nil {
14 | return err
15 | }
16 |
17 | return d.PubSub.CreateTopic(context.Background(), "order-logs")
18 | },
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/using-publisher/migrations/all.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | func All() map[int64]migration.Migrate {
8 | return map[int64]migration.Migrate{
9 | 1721801313: createTopics(),
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/using-publisher/migrations/all_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "gofr.dev/pkg/gofr/migration"
9 | )
10 |
11 | func TestAll(t *testing.T) {
12 | // Get the map of migrations
13 | allMigrations := All()
14 |
15 | expected := map[int64]migration.Migrate{
16 | 1721801313: createTopics(),
17 | }
18 |
19 | // Check if the length of the maps match
20 | assert.Equal(t, len(expected), len(allMigrations), "TestAll Failed!")
21 | }
22 |
--------------------------------------------------------------------------------
/examples/using-publisher/readme.md:
--------------------------------------------------------------------------------
1 | # Publisher Example
2 |
3 | This GoFr example demonstrates a simple Publisher that publishes to the given topic when an HTTP request is made to it's
4 | matching route.
5 |
6 | ### To run the example follow the below steps:
7 |
8 | - Run the docker image of Kafka and ensure that your provided topics are created before publishing
9 | ```console
10 | docker run --name kafka-1 -p 9092:9092 \
11 | -e KAFKA_ENABLE_KRAFT=yes \
12 | -e KAFKA_CFG_PROCESS_ROLES=broker,controller \
13 | -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
14 | -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
15 | -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
16 | -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 \
17 | -e KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true \
18 | -e KAFKA_BROKER_ID=1 \
19 | -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@127.0.0.1:9093 \
20 | -e ALLOW_PLAINTEXT_LISTENER=yes \
21 | -e KAFKA_CFG_NODE_ID=1 \
22 | -v kafka_data:/bitnami \
23 | bitnami/kafka:3.4
24 | ```
25 |
26 | - Now run the example using below command :
27 |
28 | ```console
29 | go run main.go
30 | ```
31 |
32 |
--------------------------------------------------------------------------------
/examples/using-subscriber/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.24
2 |
3 | RUN mkdir /src/
4 | WORKDIR /src/
5 | COPY . .
6 | RUN go get ./...
7 | RUN go build -ldflags "-linkmode external -extldflags -static" -a main.go
8 |
9 | FROM alpine:latest
10 | RUN apk add --no-cache tzdata ca-certificates
11 | COPY --from=0 /src/main /main
12 | COPY --from=0 /src/configs /configs
13 | EXPOSE 8200
14 |
15 | CMD ["/main"]
16 |
--------------------------------------------------------------------------------
/examples/using-subscriber/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=sample-api
2 | HTTP_PORT=8200
3 |
4 | LOG_LEVEL=DEBUG
5 |
6 | PUBSUB_BACKEND=KAFKA
7 | PUBSUB_BROKER=localhost:9092
8 | CONSUMER_ID=test
9 | PUBSUB_OFFSET=-2
10 |
11 | # For using MQTT uncomment these configs
12 | #PUBSUB_BACKEND=MQTT
13 | #MQTT_PROTOCOL=tcp
14 | #MQTT_HOST=localhost
15 | #MQTT_PORT=8883
16 | #MQTT_CLIENT_ID_SUFFIX=test-subscriber
17 |
18 | #PUBSUB_BACKEND=GOOGLE
19 | #GOOGLE_PROJECT_ID=products
20 | #GOOGLE_SUBSCRIPTION_NAME=gcp-subs
21 |
--------------------------------------------------------------------------------
/examples/using-subscriber/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/examples/using-subscriber/migrations"
5 | "gofr.dev/pkg/gofr"
6 | )
7 |
8 | func main() {
9 | app := gofr.New()
10 |
11 | app.Migrate(migrations.All())
12 |
13 | app.Subscribe("products", func(c *gofr.Context) error {
14 | var productInfo struct {
15 | ProductId string `json:"productId"`
16 | Price string `json:"price"`
17 | }
18 |
19 | err := c.Bind(&productInfo)
20 | if err != nil {
21 | c.Logger.Error(err)
22 |
23 | return nil
24 | }
25 |
26 | c.Logger.Info("Received product", productInfo)
27 |
28 | return nil
29 | })
30 |
31 | app.Subscribe("order-logs", func(c *gofr.Context) error {
32 | var orderStatus struct {
33 | OrderId string `json:"orderId"`
34 | Status string `json:"status"`
35 | }
36 |
37 | err := c.Bind(&orderStatus)
38 | if err != nil {
39 | c.Logger.Error(err)
40 |
41 | return nil
42 | }
43 |
44 | c.Logger.Info("Received order", orderStatus)
45 |
46 | return nil
47 | })
48 |
49 | app.Run()
50 | }
51 |
--------------------------------------------------------------------------------
/examples/using-subscriber/migrations/1721800255_create_topics.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "context"
5 |
6 | "gofr.dev/pkg/gofr/migration"
7 | )
8 |
9 | func createTopics() migration.Migrate {
10 | return migration.Migrate{
11 | UP: func(d migration.Datasource) error {
12 | err := d.PubSub.CreateTopic(context.Background(), "products")
13 | if err != nil {
14 | return err
15 | }
16 |
17 | return d.PubSub.CreateTopic(context.Background(), "order-logs")
18 | },
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/using-subscriber/migrations/all.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "gofr.dev/pkg/gofr/migration"
5 | )
6 |
7 | func All() map[int64]migration.Migrate {
8 | return map[int64]migration.Migrate{
9 | 1721800255: createTopics(),
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/using-subscriber/migrations/all_test.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "gofr.dev/pkg/gofr/migration"
9 | )
10 |
11 | func TestAll(t *testing.T) {
12 | // Get the map of migrations
13 | allMigrations := All()
14 |
15 | expected := map[int64]migration.Migrate{
16 | 1721800255: createTopics(),
17 | }
18 |
19 | // Check if the length of the maps match
20 | assert.Equal(t, len(expected), len(allMigrations), "TestAll Failed!")
21 | }
22 |
--------------------------------------------------------------------------------
/examples/using-subscriber/readme.md:
--------------------------------------------------------------------------------
1 | # Subscriber Example
2 |
3 | This GoFr example demonstrates a simple Subscriber that subscribes asynchronously to a given topic and commits based
4 | on the handler response.
5 |
6 | ### To run the example follow the below steps:
7 |
8 | - Run the docker image of kafka and ensure that your provided topics are created before subscribing.
9 | ```console
10 | docker run --name kafka-1 -p 9092:9092 \
11 | -e KAFKA_ENABLE_KRAFT=yes \
12 | -e KAFKA_CFG_PROCESS_ROLES=broker,controller \
13 | -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
14 | -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
15 | -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
16 | -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 \
17 | -e KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true \
18 | -e KAFKA_BROKER_ID=1 \
19 | -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@127.0.0.1:9093 \
20 | -e ALLOW_PLAINTEXT_LISTENER=yes \
21 | -e KAFKA_CFG_NODE_ID=1 \
22 | -v kafka_data:/bitnami \
23 | bitnami/kafka:3.4
24 | ```
25 |
26 | - Now run the example using below command :
27 | ```console
28 | go run main.go
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/examples/using-web-socket/configs/.env:
--------------------------------------------------------------------------------
1 | APP_NAME=using-web-socket
2 |
3 | HTTP_PORT=8001
--------------------------------------------------------------------------------
/examples/using-web-socket/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gofr.dev/pkg/gofr"
5 | )
6 |
7 | func main() {
8 | app := gofr.New()
9 |
10 | app.WebSocket("/ws", WSHandler)
11 |
12 | app.Run()
13 | }
14 |
15 | func WSHandler(ctx *gofr.Context) (any, error) {
16 | var message string
17 |
18 | err := ctx.Bind(&message)
19 | if err != nil {
20 | ctx.Logger.Errorf("Error binding message: %v", err)
21 | return nil, err
22 | }
23 |
24 | ctx.Logger.Infof("Received message: %s", message)
25 |
26 | err = ctx.WriteMessageToSocket("Hello! GoFr")
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | return message, nil
32 | }
33 |
--------------------------------------------------------------------------------
/examples/using-web-socket/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | "time"
8 |
9 | "github.com/gorilla/websocket"
10 | "github.com/stretchr/testify/assert"
11 |
12 | "gofr.dev/pkg/gofr/testutil"
13 | )
14 |
15 | func TestMain(m *testing.M) {
16 | os.Setenv("GOFR_TELEMETRY", "false")
17 | m.Run()
18 | }
19 |
20 | func Test_WebSocket_Success(t *testing.T) {
21 | configs := testutil.NewServerConfigs(t)
22 | wsURL := fmt.Sprintf("ws://localhost:%d/ws", configs.HTTPPort)
23 |
24 | go main()
25 | time.Sleep(100 * time.Millisecond)
26 |
27 | testMessage := "Hello! GoFr"
28 | dialer := &websocket.Dialer{}
29 |
30 | conn, _, err := dialer.Dial(wsURL, nil)
31 | assert.Nil(t, err, "Error dialing websocket : %v", err)
32 |
33 | defer conn.Close()
34 |
35 | // writing test message to websocket connection
36 | err = conn.WriteMessage(websocket.TextMessage, []byte(testMessage))
37 | assert.Nil(t, err, "Unexpected error while writing message : %v", err)
38 |
39 | // Read response from server
40 | _, message, err := conn.ReadMessage()
41 | assert.Nil(t, err, "Unexpected error while reading message : %v", err)
42 |
43 | assert.Equal(t, string(message), testMessage, "Test_WebSocket_Success Failed!")
44 | }
45 |
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.24.0
2 |
3 | toolchain go1.24.0
4 |
5 | use (
6 | .
7 | ./examples/using-add-filestore
8 | ./pkg/gofr/datasource/arangodb
9 | ./pkg/gofr/datasource/cassandra
10 | ./pkg/gofr/datasource/clickhouse
11 | ./pkg/gofr/datasource/dgraph
12 | ./pkg/gofr/datasource/elasticsearch
13 | ./pkg/gofr/datasource/file/ftp
14 | ./pkg/gofr/datasource/file/s3
15 | ./pkg/gofr/datasource/file/sftp
16 | ./pkg/gofr/datasource/kv-store/badger
17 | ./pkg/gofr/datasource/kv-store/nats
18 | ./pkg/gofr/datasource/mongo
19 | ./pkg/gofr/datasource/opentsdb
20 | ./pkg/gofr/datasource/pubsub/eventhub
21 | ./pkg/gofr/datasource/pubsub/nats
22 | ./pkg/gofr/datasource/scylladb
23 | ./pkg/gofr/datasource/solr
24 | ./pkg/gofr/datasource/surrealdb
25 | )
26 |
--------------------------------------------------------------------------------
/pkg/gofr/cmd/responder.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | )
7 |
8 | type Responder struct{}
9 |
10 | func (*Responder) Respond(data any, err error) {
11 | // TODO - provide proper exit codes here. Using os.Exit directly is a problem for tests.
12 | if data != nil {
13 | fmt.Fprintln(os.Stdout, data)
14 | }
15 |
16 | if err != nil {
17 | fmt.Fprintln(os.Stderr, err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/gofr/cmd/responder_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "gofr.dev/pkg/gofr/testutil"
10 | )
11 |
12 | func TestResponder_Respond(t *testing.T) {
13 | r := Responder{}
14 |
15 | out := testutil.StdoutOutputForFunc(func() {
16 | r.Respond("data", nil)
17 | })
18 |
19 | err := testutil.StderrOutputForFunc(func() {
20 | r.Respond(nil, errors.New("error")) //nolint:err113 // We are testing if a dynamic error would work.
21 | })
22 |
23 | assert.Equal(t, "data\n", out, "TEST Failed.\n", "Responder stdout output")
24 |
25 | assert.Equal(t, "error\n", err, "TEST Failed.\n", "Responder stderr output")
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/gofr/cmd/terminal/colors.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | const (
4 | Black = iota
5 | Red
6 | Green
7 | Yellow
8 | Blue
9 | Magenta
10 | Cyan
11 | White
12 | BrightBlack
13 | BrightRed
14 | BrightGreen
15 | BrightYellow
16 | BrightBlue
17 | BrightMagenta
18 | BrightCyan
19 | BrightWhite
20 | )
21 |
22 | func (o *Out) SetColor(colorCode int) {
23 | o.Printf(csi+"38;5;%d"+"m", colorCode)
24 | }
25 |
26 | func (o *Out) ResetColor() {
27 | o.Print(csi + "0m")
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/gofr/cmd/terminal/printers.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func (o *Out) Printf(format string, args ...any) {
8 | fmt.Fprintf(o.out, format, args...)
9 | }
10 |
11 | func (o *Out) Print(messages ...any) {
12 | fmt.Fprint(o.out, messages...)
13 | }
14 |
15 | func (o *Out) Println(messages ...any) {
16 | fmt.Fprintln(o.out, messages...)
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/gofr/cmd/terminal/printers_test.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestOutput_Printf(t *testing.T) {
12 | var buf bytes.Buffer
13 | output := Out{out: &buf}
14 |
15 | format := "Hello, %s!"
16 | args := "world"
17 | output.Printf(format, args)
18 |
19 | expectedString := fmt.Sprintf(format, args)
20 | assert.Equal(t, expectedString, buf.String(), "Printf: unexpected written string. Expected: %s, got: %s", expectedString, buf.String())
21 | }
22 |
23 | func TestOutput_Print(t *testing.T) {
24 | var buf bytes.Buffer
25 | output := Out{out: &buf}
26 |
27 | message := "Hello, world!"
28 | output.Print(message)
29 |
30 | assert.Equal(t, message, buf.String(), "Printf: unexpected written string. Expected: %s, got: %s", message, buf.String())
31 | }
32 |
33 | func TestOutput_Println(t *testing.T) {
34 | var buf bytes.Buffer
35 | output := Out{out: &buf}
36 |
37 | message := "Hello, world!"
38 | output.Println(message)
39 |
40 | expectedString := message + "\n"
41 | assert.Equal(t, expectedString, buf.String(), "Printf: unexpected written string. Expected: %s, got: %s", expectedString, buf.String())
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/gofr/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Config interface {
4 | Get(string) string
5 | GetOrDefault(string, string) string
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/gofr/config/mock_config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type mockConfig struct {
4 | conf map[string]string
5 | }
6 |
7 | func NewMockConfig(configMap map[string]string) Config {
8 | if configMap == nil {
9 | configMap = make(map[string]string)
10 | }
11 |
12 | // setting telemetry false for running tests
13 | configMap["GOFR_TELEMETRY"] = "false"
14 |
15 | return &mockConfig{
16 | conf: configMap,
17 | }
18 | }
19 |
20 | func (m *mockConfig) Get(s string) string {
21 | return m.conf[s]
22 | }
23 |
24 | func (m *mockConfig) GetOrDefault(s, d string) string {
25 | res, ok := m.conf[s]
26 | if !ok {
27 | res = d
28 | }
29 |
30 | return res
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/config/mock_config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func Test_NewMockConfig(t *testing.T) {
10 | cfg := NewMockConfig(map[string]string{"config": "value"})
11 |
12 | assert.Equal(t, "value", cfg.Get("config"))
13 |
14 | assert.Equal(t, "value1", cfg.GetOrDefault("config1", "value1"))
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/gofr/constants.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import "time"
4 |
5 | const (
6 | defaultPublicStaticDir = "static"
7 | shutDownTimeout = 30 * time.Second
8 | gofrTraceExporter = "gofr"
9 | gofrTracerURL = "https://tracer.gofr.dev"
10 | checkPortTimeout = 2 * time.Second
11 | gofrHost = "https://gofr.dev"
12 | startServerPing = "/api/ping/up"
13 | shutServerPing = "/api/ping/down"
14 | pingTimeout = 5 * time.Second
15 | defaultTelemetry = "true"
16 | )
17 |
--------------------------------------------------------------------------------
/pkg/gofr/container/metrics.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewCounter(name, desc string)
7 | NewUpDownCounter(name, desc string)
8 | NewHistogram(name, desc string, buckets ...float64)
9 | NewGauge(name, desc string)
10 |
11 | IncrementCounter(ctx context.Context, name string, labels ...string)
12 | DeltaUpDownCounter(ctx context.Context, name string, value float64, labels ...string)
13 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
14 | SetGauge(name string, value float64, labels ...string)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/arangodb/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/arangodb
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/arangodb/go-driver/v2 v2.1.3
9 | github.com/stretchr/testify v1.10.0
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
17 | github.com/davecgh/go-spew v1.1.1 // indirect
18 | github.com/dchest/siphash v1.2.3 // indirect
19 | github.com/go-logr/logr v1.4.2 // indirect
20 | github.com/go-logr/stdr v1.2.2 // indirect
21 | github.com/google/uuid v1.6.0 // indirect
22 | github.com/kkdai/maglev v0.2.0 // indirect
23 | github.com/mattn/go-colorable v0.1.13 // indirect
24 | github.com/mattn/go-isatty v0.0.20 // indirect
25 | github.com/pkg/errors v0.9.1 // indirect
26 | github.com/pmezard/go-difflib v1.0.0 // indirect
27 | github.com/rs/zerolog v1.33.0 // indirect
28 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
29 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
30 | golang.org/x/net v0.38.0 // indirect
31 | golang.org/x/sys v0.31.0 // indirect
32 | golang.org/x/text v0.23.0 // indirect
33 | gopkg.in/yaml.v3 v3.0.1 // indirect
34 | )
35 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/arangodb/logger.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type Logger interface {
11 | Debug(args ...any)
12 | Debugf(pattern string, args ...any)
13 | Logf(pattern string, args ...any)
14 | Errorf(pattern string, args ...any)
15 | }
16 |
17 | type QueryLog struct {
18 | Query string `json:"query"`
19 | Duration int64 `json:"duration"`
20 | Database string `json:"database,omitempty"`
21 | Collection string `json:"collection,omitempty"`
22 | Filter any `json:"filter,omitempty"`
23 | ID any `json:"id,omitempty"`
24 | Operation string `json:"operation,omitempty"`
25 | }
26 |
27 | // PrettyPrint formats the QueryLog for output.
28 | func (ql *QueryLog) PrettyPrint(writer io.Writer) {
29 | if ql.Filter == nil {
30 | ql.Filter = ""
31 | }
32 |
33 | if ql.ID == nil {
34 | ql.ID = ""
35 | }
36 |
37 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;206m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s %s\n",
38 | clean(ql.Operation), "ARANGODB", ql.Duration,
39 | clean(strings.Join([]string{ql.Database, ql.Collection, fmt.Sprint(ql.Filter), fmt.Sprint(ql.ID)}, " ")), clean(ql.Query))
40 | }
41 |
42 | func clean(query string) string {
43 | // Replace multiple consecutive whitespace characters with a single space
44 | query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
45 | // Trim leading and trailing whitespace from the string
46 | return strings.TrimSpace(query)
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/arangodb/logger_test.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func Test_PrettyPrint(t *testing.T) {
11 | queryLog := QueryLog{
12 | Query: "",
13 | Duration: 12345,
14 | Database: "test",
15 | Collection: "test",
16 | Filter: true,
17 | ID: "12345",
18 | Operation: "getDocument",
19 | }
20 | expected := "getDocument"
21 |
22 | var buf bytes.Buffer
23 |
24 | queryLog.PrettyPrint(&buf)
25 |
26 | assert.Contains(t, buf.String(), expected)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/arangodb/metrics.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import "context"
4 |
5 | // Metrics defines the interface for capturing metrics.
6 | type Metrics interface {
7 | NewHistogram(name, desc string, buckets ...float64)
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/arangodb/mock_user.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/arangodb/go-driver/v2/arangodb"
7 | "go.uber.org/mock/gomock"
8 | )
9 |
10 | // MockUser implements the complete arangodb.user interface.
11 | type MockUser struct {
12 | ctrl *gomock.Controller
13 | name string
14 | active bool
15 | }
16 |
17 | func NewMockUser(ctrl *gomock.Controller) *MockUser {
18 | return &MockUser{
19 | ctrl: ctrl,
20 | name: "testUser",
21 | active: true,
22 | }
23 | }
24 |
25 | func (m *MockUser) Name() string { return m.name }
26 | func (m *MockUser) IsActive() bool { return m.active }
27 | func (*MockUser) Extra(any) error { return nil }
28 |
29 | func (*MockUser) AccessibleDatabases(context.Context) (map[string]arangodb.Grant, error) {
30 | return nil, nil
31 | }
32 |
33 | func (*MockUser) AccessibleDatabasesFull(context.Context) (map[string]arangodb.DatabasePermissions, error) {
34 | return nil, nil
35 | }
36 |
37 | func (*MockUser) GetDatabaseAccess(context.Context, string) (arangodb.Grant, error) {
38 | return arangodb.GrantNone, nil
39 | }
40 |
41 | func (*MockUser) GetCollectionAccess(context.Context, string, string) (arangodb.Grant, error) {
42 | return arangodb.GrantNone, nil
43 | }
44 |
45 | func (*MockUser) SetDatabaseAccess(context.Context, string, arangodb.Grant) error {
46 | return nil
47 | }
48 |
49 | func (*MockUser) SetCollectionAccess(context.Context, string, string, arangodb.Grant) error {
50 | return nil
51 | }
52 |
53 | func (*MockUser) RemoveDatabaseAccess(context.Context, string) error {
54 | return nil
55 | }
56 |
57 | func (*MockUser) RemoveCollectionAccess(context.Context, string, string) error {
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/errors.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var (
9 | errDestinationIsNotPointer = errors.New("destination is not pointer")
10 | errUnexpectedMap = errors.New("a map was not expected")
11 | errUnsupportedBatchType = errors.New("batch type not supported")
12 | errBatchNotInitialized = errors.New("batch not initialized")
13 | )
14 |
15 | type errUnexpectedPointer struct {
16 | target string
17 | }
18 |
19 | func (d errUnexpectedPointer) Error() string {
20 | return fmt.Sprintf("a pointer to %v was not expected.", d.target)
21 | }
22 |
23 | type errUnexpectedSlice struct {
24 | target string
25 | }
26 |
27 | func (d errUnexpectedSlice) Error() string {
28 | return fmt.Sprintf("a slice of %v was not expected.", d.target)
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/errors_test.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func Test_DestinationIsNotPointer_Error(t *testing.T) {
10 | err := errDestinationIsNotPointer
11 |
12 | require.Equal(t, err, errDestinationIsNotPointer)
13 | }
14 |
15 | func Test_UnexpectedPointer_Error(t *testing.T) {
16 | expected := "a pointer to int was not expected."
17 | err := errUnexpectedPointer{target: "int"}
18 |
19 | require.ErrorContains(t, err, expected)
20 | }
21 |
22 | func Test_UnexpectedSlice_Error(t *testing.T) {
23 | expected := "a slice of int was not expected."
24 | err := errUnexpectedSlice{target: "int"}
25 |
26 | require.ErrorContains(t, err, expected)
27 | }
28 |
29 | func Test_UnexpectedMap_Error(t *testing.T) {
30 | err := errUnexpectedMap
31 |
32 | require.ErrorIs(t, err, errUnexpectedMap)
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/cassandra
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/gocql/gocql v1.7.0
9 | github.com/stretchr/testify v1.10.0
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/go-logr/logr v1.4.2 // indirect
18 | github.com/go-logr/stdr v1.2.2 // indirect
19 | github.com/golang/snappy v0.0.4 // indirect
20 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
21 | github.com/pmezard/go-difflib v1.0.0 // indirect
22 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
23 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
24 | gopkg.in/inf.v0 v0.9.1 // indirect
25 | gopkg.in/yaml.v3 v3.0.1 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/interfaces.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import (
4 | "github.com/gocql/gocql"
5 | )
6 |
7 | //go:generate mockgen -source=interfaces.go -destination=mock_interfaces.go -package=cassandra
8 |
9 | // All interfaces is designed to be mockable for unit testing purposes, allowing you to control the behavior of Cassandra
10 | // interactions during tests.
11 |
12 | // clusterConfig defines methods for interacting with a Cassandra clusterConfig.
13 | type clusterConfig interface {
14 | createSession() (session, error)
15 | }
16 |
17 | // session defines methods for interacting with a Cassandra session.
18 | type session interface {
19 | query(stmt string, values ...any) query
20 | newBatch(batchtype gocql.BatchType) batch
21 | executeBatch(batch batch) error
22 | executeBatchCAS(b batch, dest ...any) (bool, error)
23 | }
24 |
25 | // query defines methods for interacting with a Cassandra query.
26 | type query interface {
27 | exec() error
28 | iter() iterator
29 | mapScanCAS(dest map[string]any) (applied bool, err error)
30 | scanCAS(dest ...any) (applied bool, err error)
31 | }
32 |
33 | // batch defines methods for interacting with a Cassandra batch.
34 | type batch interface {
35 | Query(stmt string, args ...any)
36 | getBatch() *gocql.Batch
37 | }
38 |
39 | // iterator defines methods for interacting with a Cassandra iterator.
40 | type iterator interface {
41 | columns() []gocql.ColumnInfo
42 | scan(dest ...any) bool
43 | numRows() int
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/logger.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type Logger interface {
11 | Debug(args ...any)
12 | Debugf(pattern string, args ...any)
13 | Log(args ...any)
14 | Logf(pattern string, args ...any)
15 | Error(args ...any)
16 | Errorf(pattern string, args ...any)
17 | }
18 |
19 | type QueryLog struct {
20 | Operation string `json:"operation"`
21 | Query string `json:"query"`
22 | Duration int64 `json:"duration"`
23 | Keyspace string `json:"keyspace,omitempty"`
24 | }
25 |
26 | func (ql *QueryLog) PrettyPrint(writer io.Writer) {
27 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;206m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s \u001B[38;5;8m%-32s\u001B[0m\n",
28 | clean(ql.Operation), "CASS", ql.Duration, clean(ql.Keyspace), clean(ql.Query))
29 | }
30 |
31 | // clean takes a string query as input and performs two operations to clean it up:
32 | // 1. It replaces multiple consecutive whitespace characters with a single space.
33 | // 2. It trims leading and trailing whitespace from the string.
34 | // The cleaned-up query string is then returned.
35 | func clean(query string) string {
36 | // Replace multiple consecutive whitespace characters with a single space
37 | query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
38 |
39 | // Trim leading and trailing whitespace from the string
40 | query = strings.TrimSpace(query)
41 |
42 | return query
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/logger_test.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func Test_PrettyPrint(t *testing.T) {
11 | queryLog := QueryLog{
12 | Query: "sample query",
13 | Duration: 12345,
14 | }
15 | expected := "sample query"
16 |
17 | var buf bytes.Buffer
18 |
19 | queryLog.PrettyPrint(&buf)
20 |
21 | assert.Contains(t, buf.String(), expected)
22 | }
23 |
24 | func Test_Clean(t *testing.T) {
25 | testCases := []struct {
26 | desc string
27 | input string
28 | expected string
29 | }{
30 | {"multiple spaces", " multiple spaces ", "multiple spaces"},
31 | {"leading and trailing", "leading and trailing ", "leading and trailing"},
32 | {"mixed white spaces", " mixed\twhite\nspaces", "mixed white spaces"},
33 | {"single word", "singleword", "singleword"},
34 | {"empty string", "", ""},
35 | {"empty string with spaces", " ", ""},
36 | }
37 |
38 | for i, tc := range testCases {
39 | t.Run(tc.input, func(t *testing.T) {
40 | result := clean(tc.input)
41 | assert.Equal(t, tc.expected, result, "TEST[%d], Failed.\n%s", i, tc.desc)
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/cassandra/metrics.go:
--------------------------------------------------------------------------------
1 | package cassandra
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 |
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/clickhouse/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/clickhouse
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.0
6 |
7 | require (
8 | github.com/ClickHouse/clickhouse-go/v2 v2.35.0
9 | github.com/stretchr/testify v1.10.0
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/ClickHouse/ch-go v0.66.0 // indirect
17 | github.com/andybalholm/brotli v1.1.1 // indirect
18 | github.com/davecgh/go-spew v1.1.1 // indirect
19 | github.com/go-faster/city v1.0.1 // indirect
20 | github.com/go-faster/errors v0.7.1 // indirect
21 | github.com/google/uuid v1.6.0 // indirect
22 | github.com/klauspost/compress v1.18.0 // indirect
23 | github.com/kr/pretty v0.3.1 // indirect
24 | github.com/paulmach/orb v0.11.1 // indirect
25 | github.com/pierrec/lz4/v4 v4.1.22 // indirect
26 | github.com/pmezard/go-difflib v1.0.0 // indirect
27 | github.com/rogpeppe/go-internal v1.13.1 // indirect
28 | github.com/segmentio/asm v1.2.0 // indirect
29 | github.com/shopspring/decimal v1.4.0 // indirect
30 | golang.org/x/sys v0.33.0 // indirect
31 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
32 | gopkg.in/yaml.v3 v3.0.1 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/clickhouse/logger.go:
--------------------------------------------------------------------------------
1 | package clickhouse
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type Logger interface {
11 | Debugf(pattern string, args ...any)
12 | Debug(args ...any)
13 | Logf(pattern string, args ...any)
14 | Errorf(pattern string, args ...any)
15 | }
16 |
17 | type Log struct {
18 | Type string `json:"type"`
19 | Query string `json:"query"`
20 | Duration int64 `json:"duration"`
21 | Args []any `json:"args,omitempty"`
22 | }
23 |
24 | func (l *Log) PrettyPrint(writer io.Writer) {
25 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;24m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s\n",
26 | l.Type, "CHDB", l.Duration, clean(l.Query))
27 | }
28 |
29 | // clean takes a string query as input and performs two operations to clean it up:
30 | // 1. It replaces multiple consecutive whitespace characters with a single space.
31 | // 2. It trims leading and trailing whitespace from the string.
32 | // The cleaned-up query string is then returned.
33 | func clean(query string) string {
34 | // Replace multiple consecutive whitespace characters with a single space
35 | query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
36 |
37 | // Trim leading and trailing whitespace from the string
38 | query = strings.TrimSpace(query)
39 |
40 | return query
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/clickhouse/logger_test.go:
--------------------------------------------------------------------------------
1 | package clickhouse
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestLoggingDataPresent(t *testing.T) {
11 | queryLog := Log{
12 | Type: "SELECT",
13 | Query: "SELECT * FROM users",
14 | Duration: 12345,
15 | }
16 | expected := "SELECT"
17 |
18 | var buf bytes.Buffer
19 |
20 | queryLog.PrettyPrint(&buf)
21 |
22 | assert.Contains(t, buf.String(), expected)
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/clickhouse/metrics.go:
--------------------------------------------------------------------------------
1 | package clickhouse
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 | NewGauge(name, desc string)
8 |
9 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
10 | SetGauge(name string, value float64, labels ...string)
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/datasource.go:
--------------------------------------------------------------------------------
1 | package datasource
2 |
3 | import "gofr.dev/pkg/gofr/config"
4 |
5 | type Datasource interface {
6 | Register(config config.Config)
7 | }
8 |
9 | // Question is: is container aware exactly "Redis" is there or some opaque datasource. in the later case, how do we
10 | // retrieve from context?
11 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/dgraph/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/dgraph
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d
9 | github.com/prometheus/client_golang v1.22.0
10 | github.com/stretchr/testify v1.10.0
11 | go.opentelemetry.io/otel v1.36.0
12 | go.opentelemetry.io/otel/trace v1.36.0
13 | go.uber.org/mock v0.5.2
14 | google.golang.org/grpc v1.72.2
15 | )
16 |
17 | require (
18 | github.com/beorn7/perks v1.0.1 // indirect
19 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
20 | github.com/davecgh/go-spew v1.1.1 // indirect
21 | github.com/go-logr/logr v1.4.2 // indirect
22 | github.com/go-logr/stdr v1.2.2 // indirect
23 | github.com/gogo/protobuf v1.3.2 // indirect
24 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
25 | github.com/pkg/errors v0.9.1 // indirect
26 | github.com/pmezard/go-difflib v1.0.0 // indirect
27 | github.com/prometheus/client_model v0.6.1 // indirect
28 | github.com/prometheus/common v0.62.0 // indirect
29 | github.com/prometheus/procfs v0.15.1 // indirect
30 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
31 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
32 | golang.org/x/net v0.38.0 // indirect
33 | golang.org/x/sys v0.31.0 // indirect
34 | golang.org/x/text v0.23.0 // indirect
35 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
36 | google.golang.org/protobuf v1.36.5 // indirect
37 | gopkg.in/yaml.v3 v3.0.1 // indirect
38 | )
39 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/dgraph/logger.go:
--------------------------------------------------------------------------------
1 | package dgraph
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | var whitespaceRegex = regexp.MustCompile(`\s+`)
10 |
11 | // Logger interface with required methods.
12 | type Logger interface {
13 | Debug(args ...any)
14 | Debugf(pattern string, args ...any)
15 | Log(args ...any)
16 | Logf(pattern string, args ...any)
17 | Error(args ...any)
18 | Errorf(pattern string, args ...any)
19 | }
20 |
21 | // QueryLog represents the structure for query logging.
22 | type QueryLog struct {
23 | Type string `json:"type"`
24 | URL string `json:"url"`
25 | Duration int64 `json:"duration"` // Duration in microseconds
26 | }
27 |
28 | // PrettyPrint logs the QueryLog in a structured format to the given writer.
29 | func (ql *QueryLog) PrettyPrint(logger Logger) {
30 | // Format the log string
31 | formattedLog := fmt.Sprintf(
32 | "\u001B[38;5;8m%-32s \u001B[38;5;206m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s",
33 | clean(ql.Type), "DGRAPH", ql.Duration, clean(ql.URL),
34 | )
35 |
36 | // Log the formatted string using the logger
37 | logger.Debug(formattedLog)
38 | }
39 |
40 | // clean replaces multiple consecutive whitespace characters with a single space and trims leading/trailing whitespace.
41 | func clean(query string) string {
42 | query = whitespaceRegex.ReplaceAllString(query, " ")
43 | return strings.TrimSpace(query)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/dgraph/logger_test.go:
--------------------------------------------------------------------------------
1 | package dgraph
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | "go.uber.org/mock/gomock"
8 | )
9 |
10 | func Test_PrettyPrint(t *testing.T) {
11 | queryLog := QueryLog{
12 | Type: "GET",
13 | Duration: 12345,
14 | }
15 |
16 | logger := NewMockLogger(gomock.NewController(t))
17 |
18 | logger.EXPECT().Debug(gomock.Any())
19 |
20 | queryLog.PrettyPrint(logger)
21 |
22 | require.True(t, logger.ctrl.Satisfied(), "Test_PrettyPrint Failed!")
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/dgraph/metrics.go:
--------------------------------------------------------------------------------
1 | package dgraph
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/prometheus/client_golang/prometheus"
7 | )
8 |
9 | type Metrics interface {
10 | NewHistogram(name, desc string, buckets ...float64)
11 |
12 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
13 | }
14 |
15 | type PrometheusMetrics struct {
16 | histograms map[string]*prometheus.HistogramVec
17 | }
18 |
19 | // NewHistogram creates a new histogram metric with the given name, description, and optional bucket sizes.
20 | func (p *PrometheusMetrics) NewHistogram(name, desc string, buckets ...float64) {
21 | histogram := prometheus.NewHistogramVec(
22 | prometheus.HistogramOpts{
23 | Name: name,
24 | Help: desc,
25 | Buckets: buckets,
26 | },
27 | []string{}, // labels can be added here if needed
28 | )
29 | p.histograms[name] = histogram
30 | prometheus.MustRegister(histogram)
31 | }
32 |
33 | // RecordHistogram records a value to the specified histogram metric with optional labels.
34 | func (p *PrometheusMetrics) RecordHistogram(_ context.Context, name string, value float64, labels ...string) {
35 | histogram, exists := p.histograms[name]
36 | if !exists {
37 | // Handle error: histogram not found
38 | return
39 | }
40 |
41 | histogram.WithLabelValues(labels...).Observe(value)
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/elasticsearch/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/elasticsearch
2 |
3 | go 1.24.0
4 |
5 | require (
6 | github.com/elastic/go-elasticsearch/v8 v8.18.0
7 | github.com/stretchr/testify v1.10.0
8 | go.opentelemetry.io/otel v1.36.0
9 | go.opentelemetry.io/otel/trace v1.36.0
10 | go.uber.org/mock v0.5.2
11 | )
12 |
13 | require (
14 | github.com/davecgh/go-spew v1.1.1 // indirect
15 | github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
16 | github.com/go-logr/logr v1.4.2 // indirect
17 | github.com/go-logr/stdr v1.2.2 // indirect
18 | github.com/pmezard/go-difflib v1.0.0 // indirect
19 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
20 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
21 | gopkg.in/yaml.v3 v3.0.1 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/elasticsearch/metrics.go:
--------------------------------------------------------------------------------
1 | package elasticsearch
2 |
3 | import "context"
4 |
5 | // Metrics defines the interface for capturing metrics.
6 | type Metrics interface {
7 | NewHistogram(name, desc string, buckets ...float64)
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/errors.go:
--------------------------------------------------------------------------------
1 | package datasource
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/pkg/errors"
7 | )
8 |
9 | // ErrorDB represents an error specific to database operations.
10 | type ErrorDB struct {
11 | Err error
12 | Message string
13 | }
14 |
15 | func (e ErrorDB) Error() string {
16 | switch {
17 | case e.Message == "":
18 | return e.Err.Error()
19 | case e.Err == nil:
20 | return e.Message
21 | default:
22 | return errors.Wrap(e.Err, e.Message).Error()
23 | }
24 | }
25 |
26 | // WithStack adds a stack trace to the Error.
27 | func (e ErrorDB) WithStack() ErrorDB {
28 | e.Err = errors.WithStack(e.Err)
29 | return e
30 | }
31 |
32 | func (ErrorDB) StatusCode() int {
33 | return http.StatusInternalServerError
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/errors_test.go:
--------------------------------------------------------------------------------
1 | package datasource
2 |
3 | import (
4 | "net/http"
5 | "os"
6 | "testing"
7 |
8 | "github.com/pkg/errors"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestMain(m *testing.M) {
14 | os.Setenv("GOFR_TELEMETRY", "false")
15 | m.Run()
16 | }
17 |
18 | func Test_ErrorDB(t *testing.T) {
19 | wrappedErr := errors.New("underlying error")
20 |
21 | tests := []struct {
22 | desc string
23 | err ErrorDB
24 | expectedMsg string
25 | }{
26 | {"wrapped error", ErrorDB{Err: wrappedErr, Message: "custom message"}.WithStack(), "custom message: underlying error"},
27 | {"without wrapped error", ErrorDB{Message: "custom message"}, "custom message"},
28 | {"no custom error message", ErrorDB{Err: wrappedErr}, "underlying error"},
29 | }
30 |
31 | for i, tc := range tests {
32 | require.ErrorContains(t, tc.err, tc.expectedMsg, "TEST[%d], Failed.\n%s", i, tc.desc)
33 | }
34 | }
35 |
36 | func TestErrorDB_StatusCode(t *testing.T) {
37 | dbErr := ErrorDB{Message: "custom message"}
38 |
39 | expectedCode := http.StatusInternalServerError
40 |
41 | assert.Equal(t, expectedCode, dbErr.StatusCode(), "TEST Failed.\n")
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/ftp/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/file/ftp
2 |
3 | go 1.24
4 |
5 | require (
6 | github.com/jlaffaye/ftp v0.2.0
7 | github.com/stretchr/testify v1.10.0
8 | go.uber.org/mock v0.5.2
9 | gofr.dev v1.40.0
10 | )
11 |
12 | require (
13 | github.com/davecgh/go-spew v1.1.1 // indirect
14 | github.com/hashicorp/errwrap v1.1.0 // indirect
15 | github.com/hashicorp/go-multierror v1.1.1 // indirect
16 | github.com/joho/godotenv v1.5.1 // indirect
17 | github.com/kr/pretty v0.3.1 // indirect
18 | github.com/pkg/errors v0.9.1 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | github.com/rogpeppe/go-internal v1.13.1 // indirect
21 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
22 | gopkg.in/yaml.v3 v3.0.1 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/ftp/interface.go:
--------------------------------------------------------------------------------
1 | package ftp
2 |
3 | import (
4 | "context"
5 | "io"
6 | "time"
7 |
8 | "github.com/jlaffaye/ftp"
9 | )
10 |
11 | // Logger interface is used by ftp package to log information about query execution.
12 | type Logger interface {
13 | Debug(args ...any)
14 | Debugf(pattern string, args ...any)
15 | Logf(pattern string, args ...any)
16 | Errorf(pattern string, args ...any)
17 | }
18 |
19 | type Metrics interface {
20 | NewHistogram(name, desc string, buckets ...float64)
21 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
22 | }
23 |
24 | // serverConn represents a connection to an FTP server.
25 | type serverConn interface {
26 | Login(string, string) error
27 | Retr(string) (ftpResponse, error)
28 | RetrFrom(string, uint64) (ftpResponse, error)
29 | Stor(string, io.Reader) error
30 | StorFrom(string, io.Reader, uint64) error
31 | Rename(string, string) error
32 | Delete(string) error
33 | RemoveDirRecur(path string) error
34 | MakeDir(path string) error
35 | RemoveDir(path string) error
36 | Quit() error
37 | FileSize(name string) (int64, error)
38 | CurrentDir() (string, error)
39 | ChangeDir(path string) error
40 | List(string) ([]*ftp.Entry, error)
41 | GetTime(path string) (time.Time, error)
42 | }
43 |
44 | // ftpResponse interface mimics the behavior of *ftp.Response returned on retrieval of file from FTP.
45 | type ftpResponse interface {
46 | Read(buf []byte) (int, error)
47 | Close() error
48 | SetDeadline(t time.Time) error
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/ftp/logger.go:
--------------------------------------------------------------------------------
1 | package ftp
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | // FileLog handles logging with different levels.
11 | type FileLog struct {
12 | Operation string `json:"operation"`
13 | Duration int64 `json:"duration"`
14 | Status *string `json:"status"`
15 | Location string `json:"location,omitempty"`
16 | Message *string `json:"message,omitempty"`
17 | }
18 |
19 | var regexpSpaces = regexp.MustCompile(`\s+`)
20 |
21 | func clean(query *string) string {
22 | if query == nil {
23 | return ""
24 | }
25 |
26 | return strings.TrimSpace(regexpSpaces.ReplaceAllString(*query, " "))
27 | }
28 |
29 | func (fl *FileLog) PrettyPrint(writer io.Writer) {
30 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;148m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %-10s \u001B[0m %-48s \n",
31 | clean(&fl.Operation), "FTP", fl.Duration, clean(fl.Status), clean(fl.Message))
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/ftp/logger_test.go:
--------------------------------------------------------------------------------
1 | package ftp
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestFileLogPrettyPrint(t *testing.T) {
11 | msg := "File Created successfully"
12 |
13 | fileLog := FileLog{
14 | Operation: "Create file",
15 | Duration: 1234,
16 | Location: "/ftp/one",
17 | Message: &msg,
18 | }
19 |
20 | expected := "Create file"
21 |
22 | expectedMsg := "File Created successfully"
23 |
24 | var buf bytes.Buffer
25 |
26 | fileLog.PrettyPrint(&buf)
27 |
28 | assert.Contains(t, buf.String(), expected)
29 | assert.Contains(t, buf.String(), expectedMsg)
30 | }
31 |
32 | func TestFileLogPrettyPrintWhitespaceHandling(t *testing.T) {
33 | msg := " File creation complete "
34 | fileLog := FileLog{
35 | Operation: " Create file ",
36 | Duration: 5678,
37 | Message: &msg,
38 | }
39 | expected := "Create file"
40 | expectedMsg := "File creation complete"
41 |
42 | var buf bytes.Buffer
43 |
44 | fileLog.PrettyPrint(&buf)
45 |
46 | assert.Contains(t, buf.String(), expected)
47 | assert.Contains(t, buf.String(), expectedMsg)
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/s3/interface.go:
--------------------------------------------------------------------------------
1 | package s3
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/aws/aws-sdk-go-v2/service/s3"
7 | )
8 |
9 | // Logger interface is used by s3 package to log information about query execution.
10 | type Logger interface {
11 | Debug(args ...any)
12 | Debugf(pattern string, args ...any)
13 | Logf(pattern string, args ...any)
14 | Errorf(pattern string, args ...any)
15 | }
16 |
17 | // s3Client defines the interface that is used for gofr-s3 datasource as well as mocks to
18 | // streamline the testing as well as the implementation process.
19 | type s3Client interface {
20 | ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
21 | PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
22 | GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
23 | DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
24 | DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error)
25 | CopyObject(ctx context.Context, params *s3.CopyObjectInput, optFns ...func(*s3.Options)) (*s3.CopyObjectOutput, error)
26 | }
27 |
28 | type Metrics interface {
29 | NewHistogram(name, desc string, buckets ...float64)
30 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/s3/logger.go:
--------------------------------------------------------------------------------
1 | package s3
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | // FileLog handles logging with different levels.
11 | // In DEBUG MODE, this FileLog can be exported into a file while
12 | // running the application or can be logged in the terminal.
13 | type FileLog struct {
14 | Operation string `json:"operation"`
15 | Duration int64 `json:"duration"`
16 | Status *string `json:"status"`
17 | Location string `json:"location,omitempty"`
18 | Message *string `json:"message,omitempty"`
19 | }
20 |
21 | var regexpSpaces = regexp.MustCompile(`\s+`)
22 |
23 | func clean(query *string) string {
24 | if query == nil {
25 | return ""
26 | }
27 |
28 | return strings.TrimSpace(regexpSpaces.ReplaceAllString(*query, " "))
29 | }
30 |
31 | func (fl *FileLog) PrettyPrint(writer io.Writer) {
32 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;148m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %-10s \u001B[0m %-48s \n",
33 | clean(&fl.Operation), "AWS_S3", fl.Duration, clean(fl.Status), clean(fl.Message))
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/s3/logger_test.go:
--------------------------------------------------------------------------------
1 | package s3
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestFileLogPrettyPrint(t *testing.T) {
11 | msg := "File Created successfully"
12 |
13 | fileLog := FileLog{
14 | Operation: "Create file",
15 | Duration: 1234,
16 | Location: "/ftp/one",
17 | Message: &msg,
18 | }
19 |
20 | expected := "Create file"
21 |
22 | expectedMsg := "File Created successfully"
23 |
24 | var buf bytes.Buffer
25 |
26 | fileLog.PrettyPrint(&buf)
27 |
28 | assert.Contains(t, buf.String(), expected)
29 | assert.Contains(t, buf.String(), expectedMsg)
30 | }
31 |
32 | func TestFileLogPrettyPrintWhitespaceHandling(t *testing.T) {
33 | msg := " File creation complete "
34 | fileLog := FileLog{
35 | Operation: " Create file ",
36 | Duration: 5678,
37 | Message: &msg,
38 | }
39 | expected := "Create file"
40 | expectedMsg := "File creation complete"
41 |
42 | var buf bytes.Buffer
43 |
44 | fileLog.PrettyPrint(&buf)
45 |
46 | assert.Contains(t, buf.String(), expected)
47 | assert.Contains(t, buf.String(), expectedMsg)
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/sftp/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/file/sftp
2 |
3 | go 1.24
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/pkg/sftp v1.13.6
9 | github.com/stretchr/testify v1.10.0
10 | go.uber.org/mock v0.5.2
11 | gofr.dev v1.40.0
12 | golang.org/x/crypto v0.38.0
13 | )
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/joho/godotenv v1.5.1 // indirect
18 | github.com/kr/fs v0.1.0 // indirect
19 | github.com/kr/pretty v0.3.1 // indirect
20 | github.com/pkg/errors v0.9.1 // indirect
21 | github.com/pmezard/go-difflib v1.0.0 // indirect
22 | github.com/rogpeppe/go-internal v1.13.1 // indirect
23 | golang.org/x/sys v0.33.0 // indirect
24 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
25 | gopkg.in/yaml.v3 v3.0.1 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/sftp/interface.go:
--------------------------------------------------------------------------------
1 | package sftp
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/pkg/sftp"
8 | )
9 |
10 | // Logger interface is used by ftp package to log information about query execution.
11 | type Logger interface {
12 | Debug(args ...any)
13 | Debugf(pattern string, args ...any)
14 | Logf(pattern string, args ...any)
15 | Errorf(pattern string, args ...any)
16 | }
17 |
18 | type Metrics interface {
19 | NewHistogram(name, desc string, buckets ...float64)
20 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
21 | }
22 |
23 | type sftpClient interface {
24 | Create(path string) (*sftp.File, error)
25 | Mkdir(path string) error
26 | MkdirAll(path string) error
27 | Open(path string) (*sftp.File, error)
28 | OpenFile(path string, f int) (*sftp.File, error)
29 | Remove(path string) error
30 | RemoveAll(path string) error
31 | Rename(oldname, newname string) error
32 | ReadDir(p string) ([]os.FileInfo, error)
33 | Stat(p string) (os.FileInfo, error)
34 | Getwd() (string, error)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/sftp/logger.go:
--------------------------------------------------------------------------------
1 | package sftp
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | // FileLog handles logging with different levels.
11 | type FileLog struct {
12 | Operation string `json:"operation"`
13 | Duration int64 `json:"duration"`
14 | Status *string `json:"status"`
15 | Location string `json:"location,omitempty"`
16 | }
17 |
18 | var regexpSpaces = regexp.MustCompile(`\s+`)
19 |
20 | func clean(query *string) string {
21 | if query == nil {
22 | return ""
23 | }
24 |
25 | return strings.TrimSpace(regexpSpaces.ReplaceAllString(*query, " "))
26 | }
27 |
28 | func (fl *FileLog) PrettyPrint(writer io.Writer) {
29 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;148m%-6s\u001B[0m %8d\u001B[38;5;8mµs \u001B[0m %-48s \n",
30 | clean(&fl.Operation)+" "+clean(fl.Status), "SFTP", fl.Duration, fl.Location)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/file/sftp/logger_test.go:
--------------------------------------------------------------------------------
1 | package sftp
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestFileLogPrettyPrint(t *testing.T) {
11 | fileLog := FileLog{
12 | Operation: "Create file",
13 | Duration: 1234,
14 | Location: "/ftp/one",
15 | }
16 |
17 | expected := "Create file"
18 |
19 | var buf bytes.Buffer
20 |
21 | fileLog.PrettyPrint(&buf)
22 |
23 | assert.Contains(t, buf.String(), expected)
24 | }
25 |
26 | func TestFileLogPrettyPrintWhitespaceHandling(t *testing.T) {
27 | fileLog := FileLog{
28 | Operation: " Create file ",
29 | Duration: 5678,
30 | }
31 |
32 | expectedMsg := "Create file"
33 |
34 | var buf bytes.Buffer
35 |
36 | fileLog.PrettyPrint(&buf)
37 |
38 | assert.Contains(t, buf.String(), expectedMsg)
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/health.go:
--------------------------------------------------------------------------------
1 | package datasource
2 |
3 | const (
4 | StatusUp = "UP"
5 | StatusDown = "DOWN"
6 | )
7 |
8 | type Health struct {
9 | Status string `json:"status,omitempty"`
10 | Details map[string]any `json:"details,omitempty"`
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/badger/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/kv-store/badger
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/dgraph-io/badger/v4 v4.5.0
9 | github.com/stretchr/testify v1.10.0
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
17 | github.com/davecgh/go-spew v1.1.1 // indirect
18 | github.com/dgraph-io/ristretto/v2 v2.0.1 // indirect
19 | github.com/dustin/go-humanize v1.0.1 // indirect
20 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
21 | github.com/google/flatbuffers v24.12.23+incompatible // indirect
22 | github.com/klauspost/compress v1.17.11 // indirect
23 | github.com/kr/pretty v0.3.1 // indirect
24 | github.com/pkg/errors v0.9.1 // indirect
25 | github.com/pmezard/go-difflib v1.0.0 // indirect
26 | github.com/rogpeppe/go-internal v1.13.1 // indirect
27 | go.opencensus.io v0.24.0 // indirect
28 | golang.org/x/net v0.38.0 // indirect
29 | golang.org/x/sys v0.31.0 // indirect
30 | google.golang.org/protobuf v1.36.3 // indirect
31 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
32 | gopkg.in/yaml.v3 v3.0.1 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/badger/logger.go:
--------------------------------------------------------------------------------
1 | package badger
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | type Logger interface {
9 | Debug(args ...any)
10 | Debugf(pattern string, args ...any)
11 | Info(args ...any)
12 | Infof(pattern string, args ...any)
13 | Error(args ...any)
14 | Errorf(pattern string, args ...any)
15 | }
16 |
17 | type Log struct {
18 | Type string `json:"type"`
19 | Duration int64 `json:"duration"`
20 | Key string `json:"key"`
21 | Value string `json:"value,omitempty"`
22 | }
23 |
24 | func (l *Log) PrettyPrint(writer io.Writer) {
25 | 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",
26 | l.Type, "BADGR", l.Duration, l.Key+" "+l.Value)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/badger/logger_test.go:
--------------------------------------------------------------------------------
1 | package badger
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func Test_PrettyPrint(t *testing.T) {
11 | queryLog := Log{
12 | Type: "GET",
13 | Duration: 12345,
14 | }
15 | expected := "GET"
16 |
17 | var buf bytes.Buffer
18 |
19 | queryLog.PrettyPrint(&buf)
20 |
21 | assert.Contains(t, buf.String(), expected)
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/badger/metrics.go:
--------------------------------------------------------------------------------
1 | package badger
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 |
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/nats/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/kv-store/nats
2 |
3 | go 1.23.4
4 |
5 | require (
6 | github.com/nats-io/nats.go v1.38.0
7 | github.com/stretchr/testify v1.10.0
8 | go.opentelemetry.io/otel v1.36.0
9 | go.opentelemetry.io/otel/trace v1.36.0
10 | go.uber.org/mock v0.5.2
11 | )
12 |
13 | require (
14 | github.com/davecgh/go-spew v1.1.1 // indirect
15 | github.com/klauspost/compress v1.17.9 // indirect
16 | github.com/kr/pretty v0.3.1 // indirect
17 | github.com/nats-io/nkeys v0.4.9 // indirect
18 | github.com/nats-io/nuid v1.0.1 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | github.com/rogpeppe/go-internal v1.13.1 // indirect
21 | golang.org/x/crypto v0.35.0 // indirect
22 | golang.org/x/sys v0.30.0 // indirect
23 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
24 | gopkg.in/yaml.v3 v3.0.1 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/nats/logger.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | const (
9 | uuidLength = 36
10 | )
11 |
12 | type Logger interface {
13 | Debug(args ...any)
14 | Debugf(pattern string, args ...any)
15 | Info(args ...any)
16 | Infof(pattern string, args ...any)
17 | Error(args ...any)
18 | Errorf(pattern string, args ...any)
19 | }
20 |
21 | type Log struct {
22 | Type string `json:"type"`
23 | Duration int64 `json:"duration"`
24 | Key string `json:"key"`
25 | Value string `json:"value,omitempty"`
26 | }
27 |
28 | func (l *Log) PrettyPrint(writer io.Writer) {
29 | var description string
30 |
31 | switch l.Type {
32 | case "GET":
33 | description = fmt.Sprintf("Fetching record from bucket '%s' with ID '%s'", l.Value, l.Key)
34 | case "SET":
35 | if len(l.Key) == uuidLength {
36 | description = fmt.Sprintf("Creating new record in bucket '%s' with ID '%s'", l.Value, l.Key)
37 | } else {
38 | description = fmt.Sprintf("Updating record with ID '%s' in bucket '%s'", l.Key, l.Value)
39 | }
40 | case "DELETE":
41 | description = fmt.Sprintf("Deleting record from bucket '%s' with ID '%s'", l.Value, l.Key)
42 | }
43 |
44 | fmt.Fprintf(writer, "%-32s \u001B[38;5;162mNATS\u001B[0m %8dμs \u001B[38;5;8m%s\u001B[0m\n",
45 | l.Type,
46 | l.Duration,
47 | description)
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/kv-store/nats/metrics.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 |
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/logger.go:
--------------------------------------------------------------------------------
1 | package datasource
2 |
3 | // Logger interface is used by datasource packages to log information about query execution.
4 | // Developer Notes: Note that it's a reduced version of logging.Logger interface. We are not using that package to
5 | // ensure that datasource package is not dependent on logging package. That way logging package should be easily able
6 | // to import datasource package and provide a different "pretty" version for different log types defined here while
7 | // avoiding the cyclical import issue. Idiomatically, interfaces should be defined by packages who are using it; unlike
8 | // other languages. Also - accept interfaces, return concrete types.
9 | type Logger interface {
10 | Debug(args ...any)
11 | Debugf(format string, args ...any)
12 | Info(args ...any)
13 | Infof(format string, args ...any)
14 | Error(args ...any)
15 | Errorf(format string, args ...any)
16 | Warn(args ...any)
17 | Warnf(format string, args ...any)
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/mongo/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/mongo
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/stretchr/testify v1.10.0
9 | go.mongodb.org/mongo-driver v1.17.3
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/go-logr/logr v1.4.2 // indirect
18 | github.com/go-logr/stdr v1.2.2 // indirect
19 | github.com/golang/snappy v0.0.4 // indirect
20 | github.com/klauspost/compress v1.17.11 // indirect
21 | github.com/montanaflynn/stats v0.7.1 // indirect
22 | github.com/pmezard/go-difflib v1.0.0 // indirect
23 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
24 | github.com/xdg-go/scram v1.1.2 // indirect
25 | github.com/xdg-go/stringprep v1.0.4 // indirect
26 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
27 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
28 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
29 | golang.org/x/crypto v0.35.0 // indirect
30 | golang.org/x/sync v0.11.0 // indirect
31 | golang.org/x/text v0.22.0 // indirect
32 | gopkg.in/yaml.v3 v3.0.1 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/mongo/logger_test.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestLoggingDataPresent(t *testing.T) {
11 | queryLog := QueryLog{
12 | Query: "find",
13 | Duration: 12345,
14 | Collection: "users",
15 | Filter: map[string]string{"name": "John"},
16 | ID: "123",
17 | Update: map[string]string{"$set": "Doe"},
18 | }
19 | expected := "name:John"
20 |
21 | var buf bytes.Buffer
22 |
23 | queryLog.PrettyPrint(&buf)
24 |
25 | assert.Contains(t, buf.String(), expected)
26 | }
27 |
28 | func TestLoggingEmptyData(t *testing.T) {
29 | queryLog := QueryLog{
30 | Query: "insert",
31 | Duration: 6789,
32 | }
33 | expected := "name:John"
34 |
35 | var buf bytes.Buffer
36 |
37 | queryLog.PrettyPrint(&buf)
38 |
39 | assert.NotContains(t, buf.String(), expected)
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/mongo/metrics.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 |
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/opentsdb/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/opentsdb
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/stretchr/testify v1.10.0
9 | go.opentelemetry.io/otel v1.36.0
10 | go.opentelemetry.io/otel/trace v1.36.0
11 | go.uber.org/mock v0.5.2
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/go-logr/logr v1.4.2 // indirect
17 | github.com/go-logr/stdr v1.2.2 // indirect
18 | github.com/pmezard/go-difflib v1.0.0 // indirect
19 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
20 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
21 | gopkg.in/yaml.v3 v3.0.1 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/eventhub/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/pubsub/eventhub
2 |
3 | go 1.24
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.2.3
9 | github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0
10 | github.com/stretchr/testify v1.10.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | gofr.dev v1.40.0
14 | nhooyr.io/websocket v1.8.11
15 | )
16 |
17 | require (
18 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
19 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
20 | github.com/Azure/go-amqp v1.3.0 // indirect
21 | github.com/davecgh/go-spew v1.1.1 // indirect
22 | github.com/joho/godotenv v1.5.1 // indirect
23 | github.com/pkg/errors v0.9.1 // indirect
24 | github.com/pmezard/go-difflib v1.0.0 // indirect
25 | github.com/rogpeppe/go-internal v1.13.1 // indirect
26 | go.opentelemetry.io/otel v1.36.0 // indirect
27 | golang.org/x/net v0.40.0 // indirect
28 | golang.org/x/text v0.25.0 // indirect
29 | gopkg.in/yaml.v3 v3.0.1 // indirect
30 | )
31 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/eventhub/logger.go:
--------------------------------------------------------------------------------
1 | package eventhub
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | // Logger interface with required methods.
9 | type Logger interface {
10 | Debug(args ...any)
11 | Debugf(pattern string, args ...any)
12 | Log(args ...any)
13 | Logf(pattern string, args ...any)
14 | Error(args ...any)
15 | Fatal(args ...any)
16 | Errorf(pattern string, args ...any)
17 | }
18 |
19 | type Log struct {
20 | Mode string `json:"mode"`
21 | MessageValue string `json:"messageValue"`
22 | Topic string `json:"topic"`
23 | Host string `json:"host"`
24 | PubSubBackend string `json:"pubSubBackend"`
25 | Time int64 `json:"time"`
26 | }
27 |
28 | func (l *Log) PrettyPrint(writer io.Writer) {
29 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;24m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %-4s%s \u001b[38;5;101m\n",
30 | l.Topic, l.PubSubBackend, l.Time, l.Mode, l.MessageValue)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/eventhub/logger_test.go:
--------------------------------------------------------------------------------
1 | package eventhub
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | "go.uber.org/mock/gomock"
9 | )
10 |
11 | func Test_PrettyPrint(t *testing.T) {
12 | queryLog := Log{
13 | Mode: "PUB",
14 | MessageValue: `{"myorder":"1"}`,
15 | Topic: "test-topic",
16 | Host: "localhost",
17 | PubSubBackend: "AZHUB",
18 | Time: 10,
19 | }
20 |
21 | logger := NewMockLogger(gomock.NewController(t))
22 |
23 | logger.EXPECT().Log(gomock.Any())
24 |
25 | logger.Log(queryLog)
26 |
27 | b := make([]byte, 100)
28 |
29 | writer := bytes.NewBuffer(b)
30 |
31 | queryLog.PrettyPrint(writer)
32 |
33 | require.Contains(t, writer.String(), "test-topic")
34 | require.Contains(t, writer.String(), "AZHUB")
35 | require.Contains(t, writer.String(), `{"myorder":"1"}`)
36 |
37 | require.True(t, logger.ctrl.Satisfied(), "Test_PrettyPrint Failed!")
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/eventhub/message.go:
--------------------------------------------------------------------------------
1 | package eventhub
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs"
7 | )
8 |
9 | type Message struct {
10 | event *azeventhubs.ReceivedEventData
11 | processor *azeventhubs.ProcessorPartitionClient
12 | logger Logger
13 | }
14 |
15 | func (a *Message) Commit() {
16 | // Update the checkpoint with the latest event received
17 | err := a.processor.UpdateCheckpoint(context.Background(), a.event, nil)
18 | if err != nil {
19 | a.logger.Errorf("failed to acknowledge event with eventID %v", a.event.MessageID)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/eventhub/metrics.go:
--------------------------------------------------------------------------------
1 | package eventhub
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type Metrics interface {
8 | IncrementCounter(ctx context.Context, name string, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/google/interfaces.go:
--------------------------------------------------------------------------------
1 | package google
2 |
3 | import (
4 | "context"
5 |
6 | "cloud.google.com/go/pubsub"
7 | )
8 |
9 | type Client interface {
10 | Writer
11 | Reader
12 |
13 | Close() error
14 | Topics(ctx context.Context) *pubsub.TopicIterator
15 | Subscriptions(ctx context.Context) *pubsub.SubscriptionIterator
16 | }
17 |
18 | type Writer interface {
19 | Subscription(id string) *pubsub.Subscription
20 | CreateSubscription(ctx context.Context, id string, cfg pubsub.SubscriptionConfig) (*pubsub.Subscription, error)
21 | }
22 |
23 | type Reader interface {
24 | Topic(id string) *pubsub.Topic
25 | CreateTopic(ctx context.Context, topicID string) (*pubsub.Topic, error)
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/google/message.go:
--------------------------------------------------------------------------------
1 | package google
2 |
3 | import (
4 | gcPubSub "cloud.google.com/go/pubsub"
5 | )
6 |
7 | type googleMessage struct {
8 | msg *gcPubSub.Message
9 | }
10 |
11 | func newGoogleMessage(msg *gcPubSub.Message) *googleMessage {
12 | return &googleMessage{msg: msg}
13 | }
14 |
15 | func (gm *googleMessage) Commit() {
16 | gm.msg.Ack()
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/google/message_test.go:
--------------------------------------------------------------------------------
1 | package google
2 |
3 | import (
4 | "testing"
5 |
6 | gcPubSub "cloud.google.com/go/pubsub"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNew(t *testing.T) {
11 | msg := new(gcPubSub.Message)
12 |
13 | out := newGoogleMessage(msg)
14 |
15 | assert.Equal(t, msg, out.msg)
16 | }
17 |
18 | func TestGoogleMessage_Commit(_ *testing.T) {
19 | msg := newGoogleMessage(&gcPubSub.Message{})
20 |
21 | msg.Commit()
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/google/metrics.go:
--------------------------------------------------------------------------------
1 | package google
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | IncrementCounter(ctx context.Context, name string, labels ...string)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/interface.go:
--------------------------------------------------------------------------------
1 | // Package pubsub provides a foundation for implementing pub/sub clients for various message brokers such as google pub-sub,
2 | // kafka and MQTT. It defines interfaces for publishing and subscribing to messages, managing topics, and handling messages.
3 | package pubsub
4 |
5 | import (
6 | "context"
7 |
8 | "gofr.dev/pkg/gofr/datasource"
9 | )
10 |
11 | type Publisher interface {
12 | Publish(ctx context.Context, topic string, message []byte) error
13 | }
14 |
15 | type Subscriber interface {
16 | Subscribe(ctx context.Context, topic string) (*Message, error)
17 | }
18 |
19 | type Client interface {
20 | Publisher
21 | Subscriber
22 | Health() datasource.Health
23 |
24 | CreateTopic(context context.Context, name string) error
25 | DeleteTopic(context context.Context, name string) error
26 |
27 | Close() error
28 | }
29 |
30 | type Committer interface {
31 | Commit()
32 | }
33 |
34 | type Logger interface {
35 | Debugf(format string, args ...any)
36 | Debug(args ...any)
37 | Logf(format string, args ...any)
38 | Log(args ...any)
39 | Errorf(format string, args ...any)
40 | Error(args ...any)
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/errors.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrConsumerGroupNotProvided = errors.New("consumer group id not provided")
7 | errFailedToConnectBrokers = errors.New("failed to connect to any kafka brokers")
8 | errBrokerNotProvided = errors.New("kafka broker address not provided")
9 | errPublisherNotConfigured = errors.New("can't publish message. Publisher not configured or topic is empty")
10 | errBatchSize = errors.New("KAFKA_BATCH_SIZE must be greater than 0")
11 | errBatchBytes = errors.New("KAFKA_BATCH_BYTES must be greater than 0")
12 | errBatchTimeout = errors.New("KAFKA_BATCH_TIMEOUT must be greater than 0")
13 | errClientNotConnected = errors.New("kafka client not connected")
14 | errUnsupportedSASLMechanism = errors.New("unsupported SASL mechanism")
15 | errSASLCredentialsMissing = errors.New("SASL credentials missing")
16 | errUnsupportedSecurityProtocol = errors.New("unsupported security protocol")
17 | errNoActiveConnections = errors.New("no active connections to brokers")
18 | errCACertFileRead = errors.New("failed to read CA certificate file")
19 | errClientCertLoad = errors.New("failed to load client certificate")
20 | errNotController = errors.New("not a controller")
21 | errUnreachable = errors.New("unreachable")
22 | )
23 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/interfaces.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "context"
5 | "net"
6 |
7 | "github.com/segmentio/kafka-go"
8 | )
9 |
10 | //go:generate go run go.uber.org/mock/mockgen -source=interfaces.go -destination=mock_interfaces.go -package=kafka
11 |
12 | type Reader interface {
13 | ReadMessage(ctx context.Context) (kafka.Message, error)
14 | FetchMessage(ctx context.Context) (kafka.Message, error)
15 | CommitMessages(ctx context.Context, msgs ...kafka.Message) error
16 | Stats() kafka.ReaderStats
17 | Close() error
18 | }
19 |
20 | type Writer interface {
21 | WriteMessages(ctx context.Context, msg ...kafka.Message) error
22 | Close() error
23 | Stats() kafka.WriterStats
24 | }
25 |
26 | type Connection interface {
27 | Controller() (broker kafka.Broker, err error)
28 | CreateTopics(topics ...kafka.TopicConfig) error
29 | DeleteTopics(topics ...string) error
30 | RemoteAddr() net.Addr
31 | ReadPartitions(topics ...string) (partitions []kafka.Partition, err error)
32 | Close() error
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/message.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/segmentio/kafka-go"
7 |
8 | "gofr.dev/pkg/gofr/datasource/pubsub"
9 | )
10 |
11 | type kafkaMessage struct {
12 | msg *kafka.Message
13 | reader Reader
14 | logger pubsub.Logger
15 | }
16 |
17 | func newKafkaMessage(msg *kafka.Message, reader Reader, logger pubsub.Logger) *kafkaMessage {
18 | return &kafkaMessage{
19 | msg: msg,
20 | reader: reader,
21 | logger: logger,
22 | }
23 | }
24 |
25 | func (kmsg *kafkaMessage) Commit() {
26 | if kmsg.reader != nil {
27 | err := kmsg.reader.CommitMessages(context.Background(), *kmsg.msg)
28 | if err != nil {
29 | kmsg.logger.Errorf("unable to commit message on kafka")
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/message_test.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/segmentio/kafka-go"
7 | "github.com/stretchr/testify/assert"
8 | "go.uber.org/mock/gomock"
9 |
10 | "gofr.dev/pkg/gofr/logging"
11 | "gofr.dev/pkg/gofr/testutil"
12 | )
13 |
14 | func TestNewMessage(t *testing.T) {
15 | msg := new(kafka.Message)
16 | reader := new(kafka.Reader)
17 | k := newKafkaMessage(msg, reader, nil)
18 |
19 | assert.NotNil(t, k)
20 | assert.Equal(t, msg, k.msg)
21 | assert.Equal(t, reader, k.reader)
22 | }
23 |
24 | func TestKafkaMessage_Commit(t *testing.T) {
25 | ctrl := gomock.NewController(t)
26 | defer ctrl.Finish()
27 |
28 | mockReader := NewMockReader(ctrl)
29 |
30 | msg := &kafka.Message{Topic: "test", Value: []byte("hello")}
31 | logger := logging.NewMockLogger(logging.ERROR)
32 | k := newKafkaMessage(msg, mockReader, logger)
33 |
34 | mockReader.EXPECT().CommitMessages(gomock.Any(), *msg).Return(nil)
35 |
36 | k.Commit()
37 | }
38 |
39 | func TestKafkaMessage_CommitError(t *testing.T) {
40 | ctrl := gomock.NewController(t)
41 | defer ctrl.Finish()
42 |
43 | mockReader := NewMockReader(ctrl)
44 |
45 | out := testutil.StderrOutputForFunc(func() {
46 | msg := &kafka.Message{Topic: "test", Value: []byte("hello")}
47 | logger := logging.NewMockLogger(logging.ERROR)
48 | k := newKafkaMessage(msg, mockReader, logger)
49 |
50 | mockReader.EXPECT().CommitMessages(gomock.Any(), *msg).
51 | Return(testutil.CustomError{ErrorMessage: "error"})
52 |
53 | k.Commit()
54 | })
55 |
56 | assert.Contains(t, out, "unable to commit message on kafka")
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/metrics.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | IncrementCounter(ctx context.Context, name string, labels ...string)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/kafka/tls.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "os"
8 | "strings"
9 | )
10 |
11 | type TLSConfig struct {
12 | CertFile string
13 | KeyFile string
14 | CACertFile string
15 | InsecureSkipVerify bool
16 | }
17 |
18 | func createTLSConfig(tlsConf *TLSConfig) (*tls.Config, error) {
19 | tlsConfig := &tls.Config{
20 | InsecureSkipVerify: tlsConf.InsecureSkipVerify, //nolint:gosec //Populate the value as per user input
21 | }
22 |
23 | if tlsConf.CACertFile != "" {
24 | caCert, err := os.ReadFile(tlsConf.CACertFile)
25 | if err != nil {
26 | return nil, fmt.Errorf("%w: %w", errCACertFileRead, err)
27 | }
28 |
29 | caCertPool := x509.NewCertPool()
30 | caCertPool.AppendCertsFromPEM(caCert)
31 | tlsConfig.RootCAs = caCertPool
32 | }
33 |
34 | if tlsConf.CertFile != "" && tlsConf.KeyFile != "" {
35 | cert, err := tls.LoadX509KeyPair(tlsConf.CertFile, tlsConf.KeyFile)
36 | if err != nil {
37 | return nil, fmt.Errorf("%w: %w", errClientCertLoad, err)
38 | }
39 |
40 | tlsConfig.Certificates = []tls.Certificate{cert}
41 | }
42 |
43 | return tlsConfig, nil
44 | }
45 |
46 | func validateTLSConfigs(conf *Config) error {
47 | protocol := strings.ToUpper(conf.SecurityProtocol)
48 |
49 | if protocol == protocolSSL || protocol == protocolSASLSSL {
50 | if conf.TLS.CACertFile == "" && !conf.TLS.InsecureSkipVerify && conf.TLS.CertFile == "" {
51 | return fmt.Errorf("for %s, provide either CA cert, client certs, or enable insecure mode: %w",
52 | protocol, errUnsupportedSecurityProtocol)
53 | }
54 | }
55 |
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/log.go:
--------------------------------------------------------------------------------
1 | package pubsub
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | type Log struct {
9 | Mode string `json:"mode"`
10 | CorrelationID string `json:"correlationID"`
11 | MessageValue string `json:"messageValue"`
12 | Topic string `json:"topic"`
13 | Host string `json:"host"`
14 | PubSubBackend string `json:"pubSubBackend"`
15 | Time int64 `json:"time"`
16 | }
17 |
18 | func (l *Log) PrettyPrint(writer io.Writer) {
19 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;24m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %-4s %s \u001b[38;5;101m%s\u001b[0m\n",
20 | l.CorrelationID, l.PubSubBackend, l.Time, l.Mode, l.Topic, l.MessageValue)
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/mqtt/interface.go:
--------------------------------------------------------------------------------
1 | // Package mqtt provides a client for interacting with MQTT message brokers.This package facilitates interaction with
2 | // MQTT brokers, allowing publishing and subscribing to topics, managing subscriptions, and handling messages.
3 | package mqtt
4 |
5 | import (
6 | "context"
7 |
8 | "gofr.dev/pkg/gofr/datasource"
9 | )
10 |
11 | //go:generate go run go.uber.org/mock/mockgen -destination=mock_client.go -package=mqtt github.com/eclipse/paho.mqtt.golang Client
12 | //go:generate go run go.uber.org/mock/mockgen -destination=mock_token.go -package=mqtt github.com/eclipse/paho.mqtt.golang Token
13 | //go:generate go run go.uber.org/mock/mockgen -source=interface.go -destination=mock_interfaces.go -package=mqtt
14 |
15 | type Logger interface {
16 | Infof(format string, args ...any)
17 | Debug(args ...any)
18 | Debugf(format string, args ...any)
19 | Warnf(format string, args ...any)
20 | Errorf(format string, args ...any)
21 | }
22 |
23 | type Metrics interface {
24 | IncrementCounter(ctx context.Context, name string, labels ...string)
25 | }
26 |
27 | type PubSub interface {
28 | SubscribeWithFunction(topic string, subscribeFunc SubscribeFunc) error
29 | Publish(ctx context.Context, topic string, message []byte) error
30 | Unsubscribe(topic string) error
31 | Disconnect(waitTime uint) error
32 | Ping() error
33 | Health() datasource.Health
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/mqtt/message.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import mqtt "github.com/eclipse/paho.mqtt.golang"
4 |
5 | type message struct {
6 | msg mqtt.Message
7 | }
8 |
9 | func (m *message) Commit() {
10 | m.msg.Ack()
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/mqtt/message_test.go:
--------------------------------------------------------------------------------
1 | package mqtt
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestMessage(_ *testing.T) {
8 | m := message{msg: mockMessage{}}
9 |
10 | m.Commit()
11 | }
12 |
13 | type mockMessage struct {
14 | duplicate bool
15 | qos int
16 | retained bool
17 | topic string
18 | messageID int
19 | pyload string
20 | }
21 |
22 | func (m mockMessage) Duplicate() bool {
23 | return m.duplicate
24 | }
25 |
26 | func (m mockMessage) Qos() byte {
27 | return byte(m.qos)
28 | }
29 |
30 | func (m mockMessage) Retained() bool {
31 | return m.retained
32 | }
33 |
34 | func (m mockMessage) Topic() string {
35 | return m.topic
36 | }
37 |
38 | func (m mockMessage) MessageID() uint16 {
39 | if m.messageID < 0 || m.messageID > int(^uint16(0)) {
40 | return 0
41 | }
42 |
43 | return uint16(m.messageID)
44 | }
45 |
46 | func (m mockMessage) Payload() []byte {
47 | return []byte(m.pyload)
48 | }
49 |
50 | func (mockMessage) Ack() {
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/committer.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/nats-io/nats.go/jetstream"
7 | )
8 |
9 | // natsCommitter implements the pubsub.Committer interface for Client messages.
10 | type natsCommitter struct {
11 | msg jetstream.Msg
12 | }
13 |
14 | // Commit commits the message.
15 | func (c *natsCommitter) Commit() {
16 | if err := c.msg.Ack(); err != nil {
17 | log.Println("Error committing message:", err)
18 |
19 | // nak the message
20 | if err := c.msg.Nak(); err != nil {
21 | log.Println("Error naking message:", err)
22 | }
23 |
24 | return
25 | }
26 | }
27 |
28 | // Nak naks the message.
29 | func (c *natsCommitter) Nak() error {
30 | return c.msg.Nak()
31 | }
32 |
33 | // Rollback rolls back the message.
34 | func (c *natsCommitter) Rollback() error {
35 | return c.msg.Nak()
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/config.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "time"
5 |
6 | "gofr.dev/pkg/gofr/datasource/pubsub"
7 | )
8 |
9 | const batchSize = 100
10 |
11 | // Config defines the Client configuration.
12 | type Config struct {
13 | Server string
14 | CredsFile string
15 | Stream StreamConfig
16 | Consumer string
17 | MaxWait time.Duration
18 | MaxPullWait int
19 | }
20 |
21 | // StreamConfig holds stream settings for NATS jStream.
22 | type StreamConfig struct {
23 | Stream string
24 | Subjects []string
25 | MaxDeliver int
26 | MaxWait time.Duration
27 | MaxBytes int64
28 | }
29 |
30 | // New creates a new Client.
31 | func New(cfg *Config, logger pubsub.Logger) *PubSubWrapper {
32 | if cfg == nil {
33 | cfg = &Config{}
34 | }
35 |
36 | client := &Client{
37 | Config: cfg,
38 | subManager: newSubscriptionManager(batchSize),
39 | logger: logger,
40 | }
41 |
42 | return &PubSubWrapper{Client: client}
43 | }
44 |
45 | // validateConfigs validates the configuration for NATS jStream.
46 | func validateConfigs(conf *Config) error {
47 | if conf.Server == "" {
48 | return errServerNotProvided
49 | }
50 |
51 | if len(conf.Stream.Subjects) == 0 {
52 | return errSubjectsNotProvided
53 | }
54 |
55 | if conf.Consumer == "" {
56 | return errConsumerNotProvided
57 | }
58 |
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/connectors.go:
--------------------------------------------------------------------------------
1 | // Package nats connector.go
2 | package nats
3 |
4 | import (
5 | "github.com/nats-io/nats.go"
6 | "github.com/nats-io/nats.go/jetstream"
7 | )
8 |
9 | type defaultConnector struct{}
10 |
11 | func (*defaultConnector) Connect(serverURL string, opts ...nats.Option) (ConnInterface, error) {
12 | nc, err := nats.Connect(serverURL, opts...)
13 | if err != nil {
14 | return nil, err
15 | }
16 |
17 | return &natsConnWrapper{nc}, nil
18 | }
19 |
20 | type DefaultJetStreamCreator struct{}
21 |
22 | func (*DefaultJetStreamCreator) New(conn ConnInterface) (jetstream.JetStream, error) {
23 | return conn.JetStream()
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/errors.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import "errors"
4 |
5 | var (
6 | // Client Errors.
7 | errServerNotProvided = errors.New("client server address not provided")
8 | errSubjectsNotProvided = errors.New("subjects not provided")
9 | errConsumerNotProvided = errors.New("consumer name not provided")
10 | errConsumerCreationError = errors.New("consumer creation error")
11 | errFailedToDeleteStream = errors.New("failed to delete stream")
12 | errPublishError = errors.New("publish error")
13 | errJetStreamNotConfigured = errors.New("jStream is not configured")
14 | errJetStreamCreationFailed = errors.New("jStream creation failed")
15 | errJetStream = errors.New("jStream error")
16 | errCreateStream = errors.New("create stream error")
17 | errDeleteStream = errors.New("delete stream error")
18 | errGetStream = errors.New("get stream error")
19 | errCreateOrUpdateStream = errors.New("create or update stream error")
20 | errHandlerError = errors.New("handler error")
21 | errConnectionError = errors.New("connection error")
22 | errSubscriptionError = errors.New("subscription error")
23 | )
24 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/pubsub/nats
2 |
3 | go 1.24
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/nats-io/nats-server/v2 v2.10.27
9 | github.com/nats-io/nats.go v1.39.1
10 | github.com/stretchr/testify v1.10.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | gofr.dev v1.40.0
14 | )
15 |
16 | require (
17 | github.com/davecgh/go-spew v1.1.1 // indirect
18 | github.com/joho/godotenv v1.5.1 // indirect
19 | github.com/klauspost/compress v1.18.0 // indirect
20 | github.com/kr/pretty v0.3.1 // indirect
21 | github.com/minio/highwayhash v1.0.3 // indirect
22 | github.com/nats-io/jwt/v2 v2.7.3 // indirect
23 | github.com/nats-io/nkeys v0.4.10 // indirect
24 | github.com/nats-io/nuid v1.0.1 // indirect
25 | github.com/pkg/errors v0.9.1 // indirect
26 | github.com/pmezard/go-difflib v1.0.0 // indirect
27 | github.com/rogpeppe/go-internal v1.13.1 // indirect
28 | go.opentelemetry.io/otel v1.36.0 // indirect
29 | golang.org/x/crypto v0.38.0 // indirect
30 | golang.org/x/sys v0.33.0 // indirect
31 | golang.org/x/term v0.32.0 // indirect
32 | golang.org/x/text v0.25.0 // indirect
33 | golang.org/x/time v0.11.0 // indirect
34 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
35 | gopkg.in/yaml.v3 v3.0.1 // indirect
36 | )
37 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/health.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "context"
5 |
6 | "gofr.dev/pkg/gofr/datasource"
7 | )
8 |
9 | const (
10 | natsBackend = "Client"
11 | jetStreamStatusOK = "OK"
12 | jetStreamStatusError = "Error"
13 | jetStreamConnected = "CONNECTED"
14 | jetStreamDisconnecting = "DISCONNECTED"
15 | )
16 |
17 | // Health checks the health of the NATS connection.
18 | func (c *Client) Health() datasource.Health {
19 | if c.connManager == nil {
20 | return datasource.Health{
21 | Status: datasource.StatusDown,
22 | }
23 | }
24 |
25 | health := c.connManager.Health()
26 | health.Details["backend"] = natsBackend
27 |
28 | js, err := c.connManager.jetStream()
29 | if err != nil {
30 | health.Details["jetstream_enabled"] = false
31 | health.Details["jetstream_status"] = jetStreamStatusError + ": " + err.Error()
32 |
33 | return health
34 | }
35 |
36 | // Call AccountInfo() to get jStream status
37 | jetStreamStatus, err := GetJetStreamStatus(context.Background(), js)
38 | if err != nil {
39 | jetStreamStatus = jetStreamStatusError + ": " + err.Error()
40 | }
41 |
42 | health.Details["jetstream_enabled"] = true
43 | health.Details["jetstream_status"] = jetStreamStatus
44 |
45 | return health
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/message.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "github.com/nats-io/nats.go/jetstream"
5 | "gofr.dev/pkg/gofr/datasource/pubsub"
6 | )
7 |
8 | type natsMessage struct {
9 | msg jetstream.Msg
10 | logger pubsub.Logger
11 | }
12 |
13 | func newNATSMessage(msg jetstream.Msg, logger pubsub.Logger) *natsMessage {
14 | return &natsMessage{
15 | msg: msg,
16 | logger: logger,
17 | }
18 | }
19 |
20 | func (nmsg *natsMessage) Commit() {
21 | if err := nmsg.msg.Ack(); err != nil {
22 | nmsg.logger.Errorf("unable to acknowledge message on Client jStream: %v", err)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/message_test.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "go.uber.org/mock/gomock"
8 | "gofr.dev/pkg/gofr/logging"
9 | "gofr.dev/pkg/gofr/testutil"
10 | )
11 |
12 | func TestNewNATSMessage(t *testing.T) {
13 | ctrl := gomock.NewController(t)
14 | defer ctrl.Finish()
15 |
16 | mockMsg := NewMockMsg(ctrl)
17 | logger := logging.NewMockLogger(logging.ERROR)
18 | n := newNATSMessage(mockMsg, logger)
19 |
20 | assert.NotNil(t, n)
21 | assert.Equal(t, mockMsg, n.msg)
22 | assert.Equal(t, logger, n.logger)
23 | }
24 |
25 | func TestNATSMessage_Commit(t *testing.T) {
26 | ctrl := gomock.NewController(t)
27 | defer ctrl.Finish()
28 |
29 | mockMsg := NewMockMsg(ctrl)
30 | logger := logging.NewMockLogger(logging.ERROR)
31 | n := newNATSMessage(mockMsg, logger)
32 |
33 | mockMsg.EXPECT().Ack().Return(nil)
34 |
35 | n.Commit()
36 | }
37 |
38 | func TestNATSMessage_CommitError(t *testing.T) {
39 | ctrl := gomock.NewController(t)
40 | defer ctrl.Finish()
41 |
42 | mockMsg := NewMockMsg(ctrl)
43 |
44 | out := testutil.StderrOutputForFunc(func() {
45 | logger := logging.NewMockLogger(logging.ERROR)
46 | n := newNATSMessage(mockMsg, logger)
47 |
48 | mockMsg.EXPECT().Ack().Return(testutil.CustomError{ErrorMessage: "ack error"})
49 |
50 | n.Commit()
51 | })
52 |
53 | assert.Contains(t, out, "unable to acknowledge message on Client jStream")
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/pubsub/nats/metrics.go:
--------------------------------------------------------------------------------
1 | package nats
2 |
3 | import "context"
4 |
5 | //go:generate mockgen -destination=mock_metrics.go -package=nats -source=./metrics.go
6 |
7 | // Metrics represents the metrics interface.
8 | type Metrics interface {
9 | IncrementCounter(ctx context.Context, name string, labels ...string)
10 | }
11 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/redis/health.go:
--------------------------------------------------------------------------------
1 | // Package redis provides a client for interacting with Redis key-value stores.This package allows creating and
2 | // managing Redis clients, executing Redis commands, and handling connections to Redis databases.
3 | package redis
4 |
5 | import (
6 | "context"
7 | "strconv"
8 | "time"
9 |
10 | "gofr.dev/pkg/gofr/datasource"
11 | )
12 |
13 | func (r *Redis) HealthCheck() datasource.Health {
14 | h := datasource.Health{
15 | Details: make(map[string]any),
16 | }
17 |
18 | h.Details["host"] = r.config.HostName + ":" + strconv.Itoa(r.config.Port)
19 |
20 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
21 | defer cancel()
22 |
23 | if r.Client == nil {
24 | h.Status = datasource.StatusDown
25 | h.Details["error"] = "redis not connected"
26 |
27 | return h
28 | }
29 |
30 | info, err := r.InfoMap(ctx, "Stats").Result()
31 | if err != nil {
32 | h.Status = datasource.StatusDown
33 | h.Details["error"] = err.Error()
34 |
35 | return h
36 | }
37 |
38 | h.Status = datasource.StatusUp
39 | h.Details["stats"] = info["Stats"]
40 |
41 | return h
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/redis/hook_test.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestQueryLog_PrettyPrint(t *testing.T) {
11 | testCases := []struct {
12 | desc string
13 | ql *QueryLog
14 | expOut []string
15 | }{
16 | {
17 | desc: "pipeline",
18 | ql: &QueryLog{
19 | Query: "pipeline",
20 | Duration: 112,
21 | Args: []any{"[", "set a", "get a", "ex 300: OK", "]"},
22 | },
23 | expOut: []string{"pipeline", "112", "REDIS", "set a", "get a"},
24 | },
25 | {
26 | desc: "single command",
27 | ql: &QueryLog{
28 | Query: "get",
29 | Duration: 22,
30 | Args: []any{"get", "key1"},
31 | },
32 | expOut: []string{"get", "REDIS", "22", "get key1"},
33 | },
34 | }
35 |
36 | for _, tc := range testCases {
37 | b := new(bytes.Buffer)
38 | tc.ql.PrettyPrint(b)
39 |
40 | out := b.String()
41 |
42 | for _, v := range tc.expOut {
43 | assert.Contains(t, out, v)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/redis/metrics.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/errors.go:
--------------------------------------------------------------------------------
1 | package scylladb
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var (
9 | errUnsupportedBatchType = errors.New("batch type not supported")
10 | errDestinationIsNotPointer = errors.New("destination is not pointer")
11 | errBatchNotInitialized = errors.New("batch not initialized")
12 | errUnexpectedMap = errors.New("a map was not expected")
13 | )
14 |
15 | type errUnexpectedPointer struct {
16 | target string
17 | }
18 |
19 | func (d errUnexpectedPointer) Error() string {
20 | return fmt.Sprintf("a pointer to %v was not expected.", d.target)
21 | }
22 |
23 | type errUnexpectedSlice struct {
24 | target string
25 | }
26 |
27 | func (d errUnexpectedSlice) Error() string {
28 | return fmt.Sprintf("a slice of %v was not expected.", d.target)
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/errors_test.go:
--------------------------------------------------------------------------------
1 | package scylladb
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func Test_UnexpectedPointer_Error(t *testing.T) {
10 | expected := "a pointer to int was not expected."
11 | err := errUnexpectedPointer{target: "int"}
12 |
13 | require.ErrorContains(t, err, expected)
14 | }
15 |
16 | func Test_UnexpectedSlice_Error(t *testing.T) {
17 | expected := "a slice of int was not expected."
18 | err := errUnexpectedSlice{target: "int"}
19 |
20 | require.ErrorContains(t, err, expected)
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/scylladb
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/gocql/gocql v1.7.0
9 | github.com/stoewer/go-strcase v1.3.0
10 | github.com/stretchr/testify v1.10.0
11 | go.opentelemetry.io/otel v1.36.0
12 | go.opentelemetry.io/otel/trace v1.36.0
13 | go.uber.org/mock v0.5.2
14 | )
15 |
16 | require (
17 | github.com/davecgh/go-spew v1.1.1 // indirect
18 | github.com/golang/snappy v0.0.4 // indirect
19 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
20 | github.com/kr/pretty v0.3.1 // indirect
21 | github.com/pmezard/go-difflib v1.0.0 // indirect
22 | github.com/rogpeppe/go-internal v1.13.1 // indirect
23 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
24 | gopkg.in/inf.v0 v0.9.1 // indirect
25 | gopkg.in/yaml.v3 v3.0.1 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/interface.go:
--------------------------------------------------------------------------------
1 | package scylladb
2 |
3 | import (
4 | "github.com/gocql/gocql"
5 | )
6 |
7 | // clusterConfig defines methods for interacting with a ScyllaDB clusterConfig.
8 | type clusterConfig interface {
9 | createSession() (session, error)
10 | }
11 |
12 | // iterator defines methods for interacting with a ScyllaDB iterator.
13 | type iterator interface {
14 | Columns() []gocql.ColumnInfo
15 | Scan(dest ...any) bool
16 | NumRows() int
17 | }
18 |
19 | // query defines methods for interacting with a ScyllaDB query.
20 | type query interface {
21 | Exec() error
22 | Iter() iterator
23 | MapScanCAS(dest map[string]any) (applied bool, err error)
24 | ScanCAS(dest ...any) (applied bool, err error)
25 | }
26 |
27 | // batch defines methods for interacting with a ScyllaDB batch.
28 | type batch interface {
29 | Query(stmt string, args ...any)
30 | getBatch() *gocql.Batch
31 | }
32 |
33 | // session defines methods for interacting with a ScyllaDB session.
34 | type session interface {
35 | Query(stmt string, values ...any) query
36 | newBatch(batchType gocql.BatchType) batch
37 | executeBatch(batch batch) error
38 | executeBatchCAS(batch batch, dest ...any) (bool, error)
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/logger.go:
--------------------------------------------------------------------------------
1 | package scylladb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "regexp"
8 | "strings"
9 | )
10 |
11 | type Logger interface {
12 | Debug(args ...any)
13 | Debugf(pattern string, args ...any)
14 | Error(args ...any)
15 | Infof(pattern string, args ...any)
16 | Errorf(format string, args ...any)
17 | Log(args ...any)
18 | Logf(pattern string, args ...any)
19 | }
20 |
21 | type QueryLog struct {
22 | Operation string `json:"operation"`
23 | Query string `json:"query"`
24 | Duration int64 `json:"duration"`
25 | Keyspace string `json:"keyspace,omitempty"`
26 | }
27 |
28 | func (ql *QueryLog) PrettyPrint(writer io.Writer) {
29 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;206m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s \u001B[38;5;8m%-32s\u001B[0m\n",
30 | clean(ql.Operation), "SCYLDB", ql.Duration, clean(ql.Keyspace), clean(ql.Query))
31 | }
32 |
33 | var matchSpaces = regexp.MustCompile(`\s+`)
34 |
35 | // clean takes a string query as input and performs two operations to clean it up:
36 | // 1. It replaces multiple consecutive whitespace characters with a single space.
37 | // 2. It trims leading and trailing whitespace from the string.
38 | // The cleaned-up query string is then returned.
39 | func clean(query string) string {
40 | query = matchSpaces.ReplaceAllString(query, " ")
41 | query = strings.TrimSpace(query)
42 |
43 | return query
44 | }
45 |
46 | type Metrics interface {
47 | NewHistogram(name, desc string, buckets ...float64)
48 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/scylladb/logger_test.go:
--------------------------------------------------------------------------------
1 | package scylladb
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func Test_PrettyPrint(t *testing.T) {
11 | queryLog := QueryLog{
12 | Query: "sample query",
13 | Duration: 12345,
14 | }
15 | expected := "sample query"
16 |
17 | var buf bytes.Buffer
18 |
19 | queryLog.PrettyPrint(&buf)
20 |
21 | assert.Contains(t, buf.String(), expected)
22 | }
23 |
24 | func Test_Clean(t *testing.T) {
25 | testCases := []struct {
26 | desc string
27 | input string
28 | expected string
29 | }{
30 | {"multiple spaces", " multiple spaces ", "multiple spaces"},
31 | {"leading and trailing", "leading and trailing ", "leading and trailing"},
32 | {"mixed white spaces", " mixed\twhite\nspaces", "mixed white spaces"},
33 | {"single word", "singleword", "singleword"},
34 | {"empty string", "", ""},
35 | {"empty string with spaces", " ", ""},
36 | }
37 |
38 | for i, tc := range testCases {
39 | t.Run(tc.input, func(t *testing.T) {
40 | result := clean(tc.input)
41 | assert.Equal(t, tc.expected, result, "TEST[%d], Failed.\n%s", i, tc.desc)
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/solr/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/solr
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/stretchr/testify v1.10.0
9 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/go-logr/logr v1.4.2 // indirect
18 | github.com/go-logr/stdr v1.2.2 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
21 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
22 | gopkg.in/yaml.v3 v3.0.1 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/solr/logger.go:
--------------------------------------------------------------------------------
1 | package solr
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type Logger interface {
11 | Debug(args ...any)
12 | Debugf(pattern string, args ...any)
13 | Info(args ...any)
14 | Infof(pattern string, args ...any)
15 | Error(args ...any)
16 | Errorf(pattern string, args ...any)
17 | }
18 |
19 | type QueryLog struct {
20 | Type string `json:"type"`
21 | URL string `json:"Url"`
22 | Duration int64 `json:"duration"`
23 | }
24 |
25 | func (ql *QueryLog) PrettyPrint(writer io.Writer) {
26 | fmt.Fprintf(writer, "\u001B[38;5;8m%-32s \u001B[38;5;206m%-6s\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s\n",
27 | clean(ql.URL), "SOLR", ql.Duration, clean(ql.Type))
28 | }
29 |
30 | // clean takes a string query as input and performs two operations to clean it up:
31 | // 1. It replaces multiple consecutive whitespace characters with a single space.
32 | // 2. It trims leading and trailing whitespace from the string.
33 | // The cleaned-up query string is then returned.
34 | func clean(query string) string {
35 | // Replace multiple consecutive whitespace characters with a single space
36 | query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
37 |
38 | // Trim leading and trailing whitespace from the string
39 | query = strings.TrimSpace(query)
40 |
41 | return query
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/solr/metrics.go:
--------------------------------------------------------------------------------
1 | package solr
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | NewHistogram(name, desc string, buckets ...float64)
7 |
8 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/sql/bind.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | const (
8 | dialectMysql = "mysql"
9 | dialectPostgres = "postgres"
10 |
11 | quoteBack = "`"
12 | quoteDouble = `"`
13 | )
14 |
15 | // BindVarType represents different type of bindvars in SQL queries.
16 | type BindVarType uint
17 |
18 | const (
19 | UNKNOWN BindVarType = iota + 1
20 | QUESTION
21 | DOLLAR
22 | )
23 |
24 | func bindType(dialect string) BindVarType {
25 | switch dialect {
26 | case dialectMysql:
27 | return QUESTION
28 | case dialectPostgres:
29 | return DOLLAR
30 | default:
31 | return UNKNOWN
32 | }
33 | }
34 |
35 | func bindVar(dialect string, position int) string {
36 | if DOLLAR == bindType(dialect) {
37 | return fmt.Sprintf("$%v", position)
38 | }
39 |
40 | return "?"
41 | }
42 | func quote(dialect string) string {
43 | if dialectPostgres == dialect {
44 | return quoteDouble
45 | }
46 |
47 | return quoteBack
48 | }
49 |
50 | func quotedString(q, s string) string {
51 | return fmt.Sprintf("%s%s%s", q, s, q)
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/sql/metrics.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
7 | SetGauge(name string, value float64, labels ...string)
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/sql/sql_mock.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/DATA-DOG/go-sqlmock"
7 | "go.uber.org/mock/gomock"
8 |
9 | "gofr.dev/pkg/gofr/logging"
10 | )
11 |
12 | func NewSQLMocks(t *testing.T) (*DB, sqlmock.Sqlmock, *MockMetrics) {
13 | t.Helper()
14 |
15 | return NewSQLMocksWithConfig(t, &DBConfig{})
16 | }
17 |
18 | func NewSQLMocksWithConfig(t *testing.T, config *DBConfig) (*DB, sqlmock.Sqlmock, *MockMetrics) {
19 | t.Helper()
20 |
21 | db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
22 |
23 | if err != nil {
24 | t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
25 | }
26 |
27 | ctrl := gomock.NewController(t)
28 | mockMetrics := NewMockMetrics(ctrl)
29 |
30 | mockMetrics.EXPECT().RecordHistogram(gomock.Any(), "app_sql_stats", gomock.Any(),
31 | "hostname", gomock.Any(), "database", gomock.Any(), "type", gomock.Any()).AnyTimes()
32 |
33 | return &DB{
34 | DB: db,
35 | logger: logging.NewMockLogger(logging.DEBUG),
36 | config: config,
37 | metrics: mockMetrics,
38 | }, mock, mockMetrics
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/gofr/datasource/surrealdb/go.mod:
--------------------------------------------------------------------------------
1 | module gofr.dev/pkg/gofr/datasource/surrealdb
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/stretchr/testify v1.10.0
9 | github.com/surrealdb/surrealdb.go v0.3.2
10 | go.opentelemetry.io/otel v1.36.0
11 | go.opentelemetry.io/otel/trace v1.36.0
12 | go.uber.org/mock v0.5.2
13 | )
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
18 | github.com/go-logr/logr v1.4.2 // indirect
19 | github.com/go-logr/stdr v1.2.2 // indirect
20 | github.com/gofrs/uuid v4.4.0+incompatible // indirect
21 | github.com/gorilla/websocket v1.5.3 // indirect
22 | github.com/pmezard/go-difflib v1.0.0 // indirect
23 | github.com/x448/float16 v0.8.4 // indirect
24 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
25 | go.opentelemetry.io/otel/metric v1.36.0 // indirect
26 | gopkg.in/yaml.v3 v3.0.1 // indirect
27 | )
28 |
--------------------------------------------------------------------------------
/pkg/gofr/default.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | const (
4 | defaultHTTPPort = 8000
5 | defaultGRPCPort = 9000
6 | defaultMetricPort = 2121
7 | )
8 |
--------------------------------------------------------------------------------
/pkg/gofr/file/file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | type file struct {
4 | name string
5 | content []byte
6 | size int64
7 | isDir bool
8 | }
9 |
10 | func (f file) GetName() string {
11 | return f.name
12 | }
13 |
14 | func (f file) GetSize() int64 {
15 | return f.size
16 | }
17 |
18 | func (f file) Bytes() []byte {
19 | return f.content
20 | }
21 |
22 | func (f file) IsDir() bool {
23 | return f.isDir
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/gofr/file/file_test.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestMain(m *testing.M) {
11 | os.Setenv("GOFR_TELEMETRY", "false")
12 | m.Run()
13 | }
14 |
15 | func TestFile(t *testing.T) {
16 | // Create a sample file
17 | content := []byte("This is a test file.")
18 | f := file{
19 | name: "test.txt",
20 | content: content,
21 | size: int64(len(content)),
22 | isDir: false,
23 | }
24 |
25 | // Test GetName method
26 | assert.Equal(t, "test.txt", f.GetName(), "File name should be 'test.txt'")
27 |
28 | // Test GetSize method
29 | assert.Equal(t, int64(20), f.GetSize(), "File size should be 20 bytes")
30 |
31 | // Test Bytes method
32 | assert.Equal(t, content, f.Bytes(), "File content should match")
33 |
34 | // Test IsDir method
35 | assert.False(t, f.IsDir(), "File should not be a directory")
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/gofr/http/metrics.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import "context"
4 |
5 | // Metrics represents an interface for registering the default metrics in GoFr framework.
6 | type Metrics interface {
7 | IncrementCounter(ctx context.Context, name string, labels ...string)
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/config.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 |
7 | "golang.org/x/text/cases"
8 | "golang.org/x/text/language"
9 |
10 | "gofr.dev/pkg/gofr/config"
11 | "gofr.dev/pkg/gofr/service"
12 | )
13 |
14 | type Config struct {
15 | CorsHeaders map[string]string
16 | LogProbes LogProbes
17 | }
18 |
19 | type LogProbes struct {
20 | Disabled bool
21 | Paths []string
22 | }
23 |
24 | func GetConfigs(c config.Config) Config {
25 | middlewareConfigs := Config{
26 | CorsHeaders: make(map[string]string),
27 | }
28 |
29 | allowedCORSHeaders := []string{
30 | "ACCESS_CONTROL_ALLOW_ORIGIN",
31 | "ACCESS_CONTROL_ALLOW_HEADERS",
32 | "ACCESS_CONTROL_ALLOW_CREDENTIALS",
33 | "ACCESS_CONTROL_EXPOSE_HEADERS",
34 | "ACCESS_CONTROL_MAX_AGE",
35 | }
36 |
37 | for _, v := range allowedCORSHeaders {
38 | if val := c.Get(v); val != "" {
39 | middlewareConfigs.CorsHeaders[convertHeaderNames(v)] = val
40 | }
41 | }
42 |
43 | // Config values for Log Probes
44 | logDisableProbes := c.GetOrDefault("LOG_DISABLE_PROBES", "false")
45 | middlewareConfigs.LogProbes.Paths = []string{service.HealthPath, service.AlivePath}
46 |
47 | // Convert the string value to a boolean
48 | value, err := strconv.ParseBool(logDisableProbes)
49 | if err == nil {
50 | middlewareConfigs.LogProbes.Disabled = value
51 | }
52 |
53 | return middlewareConfigs
54 | }
55 |
56 | func convertHeaderNames(header string) string {
57 | words := strings.Split(header, "_")
58 | titleCaser := cases.Title(language.Und)
59 |
60 | for i, v := range words {
61 | words[i] = titleCaser.String(strings.ToLower(v))
62 | }
63 |
64 | return strings.Join(words, "-")
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/config_test.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "gofr.dev/pkg/gofr/config"
9 | )
10 |
11 | func TestGetConfigs(t *testing.T) {
12 | mockConfig := config.NewMockConfig(map[string]string{
13 | "ACCESS_CONTROL_ALLOW_ORIGIN": "*",
14 | "ACCESS_CONTROL_ALLOW_HEADERS": "Authorization, Content-Type",
15 | "ACCESS_CONTROL_ALLOW_CREDENTIALS": "true",
16 | "ACCESS_CONTROL_ALLOW_CUSTOMHEADER": "abc",
17 | })
18 |
19 | middlewareConfigs := GetConfigs(mockConfig)
20 |
21 | expectedConfigs := map[string]string{
22 | "Access-Control-Allow-Origin": "*",
23 | "Access-Control-Allow-Headers": "Authorization, Content-Type",
24 | "Access-Control-Allow-Credentials": "true",
25 | }
26 |
27 | assert.Equal(t, expectedConfigs, middlewareConfigs.CorsHeaders, "TestGetConfigs Failed!")
28 | assert.NotContains(t, middlewareConfigs.CorsHeaders, "Access-Control-Allow-CustomHeader", "TestGetConfigs Failed!")
29 | }
30 |
31 | func TestLogDisableProbesConfig(t *testing.T) {
32 | mockConfig := config.NewMockConfig(map[string]string{
33 | "LOG_DISABLE_PROBES": "true",
34 | })
35 |
36 | middlewareConfigs := GetConfigs(mockConfig)
37 |
38 | assert.True(t, middlewareConfigs.LogProbes.Disabled, "TestLogDisableProbesConfig Failed!")
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/tracer.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 |
8 | "go.opentelemetry.io/otel"
9 | "go.opentelemetry.io/otel/propagation"
10 |
11 | "gofr.dev/pkg/gofr/version"
12 | )
13 |
14 | // Tracer is a middleware that starts a new OpenTelemetry trace span for each request.
15 | func Tracer(inner http.Handler) http.Handler {
16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 | // Start context and Tracing
18 | ctx := r.Context()
19 |
20 | // extract the traceID and spanID from the headers and create a new context for the same
21 | // this context will make a new span using the traceID and link the incoming SpanID as
22 | // its parentID, thus connecting two spans
23 | ctx = otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header))
24 |
25 | tr := otel.GetTracerProvider().Tracer("gofr-" + version.Framework)
26 | ctx, span := tr.Start(ctx, fmt.Sprintf("%s %s", strings.ToUpper(r.Method), r.URL.Path))
27 |
28 | defer span.End()
29 |
30 | inner.ServeHTTP(w, r.WithContext(ctx))
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/tracer_test.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "go.opentelemetry.io/otel"
9 | "go.opentelemetry.io/otel/sdk/trace"
10 | otelTrace "go.opentelemetry.io/otel/trace"
11 | )
12 |
13 | type MockHandlerForTracing struct{}
14 |
15 | // ServeHTTP is used for testing if the request context has traceId.
16 | func (*MockHandlerForTracing) ServeHTTP(w http.ResponseWriter, req *http.Request) {
17 | traceID := otelTrace.SpanFromContext(req.Context()).SpanContext().TraceID().String()
18 | _, _ = w.Write([]byte(traceID))
19 | }
20 |
21 | func TestTrace(_ *testing.T) {
22 | tp := trace.NewTracerProvider()
23 | otel.SetTracerProvider(tp)
24 |
25 | handler := Tracer(&MockHandlerForTracing{})
26 | req := httptest.NewRequest(http.MethodGet, "/dummy", http.NoBody)
27 |
28 | recorder := httptest.NewRecorder()
29 |
30 | handler.ServeHTTP(recorder, req)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/validate.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import "strings"
4 |
5 | func isWellKnown(path string) bool {
6 | return strings.HasPrefix(path, "/.well-known")
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/validate_test.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func Test_isWellKnown(t *testing.T) {
10 | tests := []struct {
11 | desc string
12 | endpoint string
13 | resp bool
14 | }{
15 | {"empty endpoint", "", false},
16 | {"sample endpoint", "/sample", false},
17 | {"health-check endpoint", "/.well-known/health-check", true},
18 | {"alive endpoint", "/.well-known/alive", true},
19 | }
20 |
21 | for i, tc := range tests {
22 | resp := isWellKnown(tc.endpoint)
23 |
24 | assert.Equal(t, tc.resp, resp, "TEST[%d], Failed.\n%s", i, tc.desc)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/gofr/http/middleware/web_socket.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | gorillaWebsocket "github.com/gorilla/websocket"
8 |
9 | "gofr.dev/pkg/gofr/container"
10 | "gofr.dev/pkg/gofr/websocket"
11 | )
12 |
13 | // WSHandlerUpgrade middleware upgrades the incoming http request to a websocket connection using websocket upgrader.
14 | func WSHandlerUpgrade(c *container.Container, wsManager *websocket.Manager) func(inner http.Handler) http.Handler {
15 | return func(inner http.Handler) http.Handler {
16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 | if gorillaWebsocket.IsWebSocketUpgrade(r) {
18 | conn, err := wsManager.WebSocketUpgrader.Upgrade(w, r, nil)
19 | if err != nil {
20 | c.Errorf("Failed to upgrade to WebSocket: %v", err)
21 | http.Error(w, "Could not open WebSocket connection", http.StatusBadRequest)
22 |
23 | return
24 | }
25 |
26 | // Add the connection to the hub
27 | wsManager.AddWebsocketConnection(r.Header.Get("Sec-WebSocket-Key"), &websocket.Connection{Conn: conn})
28 |
29 | // Store the websocket connection key in the context
30 | ctx := context.WithValue(r.Context(), websocket.WSConnectionKey, r.Header.Get("Sec-WebSocket-Key"))
31 | r = r.WithContext(ctx)
32 | }
33 |
34 | inner.ServeHTTP(w, r)
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/gofr/http/response/file.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | type File struct {
4 | Content []byte
5 | ContentType string
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/gofr/http/response/raw.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | type Raw struct {
4 | Data any
5 | }
6 |
--------------------------------------------------------------------------------
/pkg/gofr/http/response/redirect.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | type Redirect struct {
4 | URL string
5 | }
6 |
--------------------------------------------------------------------------------
/pkg/gofr/http/response/response.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type Response struct {
8 | Data any `json:"data"`
9 | Metadata map[string]any `json:"metadata,omitempty"`
10 | Headers map[string]string `json:"-"`
11 | }
12 |
13 | func (resp Response) SetCustomHeaders(w http.ResponseWriter) {
14 | for key, value := range resp.Headers {
15 | w.Header().Set(key, value)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/gofr/http/response/template.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | import (
4 | "html/template"
5 | "io"
6 | )
7 |
8 | type Template struct { // Named as such to avoid conflict with imported template
9 | Data any
10 | Name string
11 | }
12 |
13 | func (t *Template) Render(w io.Writer) {
14 | tmpl := template.Must(template.ParseFiles("./templates/" + t.Name))
15 | _ = tmpl.Execute(w, t.Data)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/gofr/metrics/errors.go:
--------------------------------------------------------------------------------
1 | // Package metrics provides functionalities for instrumenting GoFr applications with metrics.
2 | package metrics
3 |
4 | import "fmt"
5 |
6 | type metricsAlreadyRegistered struct {
7 | metricsName string
8 | }
9 |
10 | type metricsNotRegistered struct {
11 | metricsName string
12 | }
13 |
14 | func (e metricsAlreadyRegistered) Error() string {
15 | return fmt.Sprintf("Metrics %v already registered", e.metricsName)
16 | }
17 |
18 | func (e metricsNotRegistered) Error() string {
19 | return fmt.Sprintf("Metrics %v is not registered", e.metricsName)
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/gofr/metrics_server.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net/http"
8 | "time"
9 |
10 | "gofr.dev/pkg/gofr/container"
11 | "gofr.dev/pkg/gofr/metrics"
12 | )
13 |
14 | type metricServer struct {
15 | port int
16 | srv *http.Server
17 | }
18 |
19 | func newMetricServer(port int) *metricServer {
20 | return &metricServer{port: port}
21 | }
22 |
23 | func (m *metricServer) Run(c *container.Container) {
24 | if m != nil {
25 | c.Logf("Starting metrics server on port: %d", m.port)
26 |
27 | m.srv = &http.Server{
28 | Addr: fmt.Sprintf(":%d", m.port),
29 | Handler: metrics.GetHandler(c.Metrics()),
30 | ReadHeaderTimeout: 5 * time.Second,
31 | }
32 |
33 | err := m.srv.ListenAndServe()
34 |
35 | if !errors.Is(err, http.ErrServerClosed) {
36 | c.Errorf("error while listening to metrics server, err: %v", err)
37 | }
38 | }
39 | }
40 |
41 | func (m *metricServer) Shutdown(ctx context.Context) error {
42 | if m.srv == nil {
43 | return nil
44 | }
45 |
46 | return ShutdownWithContext(ctx, func(ctx context.Context) error {
47 | return m.srv.Shutdown(ctx)
48 | }, nil)
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/gofr/migration/datasource.go:
--------------------------------------------------------------------------------
1 | package migration
2 |
3 | import "gofr.dev/pkg/gofr/container"
4 |
5 | type Datasource struct {
6 | // TODO Logger should not be embedded rather it should be a field.
7 | // Need to think it through as it will bring breaking changes.
8 | Logger
9 |
10 | SQL SQL
11 | Redis Redis
12 | PubSub PubSub
13 | Clickhouse Clickhouse
14 | Cassandra Cassandra
15 | Mongo Mongo
16 | ArangoDB ArangoDB
17 | SurrealDB SurrealDB
18 | DGraph DGraph
19 | }
20 |
21 | // It is a base implementation for migration manager, on this other database drivers have been wrapped.
22 |
23 | func (*Datasource) checkAndCreateMigrationTable(*container.Container) error {
24 | return nil
25 | }
26 |
27 | func (*Datasource) getLastMigration(*container.Container) int64 {
28 | return 0
29 | }
30 |
31 | func (*Datasource) beginTransaction(*container.Container) transactionData {
32 | return transactionData{}
33 | }
34 |
35 | func (*Datasource) commitMigration(c *container.Container, data transactionData) error {
36 | c.Infof("Migration %v ran successfully", data.MigrationNumber)
37 |
38 | return nil
39 | }
40 |
41 | func (*Datasource) rollback(*container.Container, transactionData) {}
42 |
--------------------------------------------------------------------------------
/pkg/gofr/migration/datasource_test.go:
--------------------------------------------------------------------------------
1 | package migration
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 |
9 | "gofr.dev/pkg/gofr/container"
10 | "gofr.dev/pkg/gofr/testutil"
11 | )
12 |
13 | func Test_getMigratorDatastoreNotInitialised(t *testing.T) {
14 | logs := testutil.StdoutOutputForFunc(func() {
15 | mockContainer, _ := container.NewMockContainer(t)
16 | mockContainer.SQL = nil
17 | mockContainer.Redis = nil
18 |
19 | mg := Datasource{}
20 |
21 | mg.rollback(mockContainer, transactionData{})
22 |
23 | assert.Equal(t, int64(0), mg.getLastMigration(mockContainer), "TEST Failed \n Last Migration is not 0")
24 | require.NoError(t, mg.checkAndCreateMigrationTable(mockContainer), "TEST Failed")
25 | assert.Equal(t, transactionData{}, mg.beginTransaction(mockContainer), "TEST Failed")
26 | require.NoError(t, mg.commitMigration(mockContainer, transactionData{}), "TEST Failed")
27 | })
28 |
29 | assert.Contains(t, logs, "Migration 0 ran successfully", "TEST Failed")
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/gofr/migration/logger.go:
--------------------------------------------------------------------------------
1 | package migration
2 |
3 | type Logger interface {
4 | Debug(args ...any)
5 | Debugf(format string, args ...any)
6 | Info(args ...any)
7 | Infof(format string, args ...any)
8 | Notice(args ...any)
9 | Noticef(format string, args ...any)
10 | Warn(args ...any)
11 | Warnf(format string, args ...any)
12 | Error(args ...any)
13 | Errorf(format string, args ...any)
14 | Fatal(args ...any)
15 | Fatalf(format string, args ...any)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/gofr/request.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // Request is an interface which is written because it allows us
8 | // to create applications without being aware of the transport.
9 | // In both cmd or server application, this abstraction can be used.
10 | type Request interface {
11 | Context() context.Context
12 | Param(string) string
13 | PathParam(string) string
14 | Bind(any) error
15 | HostName() string
16 | Params(string) []string
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/gofr/responder.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | // Responder is used by the application to provide output. This is implemented for both
4 | // cmd and HTTP server application.
5 | type Responder interface {
6 | Respond(data any, err error)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/service/health_config.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "context"
4 |
5 | type HealthConfig struct {
6 | HealthEndpoint string
7 | Timeout int
8 | }
9 |
10 | func (h *HealthConfig) AddOption(svc HTTP) HTTP {
11 | // if timeout is not provided we set a convenient default timeout.
12 | if h.Timeout == 0 {
13 | h.Timeout = defaultTimeout
14 | }
15 |
16 | return &customHealthService{
17 | healthEndpoint: h.HealthEndpoint,
18 | timeout: h.Timeout,
19 | HTTP: svc,
20 | }
21 | }
22 |
23 | type customHealthService struct {
24 | healthEndpoint string
25 | timeout int
26 | HTTP
27 | }
28 |
29 | func (c *customHealthService) HealthCheck(ctx context.Context) *Health {
30 | return c.HTTP.getHealthResponseForEndpoint(ctx, c.healthEndpoint, c.timeout)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/service/logger.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "time"
7 | )
8 |
9 | type Logger interface {
10 | Log(args ...any)
11 | }
12 |
13 | type Log struct {
14 | Timestamp time.Time `json:"timestamp"`
15 | ResponseTime int64 `json:"latency"`
16 | CorrelationID string `json:"correlationId"`
17 | ResponseCode int `json:"responseCode"`
18 | HTTPMethod string `json:"httpMethod"`
19 | URI string `json:"uri"`
20 | }
21 |
22 | func (l *Log) PrettyPrint(writer io.Writer) {
23 | fmt.Fprintf(writer, "\u001B[38;5;8m%s \u001B[38;5;%dm%-6d\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s %s \n",
24 | l.CorrelationID, colorForStatusCode(l.ResponseCode),
25 | l.ResponseCode, l.ResponseTime, l.HTTPMethod, l.URI)
26 | }
27 |
28 | type ErrorLog struct {
29 | *Log
30 | ErrorMessage string `json:"errorMessage"`
31 | }
32 |
33 | func (el *ErrorLog) PrettyPrint(writer io.Writer) {
34 | fmt.Fprintf(writer, "\u001B[38;5;8m%s \u001B[38;5;%dm%-6d\u001B[0m %8d\u001B[38;5;8mµs\u001B[0m %s %s \n",
35 | el.CorrelationID, colorForStatusCode(el.ResponseCode),
36 | el.ResponseCode, el.ResponseTime, el.HTTPMethod, el.URI)
37 | }
38 |
39 | func colorForStatusCode(status int) int {
40 | const (
41 | blue = 34
42 | red = 202
43 | yellow = 220
44 | )
45 |
46 | switch {
47 | case status >= 200 && status < 300:
48 | return blue
49 | case status >= 400 && status < 500:
50 | return yellow
51 | case status >= 500 && status < 600:
52 | return red
53 | }
54 |
55 | return 0
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/gofr/service/logger_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestLog_PrettyPrint(t *testing.T) {
11 | w := new(bytes.Buffer)
12 |
13 | l := &Log{
14 | ResponseTime: 100,
15 | CorrelationID: "abc-test-correlation-id",
16 | ResponseCode: 200,
17 | HTTPMethod: "GET",
18 | URI: "/api/test",
19 | }
20 |
21 | l.PrettyPrint(w)
22 |
23 | assert.Equal(t, "\u001B[38;5;8mabc-test-correlation-id \u001B[38;5;34m200 \u001B[0m 100\u001B[38;5;8mµs\u001B[0m GET /api/test \n",
24 | w.String())
25 | }
26 |
27 | func TestErrorLog_PrettyPrint(t *testing.T) {
28 | w := new(bytes.Buffer)
29 |
30 | l := &ErrorLog{
31 | Log: &Log{
32 | ResponseTime: 100,
33 | CorrelationID: "abc-test-correlation-id",
34 | ResponseCode: 200,
35 | HTTPMethod: "GET",
36 | URI: "/api/test",
37 | },
38 | ErrorMessage: "some error occurred",
39 | }
40 |
41 | l.PrettyPrint(w)
42 |
43 | assert.Equal(t, "\u001B[38;5;8mabc-test-correlation-id \u001B[38;5;34m200 \u001B[0m 100\u001B[38;5;8mµs\u001B[0m GET /api/test \n",
44 | w.String())
45 | }
46 |
47 | func Test_ColorForStatusCode(t *testing.T) {
48 | testCases := []struct {
49 | desc string
50 | code int
51 | expOut int
52 | }{
53 | {desc: "200 OK", code: 200, expOut: 34},
54 | {desc: "201 Created", code: 201, expOut: 34},
55 | {desc: "400 Bad Request", code: 400, expOut: 220},
56 | {desc: "409 Conflict", code: 409, expOut: 220},
57 | {desc: "500 Internal Srv Error", code: 500, expOut: 202},
58 | }
59 |
60 | for _, tc := range testCases {
61 | out := colorForStatusCode(tc.code)
62 |
63 | assert.Equal(t, tc.expOut, out)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/gofr/service/metrics.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "context"
4 |
5 | type Metrics interface {
6 | RecordHistogram(ctx context.Context, name string, value float64, labels ...string)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/gofr/service/options.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | type Options interface {
4 | AddOption(h HTTP) HTTP
5 | }
6 |
--------------------------------------------------------------------------------
/pkg/gofr/service/response.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "net/http"
4 |
5 | type Response struct {
6 | Body []byte
7 | StatusCode int
8 | headers http.Header
9 | }
10 |
11 | func (r *Response) GetHeader(key string) string {
12 | if r.headers != nil {
13 | return r.headers.Get(key)
14 | }
15 |
16 | return ""
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/gofr/service/response_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestResponse_GetHeader(t *testing.T) {
11 | // Arrange
12 | headers := http.Header{}
13 | headers.Set("Content-Type", "application/json")
14 | response := &Response{
15 | headers: headers,
16 | }
17 |
18 | result := response.GetHeader("Content-Type")
19 | headerNotFound := response.GetHeader("key")
20 |
21 | assert.Equal(t, "application/json", result)
22 | assert.Empty(t, headerNotFound)
23 | }
24 |
25 | func TestResponse_GetHeaderNil(t *testing.T) {
26 | // Arrange
27 | response := &Response{}
28 |
29 | result := response.GetHeader("Content-Type")
30 |
31 | assert.Empty(t, result)
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/gofr/shutdown.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "time"
7 |
8 | "gofr.dev/pkg/gofr/config"
9 | )
10 |
11 | // ShutdownWithContext handles the shutdown process with context timeout.
12 | // It takes a shutdown function and a force close function as parameters.
13 | // If the context times out, the force close function is called.
14 | func ShutdownWithContext(ctx context.Context, shutdownFunc func(ctx context.Context) error, forceCloseFunc func() error) error {
15 | errCh := make(chan error, 1) // Channel to receive shutdown error
16 |
17 | go func() {
18 | errCh <- shutdownFunc(ctx) // Run shutdownFunc in a goroutine and send any error to errCh
19 | }()
20 |
21 | // Wait for either the context to be done or shutdownFunc to complete
22 | select {
23 | case <-ctx.Done(): // Context timeout reached
24 | err := ctx.Err()
25 |
26 | if forceCloseFunc != nil {
27 | err = errors.Join(err, forceCloseFunc()) // Attempt force close if available
28 | }
29 |
30 | return err
31 | case err := <-errCh:
32 | return err
33 | }
34 | }
35 |
36 | func getShutdownTimeoutFromConfig(cfg config.Config) (time.Duration, error) {
37 | value := cfg.GetOrDefault("SHUTDOWN_GRACE_PERIOD", "30s")
38 | if value == "" {
39 | return shutDownTimeout, nil
40 | }
41 |
42 | timeout, err := time.ParseDuration(value)
43 | if err != nil {
44 | return shutDownTimeout, err
45 | }
46 |
47 | return timeout, nil
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/gofr/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/pkg/gofr/static/favicon-16x16.png
--------------------------------------------------------------------------------
/pkg/gofr/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/pkg/gofr/static/favicon-32x32.png
--------------------------------------------------------------------------------
/pkg/gofr/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/pkg/gofr/static/favicon.ico
--------------------------------------------------------------------------------
/pkg/gofr/static/files.go:
--------------------------------------------------------------------------------
1 | package static
2 |
3 | import "embed"
4 |
5 | //go:embed *
6 |
7 | var Files embed.FS
8 |
--------------------------------------------------------------------------------
/pkg/gofr/static/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | overflow: -moz-scrollbars-vertical;
4 | overflow-y: scroll;
5 | }
6 |
7 | *,
8 | *:before,
9 | *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | background: #fafafa;
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/gofr/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/pkg/gofr/subscriber_test.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "gofr.dev/pkg/gofr/datasource"
9 | "gofr.dev/pkg/gofr/datasource/pubsub"
10 | "gofr.dev/pkg/gofr/datasource/pubsub/kafka"
11 | )
12 |
13 | var errSubscription = errors.New("subscription error")
14 |
15 | func subscriptionError(err string) error {
16 | return fmt.Errorf("%w: %s", errSubscription, err)
17 | }
18 |
19 | type mockSubscriber struct {
20 | }
21 |
22 | func (mockSubscriber) CreateTopic(_ context.Context, _ string) error {
23 | return nil
24 | }
25 |
26 | func (mockSubscriber) DeleteTopic(_ context.Context, _ string) error {
27 | return nil
28 | }
29 |
30 | func (mockSubscriber) Health() datasource.Health {
31 | return datasource.Health{}
32 | }
33 |
34 | func (mockSubscriber) Publish(_ context.Context, _ string, _ []byte) error {
35 | return nil
36 | }
37 |
38 | func (mockSubscriber) Subscribe(ctx context.Context, topic string) (*pubsub.Message, error) {
39 | msg := pubsub.NewMessage(ctx)
40 | msg.Topic = topic
41 | msg.Value = []byte(`{"data":{"productId":"123","price":"599"}}`)
42 |
43 | if topic == "test-topic" {
44 | return msg, nil
45 | } else if topic == "test-err" {
46 | return msg, kafka.ErrConsumerGroupNotProvided
47 | }
48 |
49 | return msg, subscriptionError("subscription error")
50 | }
51 |
52 | func (mockSubscriber) Close() error {
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/gofr/telemetry.go:
--------------------------------------------------------------------------------
1 | package gofr
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | func (a *App) hasTelemetry() bool {
10 | return a.Config.GetOrDefault("GOFR_TELEMETRY", defaultTelemetry) == "true"
11 | }
12 |
13 | func (a *App) sendTelemetry(client *http.Client, isStart bool) {
14 | url := fmt.Sprint(gofrHost, shutServerPing)
15 |
16 | if isStart {
17 | url = fmt.Sprint(gofrHost, startServerPing)
18 |
19 | a.container.Info("GoFr records the number of active servers. Set GOFR_TELEMETRY=false in configs to disable it.")
20 | }
21 |
22 | ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
23 | defer cancel()
24 |
25 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody)
26 | if err != nil {
27 | return
28 | }
29 |
30 | req.Header.Set("Connection", "close")
31 |
32 | resp, err := client.Do(req)
33 | if err != nil {
34 | return
35 | }
36 |
37 | resp.Body.Close()
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/gofr/testutil/error.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | type CustomError struct {
4 | ErrorMessage string
5 | }
6 |
7 | func (c CustomError) Error() string {
8 | return c.ErrorMessage
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/gofr/testutil/error_test.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestMain(m *testing.M) {
11 | os.Setenv("GOFR_TELEMETRY", "false")
12 | m.Run()
13 | }
14 |
15 | func Test_CustomError(t *testing.T) {
16 | err := CustomError{ErrorMessage: "my error"}
17 |
18 | assert.Contains(t, err.ErrorMessage, "my error")
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/gofr/testutil/os.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "io"
5 | "os"
6 | )
7 |
8 | func StdoutOutputForFunc(f func()) string {
9 | r, w, _ := os.Pipe()
10 | old := os.Stdout
11 | os.Stdout = w
12 |
13 | f()
14 |
15 | _ = w.Close()
16 |
17 | out, _ := io.ReadAll(r)
18 | os.Stdout = old
19 |
20 | return string(out)
21 | }
22 |
23 | func StderrOutputForFunc(f func()) string {
24 | r, w, _ := os.Pipe()
25 | old := os.Stderr
26 | os.Stderr = w
27 |
28 | f()
29 |
30 | _ = w.Close()
31 |
32 | out, _ := io.ReadAll(r)
33 | os.Stderr = old
34 |
35 | return string(out)
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/gofr/testutil/os_test.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestStdoutOutputForFunc(t *testing.T) {
10 | msg := "Hello Stdout!"
11 |
12 | out := StdoutOutputForFunc(func() {
13 | fmt.Fprint(os.Stdout, msg)
14 | })
15 |
16 | if out != msg {
17 | t.Errorf("Expected: %s got: %s", msg, out)
18 | }
19 | }
20 |
21 | func TestStderrOutputForFunc(t *testing.T) {
22 | msg := "Hello Stderr!"
23 |
24 | out := StderrOutputForFunc(func() {
25 | fmt.Fprint(os.Stderr, msg)
26 | })
27 |
28 | if out != msg {
29 | t.Errorf("Expected: %s got: %s", msg, out)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/gofr/testutil/test.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gofr-dev/gofr/eeaa7597f985a9714e8fc52957dbe586bc9a7283/pkg/gofr/testutil/test.zip
--------------------------------------------------------------------------------
/pkg/gofr/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | const Framework = "dev"
4 |
--------------------------------------------------------------------------------
/pkg/gofr/websocket/interfaces.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gorilla/websocket"
7 | )
8 |
9 | // Upgrader interface for upgrading HTTP connections to WebSocket connections.
10 | type Upgrader interface {
11 | Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error)
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/gofr/websocket/options.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/gorilla/websocket"
8 | )
9 |
10 | // Options is a function type that applies a configuration to the concrete Upgrader.
11 | type Options func(u *websocket.Upgrader)
12 |
13 | // WithHandshakeTimeout sets the HandshakeTimeout option.
14 | func WithHandshakeTimeout(t time.Duration) Options {
15 | return func(u *websocket.Upgrader) {
16 | u.HandshakeTimeout = t
17 | }
18 | }
19 |
20 | // WithReadBufferSize sets the ReadBufferSize option.
21 | func WithReadBufferSize(size int) Options {
22 | return func(u *websocket.Upgrader) {
23 | u.ReadBufferSize = size
24 | }
25 | }
26 |
27 | // WithWriteBufferSize sets the WriteBufferSize option.
28 | func WithWriteBufferSize(size int) Options {
29 | return func(u *websocket.Upgrader) {
30 | u.WriteBufferSize = size
31 | }
32 | }
33 |
34 | // WithSubprotocols sets the Subprotocols option.
35 | func WithSubprotocols(subprotocols ...string) Options {
36 | return func(u *websocket.Upgrader) {
37 | u.Subprotocols = subprotocols
38 | }
39 | }
40 |
41 | // WithError sets the Error handler option.
42 | func WithError(fn func(w http.ResponseWriter, r *http.Request, status int, reason error)) Options {
43 | return func(u *websocket.Upgrader) {
44 | u.Error = fn
45 | }
46 | }
47 |
48 | // WithCheckOrigin sets the CheckOrigin handler option.
49 | func WithCheckOrigin(fn func(r *http.Request) bool) Options {
50 | return func(u *websocket.Upgrader) {
51 | u.CheckOrigin = fn
52 | }
53 | }
54 |
55 | // WithCompression enables compression.
56 | func WithCompression() Options {
57 | return func(u *websocket.Upgrader) {
58 | u.EnableCompression = true
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/typos.toml:
--------------------------------------------------------------------------------
1 | [default]
2 | locale = "en-us"
3 | extend-ignore-re=[
4 | # UID and equivalent
5 | "[A-Za-z0-9=_-]{30,}",
6 |
7 | # False positive managements with inline comments
8 | #
9 | # disable spellchecker for current line with a comment mentioning "spellchecker:disable-line"
10 | "(?Rm)^.*(|\n)?$",
11 | # disable a block from a "spellchecker:on" comment to "spellchecker:off" one
12 | "(?s)(|[^\n]*\n).*?(