├── .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 | 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).*?(