Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📚 Doc: Add how to use NewCtxFunc and middleware at the same time #3328

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1941,3 +1941,101 @@ func Benchmark_Ctx_AcquireReleaseFlow(b *testing.B) {
}
})
}

type Response struct {
Data any `json:"data"`
Message string `json:"message"`
Code int `json:"code"`
}

type testCustomCtx struct {
*DefaultCtx
}

func (c *testCustomCtx) JSON(data any, ctype ...string) error {
return c.DefaultCtx.JSON(Response{
Code: StatusOK,
Data: data,
Message: utils.StatusMessage(StatusOK),
}, ctype...)
}

func (c *testCustomCtx) Next() error {
// Increment handler index
c.indexHandler++

// Did we execute all route handlers?
if c.indexHandler < len(c.route.Handlers) {
// Continue route stack
return c.route.Handlers[c.indexHandler](c)
}

// Continue handler stack
_, err := c.app.nextCustom(c)
return err
}

func Test_App_CustomCtx_With_Use(t *testing.T) {
t.Parallel()

t.Run("without middleware", func(t *testing.T) {
t.Parallel()

app := New()
app.NewCtxFunc(func(app *App) CustomCtx {
return &testCustomCtx{
DefaultCtx: NewDefaultCtx(app),
}
})

app.Get("/test", func(c Ctx) error {
return c.JSON(Map{"a": "b"})
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)

body, err := io.ReadAll(resp.Body)
require.NoError(t, err)

expected := `{"data":{"a":"b"},"message":"OK","code":200}`
require.Equal(t, expected, string(body))
})

t.Run("with middleware", func(t *testing.T) {
t.Parallel()

app := New()
app.NewCtxFunc(func(app *App) CustomCtx {
return &testCustomCtx{
DefaultCtx: NewDefaultCtx(app),
}
})

app.Use(func(c Ctx) error {
t.Logf("Before Next() - context type: %T", c)
err := c.Next()
t.Logf("After Next() - context type: %T", c)
return err
})

app.Get("/test", func(c Ctx) error {
if _, ok := c.(*testCustomCtx); !ok {
t.Logf("Handler received context of type: %T", c)
}
return c.JSON(Map{"a": "b"})
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)

body, err := io.ReadAll(resp.Body)
require.NoError(t, err)

expected := `{"data":{"a":"b"},"message":"OK","code":200}`
require.Equal(t, expected, string(body),
"Custom context JSON format is lost when using middleware")
})
}
50 changes: 50 additions & 0 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,56 @@ func main() {
}
```

:::tip Using with Middleware
When using `NewCtxFunc` together with middleware (`app.Use`), you need to override the `Next()` method in your custom context to preserve the context type throughout the middleware chain:

```go
type CustomCtx struct {
fiber.DefaultCtx
}

// Override Next method to preserve CustomCtx type
func (c *CustomCtx) Next() error {
// Increment handler index
c.indexHandler++

// Did we execute all route handlers?
if c.indexHandler < len(c.route.Handlers) {
// Continue route stack
return c.route.Handlers[c.indexHandler](c)
}

// Continue handler stack
_, err := c.app.nextCustom(c)
return err
}

func main() {
app := fiber.New()

// Set custom context
app.NewCtxFunc(func(app *fiber.App) fiber.CustomCtx {
return &CustomCtx{
DefaultCtx: *fiber.NewDefaultCtx(app),
}
})

// Use middleware
app.Use(func(c fiber.Ctx) error {
fmt.Printf("Context type in middleware: %T\n", c)
return c.Next()
})

// Route handler
app.Get("/", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{"hello": "world"})
})
}
```

This ensures that your custom context type is maintained throughout the entire request lifecycle, including middleware execution.
:::

## RegisterCustomBinder

You can register custom binders to use with [`Bind().Custom("name")`](bind.md#custom). They should be compatible with the `CustomBinder` interface.
Expand Down
14 changes: 10 additions & 4 deletions middleware/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,18 +501,24 @@ func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) {
// go test -race -run Test_Proxy_Do_WithRealURL
func Test_Proxy_Do_WithRealURL(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("mock response")
})

app := fiber.New()
app.Get("/test", func(c fiber.Ctx) error {
return Do(c, "https://www.google.com")
return Do(c, "http://"+addr)
})

resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
require.NoError(t, err1)
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
require.Equal(t, "/test", resp.Request.URL.String())

body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "https://www.google.com/")
require.Equal(t, "mock response", string(body))
}

// go test -race -run Test_Proxy_Do_WithRedirect
Expand Down
Loading