Skip to content

Commit

Permalink
Implement Fluent Method Chaining for Status and Type Methods Using Ge…
Browse files Browse the repository at this point in the history
…nerics #3221
  • Loading branch information
ReneWerner87 committed Feb 3, 2025
1 parent 7dc7728 commit b695df6
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 42 deletions.
18 changes: 9 additions & 9 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ import (
const Version = "3.0.0-beta.4"

// Handler defines a function to serve HTTP requests.
type Handler = func(Ctx[any]) error
type Handler = func(ctx Ctx) error

type customCtxFunc = func(app *App[any]) CustomCtx[any]
type customCtxFunc = func(app *App[Ctx]) CustomCtx[Ctx]

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / lint

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / repeated

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 40 in app.go

View workflow job for this annotation

GitHub Actions / Compare

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

// Map is a shortcut for map[string]any, useful for JSON returns
type Map map[string]any
Expand Down Expand Up @@ -80,7 +80,7 @@ type Storage interface {
// return c.Status(code).SendString(err.Error())
// }
// app := fiber.New(cfg)
type ErrorHandler = func(Ctx[any], error) error
type ErrorHandler = func(Ctx, error) error

// Error represents an error that occurred while handling a request.
type Error struct {
Expand All @@ -89,7 +89,7 @@ type Error struct {
}

// App denotes the Fiber application.
type App[TCtx any] struct {
type App[TCtx CtxGeneric[TCtx]] struct {
// Ctx pool
pool sync.Pool
// Fasthttp server
Expand Down Expand Up @@ -472,7 +472,7 @@ var DefaultMethods = []string{
}

// DefaultErrorHandler that process return errors from handlers
func DefaultErrorHandler(c Ctx[any], err error) error {
func DefaultErrorHandler(c Ctx, err error) error {
code := StatusInternalServerError
var e *Error
if errors.As(err, &e) {
Expand All @@ -493,7 +493,7 @@ func DefaultErrorHandler(c Ctx[any], err error) error {
// ServerHeader: "Fiber",
// })
func New(config ...Config) *App[DefaultCtx] {

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / lint

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / repeated

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 495 in app.go

View workflow job for this annotation

GitHub Actions / Compare

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)
app := newApp[any](config...)
app := newApp[DefaultCtx](config...)

Check failure on line 496 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 496 in app.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 496 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 496 in app.go

View workflow job for this annotation

GitHub Actions / repeated

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

Check failure on line 496 in app.go

View workflow job for this annotation

GitHub Actions / Compare

DefaultCtx does not satisfy CtxGeneric[DefaultCtx] (method Accepts has pointer receiver)

// Init app
app.init()
Expand All @@ -519,7 +519,7 @@ func New(config ...Config) *App[DefaultCtx] {
// Prefork: true,
// ServerHeader: "Fiber",
// })
func NewWithCustomCtx[TCtx Ctx[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] {
func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc customCtxFunc, config ...Config) *App[TCtx] {
app := newApp[TCtx](config...)

// Set newCtxFunc
Expand All @@ -532,7 +532,7 @@ func NewWithCustomCtx[TCtx Ctx[TCtx]](newCtxFunc customCtxFunc, config ...Config
}

// newApp creates a new Fiber named instance.
func newApp[TCtx Ctx[TCtx]](config ...Config) *App[TCtx] {
func newApp[TCtx CtxGeneric[TCtx]](config ...Config) *App[TCtx] {
// Create a new app
app := &App[TCtx]{
// Create config
Expand Down Expand Up @@ -1129,7 +1129,7 @@ func (app *App[TCtx]) init() *App[TCtx] {
// sub fibers by their prefixes and if it finds a match, it uses that
// error handler. Otherwise it uses the configured error handler for
// the app, which if not set is the DefaultErrorHandler.
func (app *App[TCtx]) ErrorHandler(ctx Ctx[TCtx], err error) error {
func (app *App[TCtx]) ErrorHandler(ctx CtxGeneric[TCtx], err error) error {
var (
mountedErrHandler ErrorHandler
mountedPrefixParts int
Expand Down
4 changes: 2 additions & 2 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
type CustomBinder interface {
Name() string
MIMETypes() []string
Parse(c Ctx[any], out any) error
Parse(c Ctx, out any) error
}

// StructValidator is an interface to register custom struct validator for binding.
Expand All @@ -19,7 +19,7 @@ type StructValidator interface {

// Bind struct
type Bind struct {
ctx Ctx[any]
ctx Ctx
dontHandleErrs bool
}

Expand Down
22 changes: 12 additions & 10 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ const userContextKey contextKey = 0 // __local_user_context__
// generation tool `go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c`
// https://github.com/vburenin/ifacemaker/blob/975a95966976eeb2d4365a7fb236e274c54da64c/ifacemaker.go#L14-L30
//
//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface CtxGeneric --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
//go:generate go run ctx_interface_gen.go
type DefaultCtx struct {
app *App[any] // Reference to *App
app *App[*DefaultCtx] // Reference to *App
route *Route // Reference to *Route
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
bind *Bind // Default bind reference
Expand All @@ -72,6 +72,8 @@ type DefaultCtx struct {
matched bool // Non use route matched
}

type Ctx = CtxGeneric[*DefaultCtx]

// SendFile defines configuration options when to transfer file with SendFile.
type SendFile struct {
// FS is the file system to serve the static files from.
Expand Down Expand Up @@ -198,7 +200,7 @@ type Views interface {

// ResFmt associates a Content Type to a fiber.Handler for c.Format
type ResFmt struct {
Handler func(Ctx[any]) error
Handler func(Ctx) error
MediaType string
}

Expand All @@ -223,7 +225,7 @@ func (c *DefaultCtx) AcceptsLanguages(offers ...string) string {
}

// App returns the *App reference to the instance of the Fiber application
func (c *DefaultCtx) App() *App[DefaultCtx] {
func (c *DefaultCtx) App() *App[*DefaultCtx] {
return c.app
}

Expand Down Expand Up @@ -643,7 +645,7 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string {

// GetReqHeader returns the HTTP request header specified by filed.
// This function is generic and can handle different headers type values.
func GetReqHeader[V GenericType](c Ctx[any], key string, defaultValue ...V) V {
func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...)
}
Expand Down Expand Up @@ -979,7 +981,7 @@ func (c *DefaultCtx) Locals(key any, value ...any) any {
// All the values are removed from ctx after returning from the top
// RequestHandler. Additionally, Close method is called on each value
// implementing io.Closer before removing the value from ctx.
func Locals[V any](c Ctx[any], key any, value ...V) V {
func Locals[V any](c Ctx, key any, value ...V) V {
var v V
var ok bool
if len(value) == 0 {
Expand Down Expand Up @@ -1115,7 +1117,7 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string {
//
// http://example.com/id/:number -> http://example.com/id/john
// Params[int](c, "number", 0) -> returns 0 because can't parse 'john' as integer.
func Params[V GenericType](c Ctx[any], key string, defaultValue ...V) V {
func Params[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
return genericParseType(c.Params(key), v, defaultValue...)
}
Expand Down Expand Up @@ -1237,7 +1239,7 @@ func (c *DefaultCtx) Queries() map[string]string {
// name := Query[string](c, "search") // Returns "john"
// age := Query[int](c, "age") // Returns 8
// unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found
func Query[V GenericType](c Ctx[any], key string, defaultValue ...V) V {
func Query[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key))

Expand Down Expand Up @@ -1731,7 +1733,7 @@ func (c *DefaultCtx) Stale() bool {

// Status sets the HTTP status for the response.
// This method is chainable.
func (c *DefaultCtx) Status(status int) Ctx[DefaultCtx] {
func (c *DefaultCtx) Status(status int) *DefaultCtx {
c.fasthttp.Response.SetStatusCode(status)
return c
}
Expand Down Expand Up @@ -1776,7 +1778,7 @@ func (c *DefaultCtx) String() string {
}

// Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
func (c *DefaultCtx) Type(extension string, charset ...string) Ctx[DefaultCtx] {
func (c *DefaultCtx) Type(extension string, charset ...string) *DefaultCtx {
if len(charset) > 0 {
c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0])
} else {
Expand Down
14 changes: 7 additions & 7 deletions ctx_custom_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

type CustomCtx[T any] interface {
Ctx[T]
CtxGeneric[T]

// Reset is a method to reset context fields by given request when to use server handlers.
Reset(fctx *fasthttp.RequestCtx)
Expand All @@ -30,16 +30,16 @@ type CustomCtx[T any] interface {
setRoute(route *Route)
}

func NewDefaultCtx(app *App[DefaultCtx]) *DefaultCtx {
func NewDefaultCtx(app *App[*DefaultCtx]) *DefaultCtx {
// return ctx
return &DefaultCtx{
// Set app reference
app: app,
}
}

func (app *App[TCtx]) newCtx() Ctx[TCtx] {
var c Ctx[TCtx]
func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] {
var c CtxGeneric[TCtx]

if app.newCtxFunc != nil {
c = app.newCtxFunc(app)
Expand All @@ -51,8 +51,8 @@ func (app *App[TCtx]) newCtx() Ctx[TCtx] {
}

// AcquireCtx retrieves a new Ctx from the pool.
func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx[TCtx] {
ctx, ok := app.pool.Get().(Ctx)
func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) CtxGeneric[TCtx] {
ctx, ok := app.pool.Get().(CtxGeneric[TCtx])

if !ok {
panic(errors.New("failed to type-assert to Ctx"))
Expand All @@ -63,7 +63,7 @@ func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx[TCtx] {
}

// ReleaseCtx releases the ctx back into the pool.
func (app *App[TCtx]) ReleaseCtx(c Ctx[TCtx]) {
func (app *App[TCtx]) ReleaseCtx(c CtxGeneric[TCtx]) {
c.release()
app.pool.Put(c)
}
4 changes: 2 additions & 2 deletions ctx_interface.go

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

9 changes: 4 additions & 5 deletions ctx_interface_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func patchCtxFile(input []byte) ([]byte, error) {
scanner := bufio.NewScanner(in)
var outBuf bytes.Buffer

regexCtx := regexp.MustCompile(`\bCtx\b`)
regexApp := regexp.MustCompile(`\*\bApp\b`)
regexCtx := regexp.MustCompile(`(\*Default)Ctx`)
regexApp := regexp.MustCompile(`\*App(\[\w+])?`)

for scanner.Scan() {
line := scanner.Text()
Expand All @@ -50,8 +50,8 @@ func patchCtxFile(input []byte) ([]byte, error) {
// => "type Ctx interface {" -> "type Ctx[T any] interface {"
if strings.HasPrefix(line, "type") {
line = strings.Replace(line,
"type Ctx interface {",
"type Ctx[T any] interface {",
"type CtxGeneric interface {",
"type CtxGeneric[T any] interface {",
1,
)
} else {
Expand All @@ -64,7 +64,6 @@ func patchCtxFile(input []byte) ([]byte, error) {

// C) App with generic type
if strings.Contains(line, "App") {
// TODO: check this part
line = regexApp.ReplaceAllString(line, "*App[T]")
}
}
Expand Down
2 changes: 1 addition & 1 deletion group.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// Group struct
type Group struct {
app *App[any]
app *App[Ctx]
parentGroup *Group
name string

Expand Down
8 changes: 4 additions & 4 deletions hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ type (
OnListenHandler = func(ListenData) error
OnShutdownHandler = func() error
OnForkHandler = func(int) error
OnMountHandler = func(*App[any]) error
OnMountHandler = func(*App[Ctx]) error

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / lint

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / repeated

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 16 in hooks.go

View workflow job for this annotation

GitHub Actions / Compare

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)
)

// Hooks is a struct to use it with App.
type Hooks struct {
// Embed app
app *App[any]
app *App[Ctx]

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / lint

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / repeated

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 22 in hooks.go

View workflow job for this annotation

GitHub Actions / Compare

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

// Hooks
onRoute []OnRouteHandler
Expand All @@ -39,7 +39,7 @@ type ListenData struct {
TLS bool
}

func newHooks(app *App[Ctx[any]]) *Hooks {
func newHooks(app *App[Ctx]) *Hooks {
return &Hooks{
app: app,
onRoute: make([]OnRouteHandler, 0),
Expand Down Expand Up @@ -207,7 +207,7 @@ func (h *Hooks) executeOnForkHooks(pid int) {
}
}

func (h *Hooks) executeOnMountHooks(app *App[Ctx[any]]) error {
func (h *Hooks) executeOnMountHooks(app *App[Ctx]) error {
for _, v := range h.onMount {
if err := v(app); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
// Put fields related to mounting.
type mountFields struct {
// Mounted and main apps
appList map[string]*App[any]
appList map[string]*App[Ctx]

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / lint

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / repeated

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)

Check failure on line 18 in mount.go

View workflow job for this annotation

GitHub Actions / Compare

Ctx does not satisfy CtxGeneric[Ctx] (wrong type for method App)
// Prefix of app if it was mounted
mountPath string
// Ordered keys of apps (sorted by key length for Render)
Expand Down Expand Up @@ -68,7 +68,7 @@ func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router {
// Mount attaches another app instance as a sub-router along a routing path.
// It's very useful to split up a large API as many independent routers and
// compose them as a single service using Mount.
func (grp *Group) mount(prefix string, subApp *App[Ctx[any]]) Router {
func (grp *Group) mount(prefix string, subApp *App[Ctx]) Router {
groupPath := getGroupPath(grp.Prefix, prefix)
groupPath = utils.TrimRight(groupPath, '/')
if groupPath == "" {
Expand Down

0 comments on commit b695df6

Please sign in to comment.