Skip to content

Commit

Permalink
Release v1.27.0
Browse files Browse the repository at this point in the history
  • Loading branch information
aryanmehrotra authored Nov 15, 2024
2 parents f87b8c1 + 937f49e commit 9990977
Show file tree
Hide file tree
Showing 29 changed files with 404 additions and 30 deletions.
60 changes: 58 additions & 2 deletions docs/advanced-guide/middlewares/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ The CORS middleware provides the following overridable configs:

By adding custom middleware to your GoFr application, user can easily extend its functionality and implement
cross-cutting concerns in a modular and reusable way.
User can use the `UseMiddleware` method on your GoFr application instance to register your custom middleware.
User can use the `UseMiddleware` or `UseMiddlewareWithContainer` method on your GoFr application instance to register your custom middleware.

### Example:
### Using UseMiddleware method for Custom Middleware
The UseMiddleware method is ideal for simple middleware that doesn't need direct access to the application's container.

#### Example:

```go
import (
Expand Down Expand Up @@ -64,3 +67,56 @@ func main() {
}
```

### Using UseMiddlewareWithContainer for Custom Middleware with Container Access

The UseMiddlewareWithContainer method allows middleware to access the application's container, providing access to
services like logging, configuration, and databases. This method is especially useful for middleware that needs access
to resources in the container to modify request processing flow.

#### Example:

```go
import (
"fmt"
"net/http"

"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/container"
)

func main() {
// Create a new GoFr application instance
a := gofr.New()

// Add custom middleware with container access
a.UseMiddlewareWithContainer(customMiddleware)

// Define the application's routes
a.GET("/hello", HelloHandler)

// Run the application
a.Run()
}

// Define middleware with container access
func customMiddleware(c *container.Container, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Logger.Log("Hey! Welcome to GoFr")

// Continue with the request processing
handler.ServeHTTP(w, r)
})
}

// Sample handler function
func HelloHandler(c *gofr.Context) (interface{}, error) {
name := c.Param("name")
if name == "" {
c.Log("Name came empty")
name = "World"
}

return fmt.Sprintf("Hello %s!", name), nil
}
```

30 changes: 26 additions & 4 deletions docs/quick-start/add-rest-handlers/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,31 @@ In this example, we define a user struct representing a database entity. The `Ge
This method can be used to implement custom logic for filtering, sorting, or retrieving additional data along with the entities.


> Few Points to consider:
> 1. The struct should always be passed by reference in the method `AddRESTHandlers`.
> 2. Field Naming Convention: GoFr assumes the struct fields in snake-case match the database column names. For example, `IsEmployed` field in the struct matches `is_employed` column in the database, `Age` field matches `age` column, etc.
> 3. Primary Key: The first field of the struct is typically used as the primary key for data operations. However, user can customize this behavior using GoFr's features.
## Few Points to Consider:

**1. Passing Struct by Reference**

The struct should always be passed by reference in the method `AddRESTHandlers`.

**2. Field Naming Convention**

GoFr assumes that struct fields in snake_case match the database column names.

* For example, the `IsEmployed` field in the struct matches the `is_employed` column in the database.
* Similarly, the `Age` field matches the `age` column.

**3. Primary Key**

The first field of the struct is typically used as the primary key for data operations. However, this behavior can be customized using GoFr's features.

**4. Datatype Conversions**

| Go Type | SQL Type | Description |
|---|---|---|
| `uuid.UUID` (from `github.com/google/uuid` or `github.com/satori/go.uuid`) | `CHAR(36)` or `VARCHAR(36)` | UUIDs are typically stored as 36-character strings in SQL databases. |
| `string` | `VARCHAR(n)` or `TEXT` | Use `VARCHAR(n)` for fixed-length strings, while `TEXT` is for longer, variable-length strings. |
| `int`, `int32`, `int64`, `uint`, `uint32`, `uint64` | `INT`, `BIGINT`, `SMALLINT`, `TINYINT`, `INTEGER` | Use `INT` for general integer values, `BIGINT` for large values, and `SMALLINT` or `TINYINT` for smaller ranges. |
| `bool` | `BOOLEAN` or `TINYINT(1)` | Use `BOOLEAN` (supported by most SQL databases like PostgreSQL, MySQL) or `TINYINT(1)` in MySQL (where `0` is false, and `1` is true). |
| `float32`, `float64` | `FLOAT`, `DOUBLE`, `DECIMAL` | Use `DECIMAL` for precise decimal numbers (e.g., financial data), `FLOAT` or `DOUBLE` for approximate floating-point numbers. |
| `time.Time` | `DATE`, `TIME`, `DATETIME`, `TIMESTAMP` | Use `DATE` for just the date, `TIME` for the time of day, and `DATETIME` or `TIMESTAMP` for both date and time. |
> #### Check out the example on how to add REST Handlers in GoFr: [Visit Github](https://github.com/gofr-dev/gofr/tree/main/examples/using-add-rest-handlers)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.30.0
go.opentelemetry.io/otel/trace v1.31.0
go.uber.org/mock v0.5.0
golang.org/x/oauth2 v0.23.0
golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.8.0
golang.org/x/term v0.25.0
golang.org/x/text v0.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/cassandra/cassandra_batch_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build exclude
// +build exclude

package cassandra

Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/cassandra/cassandra_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build exclude
// +build exclude

package cassandra

Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/cassandra/mock_interfaces.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/cassandra/mock_logger.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/cassandra/mock_metrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/clickhouse/clickhouse_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build exclude
// +build exclude

package clickhouse

Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/clickhouse/mock_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/clickhouse/mock_logger.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/clickhouse/mock_metrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pkg/gofr/datasource/file/ftp/file_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build exclude
// +build exclude

package ftp

Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/file/ftp/fs_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build exclude
// +build exclude

package ftp

Expand Down
1 change: 0 additions & 1 deletion pkg/gofr/datasource/file/ftp/mock_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/gofr/datasource/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ package datasource
type Logger interface {
Debug(args ...interface{})
Debugf(format string, args ...interface{})
Log(args ...interface{})
Logf(format string, args ...interface{})
Info(args ...interface{})
Infof(format string, args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
Warn(args ...interface{})
Expand Down
2 changes: 1 addition & 1 deletion pkg/gofr/datasource/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewClient(c config.Config, logger datasource.Logger, metrics Metrics) *Redi
logger.Errorf("could not add tracing instrumentation, error: %s", err)
}

logger.Logf("connected to redis at %s:%d", redisConfig.HostName, redisConfig.Port)
logger.Infof("connected to redis at %s:%d", redisConfig.HostName, redisConfig.Port)
} else {
logger.Errorf("could not connect to redis at '%s:%d', error: %s", redisConfig.HostName, redisConfig.Port, err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/gofr/datasource/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func retryConnection(database *DB) {

for {
if database.DB.Ping() != nil {
database.logger.Log("retrying SQL database connection")
database.logger.Info("retrying SQL database connection")

for {
err := database.DB.Ping()
Expand Down Expand Up @@ -208,9 +208,9 @@ func pushDBMetrics(db *sql.DB, metrics Metrics) {

func printConnectionSuccessLog(status string, dbconfig *DBConfig, logger datasource.Logger) {
if dbconfig.Dialect == sqlite {
logger.Debugf("%s to '%s' database", status, dbconfig.Database)
logger.Infof("%s to '%s' database", status, dbconfig.Database)
} else {
logger.Debugf("%s to '%s' user to '%s' database at '%s:%s'", status, dbconfig.User,
logger.Infof("%s to '%s' user to '%s' database at '%s:%s'", status, dbconfig.User,
dbconfig.Database, dbconfig.HostName, dbconfig.Port)
}
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/gofr/gofr.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,18 @@ func (a *App) UseMiddleware(middlewares ...gofrHTTP.Middleware) {
a.httpServer.router.UseMiddleware(middlewares...)
}

// UseMiddlewareWithContainer adds a middleware that has access to the container
// and wraps the provided handler with the middleware logic.
//
// The `middleware` function receives the container and the handler, allowing
// the middleware to modify the request processing flow.
func (a *App) UseMiddlewareWithContainer(middlewareHandler func(c *container.Container, handler http.Handler) http.Handler) {
a.httpServer.router.Use(func(h http.Handler) http.Handler {
// Wrap the provided handler `h` with the middleware function `middlewareHandler`
return middlewareHandler(a.container, h)
})
}

// AddCronJob registers a cron job to the cron table.
// The cron expression can be either a 5-part or 6-part format. The 6-part format includes an
// optional second field (in beginning) and others being minute, hour, day, month and day of week respectively.
Expand Down
51 changes: 51 additions & 0 deletions pkg/gofr/gofr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,57 @@ func Test_UseMiddleware(t *testing.T) {
assert.Equal(t, "applied", testHeaderValue, "Test_UseMiddleware Failed! header value mismatch.")
}

// Test the UseMiddlewareWithContainer function.
func TestUseMiddlewareWithContainer(t *testing.T) {
// Initialize the mock container
mockContainer := container.NewContainer(config.NewMockConfig(nil))

// Create a simple handler to test middleware functionality
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Hello, world!"))
})

// Middleware to modify response and test container access
middleware := func(c *container.Container, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Ensure the container is passed correctly (for this test, we are just logging)
assert.NotNil(t, c, "Container should not be nil in the middleware")

// Continue with the handler execution
handler.ServeHTTP(w, r)
})
}

// Create a new App with a mock server
app := &App{
httpServer: &httpServer{
router: gofrHTTP.NewRouter(),
port: 8001,
},
container: mockContainer,
Config: config.NewMockConfig(map[string]string{"REQUEST_TIMEOUT": "5"}),
}

// Use the middleware with the container
app.UseMiddlewareWithContainer(middleware)

// Register the handler to a route for testing
app.httpServer.router.Handle("/test", handler)

// Create a test request
req := httptest.NewRequest(http.MethodGet, "/test", http.NoBody)
// Create a test response recorder
rr := httptest.NewRecorder()

// Call the handler with the request and recorder
app.httpServer.router.ServeHTTP(rr, req)

// Assert the status code and response body
assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, "Hello, world!", rr.Body.String())
}

func Test_APIKeyAuthMiddleware(t *testing.T) {
c, _ := container.NewMockContainer(t)

Expand Down
1 change: 1 addition & 0 deletions pkg/gofr/migration/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Datasource struct {
PubSub PubSub
Clickhouse Clickhouse
Cassandra Cassandra
Mongo Mongo
}

// It is a base implementation for migration manager, on this other database drivers have been wrapped.
Expand Down
16 changes: 16 additions & 0 deletions pkg/gofr/migration/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ type Cassandra interface {
HealthCheck(ctx context.Context) (any, error)
}

// Mongo is an interface representing a MongoDB database client with common CRUD operations.
type Mongo interface {
Find(ctx context.Context, collection string, filter any, results any) error
FindOne(ctx context.Context, collection string, filter any, result any) error
InsertOne(ctx context.Context, collection string, document any) (any, error)
InsertMany(ctx context.Context, collection string, documents []any) ([]any, error)
DeleteOne(ctx context.Context, collection string, filter any) (int64, error)
DeleteMany(ctx context.Context, collection string, filter any) (int64, error)
UpdateByID(ctx context.Context, collection string, id any, update any) (int64, error)
UpdateOne(ctx context.Context, collection string, filter any, update any) error
UpdateMany(ctx context.Context, collection string, filter any, update any) (int64, error)
Drop(ctx context.Context, collection string) error
CreateCollection(ctx context.Context, name string) error
StartSession() (any, error)
}

// keeping the migrator interface unexported as, right now it is not being implemented directly, by the externalDB drivers.
// keeping the implementations for externalDB at one place such that if any change in migration logic, we would change directly here.
type migrator interface {
Expand Down
10 changes: 10 additions & 0 deletions pkg/gofr/migration/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@ func getMigrator(c *container.Container) (Datasource, migrator, bool) {
c.Debug("initialized data source for Cassandra")
}

if !isNil(c.Mongo) {
ok = true

ds.Mongo = mongoDS{c.Mongo}

mg = mongoDS{c.Mongo}.apply(mg)

c.Debug("initialized data source for Mongo")
}

return ds, mg, ok
}

Expand Down
Loading

0 comments on commit 9990977

Please sign in to comment.