Skip to content

Commit e2db596

Browse files
wheinzewolveix
andauthored
feat(humafiber): add Fiber v3 support (#962)
* feat(humafiber): add Fiber v3 support * docs(humafiber): leave out version info for "latest" in docs * feat(humafiber): replace fasthttp's deprecated `VisitAll` usage * ci: remove Go 1.24 from test matrix * Fix deps * Bump deps --------- Co-authored-by: Robert Thomas <31854736+wolveix@users.noreply.github.com>
1 parent b2d8a3a commit e2db596

9 files changed

Lines changed: 618 additions & 85 deletions

File tree

adapters/adapters_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import (
2020
"github.com/danielgtaylor/huma/v2/humatest"
2121
"github.com/gin-gonic/gin"
2222
"github.com/go-chi/chi/v5"
23-
"github.com/gofiber/fiber/v2"
23+
fiberV2 "github.com/gofiber/fiber/v2"
24+
"github.com/gofiber/fiber/v3"
2425
"github.com/gorilla/mux"
2526
"github.com/julienschmidt/httprouter"
2627
"github.com/labstack/echo/v4"
@@ -103,7 +104,8 @@ func TestAdapters(t *testing.T) {
103104
assert.Equal(t, 1, v.ProtoMajor)
104105
assert.Equal(t, 1, v.ProtoMinor)
105106
} else {
106-
assert.Equal(t, "http", v.Proto)
107+
// Fiber adapters (both v2 and v3) don't populate ProtoMajor/ProtoMinor
108+
assert.Contains(t, []string{"http", "HTTP/1.1"}, v.Proto)
107109
}
108110

109111
// Make sure huma.WithValue works correctly
@@ -131,6 +133,9 @@ func TestAdapters(t *testing.T) {
131133
{"fiber", func() huma.API {
132134
return wrap(humafiber.New(fiber.New(), config()), true, func(ctx huma.Context) { humafiber.Unwrap(ctx) })
133135
}},
136+
{"fiber-v2", func() huma.API {
137+
return wrap(humafiber.NewV2(fiberV2.New(), config()), true, func(ctx huma.Context) { humafiber.UnwrapV2(ctx) })
138+
}},
134139
{"go", func() huma.API {
135140
return wrap(humago.New(http.NewServeMux(), config()), false, func(ctx huma.Context) { humago.Unwrap(ctx) })
136141
}},

adapters/humafiber/humafiber.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import (
1212
"time"
1313

1414
"github.com/danielgtaylor/huma/v2"
15-
"github.com/gofiber/fiber/v2"
15+
"github.com/gofiber/fiber/v3"
1616
)
1717

1818
// Unwrap extracts the underlying Fiber context from a Huma context. If passed a
1919
// context from a different adapter it will panic. Keep in mind the limitations
2020
// of the underlying Fiber/fasthttp libraries and how that impacts
2121
// memory-safety: https://docs.gofiber.io/#zero-allocation. Do not keep
2222
// references to the underlying context or its values!
23-
func Unwrap(ctx huma.Context) *fiber.Ctx {
23+
func Unwrap(ctx huma.Context) fiber.Ctx {
2424
for {
2525
if c, ok := ctx.(interface{ Unwrap() huma.Context }); ok {
2626
ctx = c.Unwrap()
@@ -42,14 +42,14 @@ type fiberAdapter struct {
4242
type fiberWrapper struct {
4343
op *huma.Operation
4444
status int
45-
orig *fiber.Ctx
45+
orig fiber.Ctx
4646
ctx context.Context
4747
}
4848

49-
// check that fiberCtx implements huma.Context
49+
// check that fiberWrapper implements huma.Context
5050
var _ huma.Context = &fiberWrapper{}
5151

52-
func (c *fiberWrapper) Unwrap() *fiber.Ctx {
52+
func (c *fiberWrapper) Unwrap() fiber.Ctx {
5353
return c.orig
5454
}
5555

@@ -74,7 +74,7 @@ func (c *fiberWrapper) Host() string {
7474
}
7575

7676
func (c *fiberWrapper) RemoteAddr() string {
77-
return c.orig.Context().RemoteAddr().String()
77+
return c.orig.RequestCtx().RemoteAddr().String()
7878
}
7979

8080
func (c *fiberWrapper) URL() url.URL {
@@ -120,7 +120,7 @@ func (c *fiberWrapper) SetReadDeadline(deadline time.Time) error {
120120
// 2. Set the Fiber app's `BodyLimit` to some small value like `1`
121121
// Fiber will only call the request handler for streaming once the limit is
122122
// reached. This is annoying but currently how things work.
123-
return c.orig.Context().Conn().SetReadDeadline(deadline)
123+
return c.orig.RequestCtx().Conn().SetReadDeadline(deadline)
124124
}
125125

126126
func (c *fiberWrapper) SetStatus(code int) {
@@ -132,6 +132,7 @@ func (c *fiberWrapper) SetStatus(code int) {
132132
func (c *fiberWrapper) Status() int {
133133
return c.status
134134
}
135+
135136
func (c *fiberWrapper) AppendHeader(name string, value string) {
136137
c.orig.Append(name, value)
137138
}
@@ -141,11 +142,11 @@ func (c *fiberWrapper) SetHeader(name string, value string) {
141142
}
142143

143144
func (c *fiberWrapper) BodyWriter() io.Writer {
144-
return c.orig.Context()
145+
return c.orig.RequestCtx()
145146
}
146147

147148
func (c *fiberWrapper) TLS() *tls.ConnectionState {
148-
return c.orig.Context().TLSConnectionState()
149+
return c.orig.RequestCtx().TLSConnectionState()
149150
}
150151

151152
func (c *fiberWrapper) Version() huma.ProtoVersion {
@@ -155,11 +156,11 @@ func (c *fiberWrapper) Version() huma.ProtoVersion {
155156
}
156157

157158
type router interface {
158-
Add(method, path string, handlers ...fiber.Handler) fiber.Router
159+
Add(methods []string, path string, handler any, handlers ...any) fiber.Router
159160
}
160161

161162
type requestTester interface {
162-
Test(*http.Request, ...int) (*http.Response, error)
163+
Test(*http.Request, ...fiber.TestConfig) (*http.Response, error)
163164
}
164165

165166
type contextWrapperValue struct {
@@ -194,9 +195,9 @@ func (a *fiberAdapter) Handle(op *huma.Operation, handler func(huma.Context)) {
194195
path := op.Path
195196
path = strings.ReplaceAll(path, "{", ":")
196197
path = strings.ReplaceAll(path, "}", "")
197-
a.router.Add(op.Method, path, func(c *fiber.Ctx) error {
198+
a.router.Add([]string{op.Method}, path, func(c fiber.Ctx) error {
198199
var values []*contextWrapperValue
199-
c.Context().VisitUserValuesAll(func(key, value any) {
200+
c.RequestCtx().VisitUserValuesAll(func(key, value any) {
200201
values = append(values, &contextWrapperValue{
201202
Key: key,
202203
Value: value,
@@ -207,16 +208,14 @@ func (a *fiberAdapter) Handle(op *huma.Operation, handler func(huma.Context)) {
207208
orig: c,
208209
ctx: &contextWrapper{
209210
values: values,
210-
Context: c.UserContext(),
211+
Context: c.Context(),
211212
},
212213
})
213214
return nil
214215
})
215216
}
216217

217218
func (a *fiberAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
218-
// b, _ := httputil.DumpRequest(r, true)
219-
// fmt.Println(string(b))
220219
resp, err := a.tester.Test(r)
221220
if resp != nil && resp.Body != nil {
222221
defer func() {
@@ -236,10 +235,12 @@ func (a *fiberAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
236235
_, _ = io.Copy(w, resp.Body)
237236
}
238237

238+
// New creates a new Huma API using the Fiber adapter.
239239
func New(r *fiber.App, config huma.Config) huma.API {
240240
return huma.NewAPI(config, &fiberAdapter{tester: r, router: r})
241241
}
242242

243+
// NewWithGroup creates a new Huma API using the Fiber adapter with a route group.
243244
func NewWithGroup(r *fiber.App, g fiber.Router, config huma.Config) huma.API {
244245
return huma.NewAPI(config, &fiberAdapter{tester: r, router: g})
245246
}

adapters/humafiber/humafiber_context_test.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
"github.com/danielgtaylor/huma/v2"
1818
"github.com/danielgtaylor/huma/v2/adapters/humafiber"
19-
"github.com/gofiber/fiber/v2"
19+
"github.com/gofiber/fiber/v3"
2020
"github.com/stretchr/testify/assert"
2121
"github.com/stretchr/testify/require"
2222
)
@@ -64,7 +64,7 @@ const (
6464
HelloPath = "/hello"
6565
)
6666

67-
func PingHandler(c *fiber.Ctx) error {
67+
func PingHandler(c fiber.Ctx) error {
6868
return c.SendStatus(fiber.StatusOK)
6969
}
7070

@@ -192,21 +192,21 @@ func HelloResponseValidate(t *testing.T, expected HelloResponseBody, response *h
192192
}
193193
}
194194

195-
func FiberMiddlewareUserValue(c *fiber.Ctx) error {
195+
func FiberMiddlewareUserValue(c fiber.Ctx) error {
196196
headers := c.GetReqHeaders()
197197
if values, found := headers[HeaderNameFiberUserValue]; found && len(values) > 0 {
198-
c.Context().SetUserValue(contextValueFiberUserValue, values[0])
198+
c.Locals(contextValueFiberUserValue, values[0])
199199
}
200200
return c.Next()
201201
}
202202

203-
func FiberMiddlewareUserContext(c *fiber.Ctx) error {
203+
func FiberMiddlewareUserContext(c fiber.Ctx) error {
204204
headers := c.GetReqHeaders()
205205
if values, found := headers[HeaderNameFiberUserContext]; found && len(values) > 0 {
206-
var original = c.UserContext()
206+
var original = c.Context()
207207
var result = context.WithValue(original, contextValueFiberUserContext, values[0])
208-
c.SetUserContext(result)
209-
defer c.SetUserContext(original)
208+
c.SetContext(result)
209+
defer c.SetContext(original)
210210
}
211211
return c.Next()
212212
}
@@ -236,9 +236,7 @@ func TestHumaFiber(t *testing.T) {
236236
require.NotZero(t, port)
237237
server := fmt.Sprintf("http://localhost:%d", port)
238238

239-
app := fiber.New(fiber.Config{
240-
DisableStartupMessage: true,
241-
})
239+
app := fiber.New()
242240
app.Use(FiberMiddlewareUserValue)
243241
app.Use(FiberMiddlewareUserContext)
244242
RegisterPing(app)

adapters/humafiber/humafiber_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"testing"
77

88
"github.com/danielgtaylor/huma/v2"
9-
"github.com/gofiber/fiber/v2"
9+
"github.com/gofiber/fiber/v3"
1010
)
1111

1212
func BenchmarkHumaFiber(b *testing.B) {
@@ -48,7 +48,7 @@ func BenchmarkNotHuma(b *testing.B) {
4848

4949
r := fiber.New()
5050

51-
r.Get("/foo/:id", func(c *fiber.Ctx) error {
51+
r.Get("/foo/:id", func(c fiber.Ctx) error {
5252
return c.JSON(&GreetingOutput{"Hello, " + c.Params("id")})
5353
})
5454

0 commit comments

Comments
 (0)