Skip to content

Commit 4235104

Browse files
authored
Merge pull request #4 from go-deck/feature/support-custom-middleware
Feature/support custom middleware
2 parents 26429d0 + 7ae534a commit 4235104

File tree

17 files changed

+553
-298
lines changed

17 files changed

+553
-298
lines changed

README.md

Lines changed: 201 additions & 162 deletions
Large diffs are not rendered by default.

ctx/context.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

frameworks/base.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package frameworks
22

33
import (
4-
"github.com/go-deck/routeflow/ctx"
5-
"github.com/go-deck/routeflow/loader"
4+
"github.com/go-deck/routeflow/internal/ctx"
5+
"github.com/go-deck/routeflow/internal/loader"
66
"gorm.io/gorm"
77
)
88

99
type Server interface {
10-
Start(cfg *loader.Config, handlerMap map[string]func(*ctx.Context) (interface{}, int), db *gorm.DB)
10+
Start(cfg *loader.Config, handlerMap map[string]ctx.HandlerFunc, db *gorm.DB, middlewareMap map[string]ctx.HandlerFunc)
1111
}

frameworks/ginserver/middleware.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,47 @@ package ginserver
22

33
import (
44
"github.com/gin-contrib/cors"
5-
"github.com/gin-gonic/gin"
6-
"github.com/go-deck/routeflow/loader"
5+
framework "github.com/gin-gonic/gin"
6+
"github.com/go-deck/routeflow/internal/ctx"
7+
"github.com/go-deck/routeflow/internal/loader"
8+
log "github.com/sirupsen/logrus"
9+
"gorm.io/gorm"
710
)
811

9-
// LoadMiddlewares applies middlewares dynamically
10-
func LoadMiddlewares(r *gin.Engine, cfg *loader.Config) {
11-
for _, mw := range cfg.Middlewares.Global {
12+
// Change to accept ctx.HandlerFunc explicitly
13+
func MiddlewareAdapter(db *gorm.DB, fn ctx.HandlerFunc) framework.HandlerFunc {
14+
return func(c *framework.Context) {
15+
cc := ctx.Get(c) // Use shared context
16+
response, status := fn(cc)
17+
if status > 0 {
18+
c.AbortWithStatusJSON(status, response)
19+
} else {
20+
c.Next()
21+
}
22+
}
23+
}
24+
25+
func LoadMiddlewares(r *framework.Engine, cfg *loader.Config, middlewareMap map[string]ctx.HandlerFunc, db *gorm.DB) {
26+
// Built-in middlewares
27+
for _, mw := range cfg.Middlewares.BuiltIn {
1228
switch mw {
1329
case "cors":
1430
corsConfig := cors.DefaultConfig()
1531
corsConfig.AllowOrigins = cfg.Server.AllowedOrigins
1632
r.Use(cors.New(corsConfig))
1733
case "logging":
18-
r.Use(gin.Logger())
34+
r.Use(framework.Logger())
1935
case "recovery":
20-
r.Use(gin.Recovery())
36+
r.Use(framework.Recovery())
37+
}
38+
}
39+
40+
// Custom middlewares
41+
for _, name := range cfg.Middlewares.Custom {
42+
if mw, exists := middlewareMap[name]; exists {
43+
r.Use(MiddlewareAdapter(db, mw))
44+
} else {
45+
log.Printf("⚠️ Middleware not found: %s", name)
2146
}
2247
}
2348
}

frameworks/ginserver/router.go

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,75 @@
11
package ginserver
22

33
import (
4-
"log"
5-
6-
"github.com/gin-gonic/gin"
7-
"github.com/go-deck/routeflow/ctx"
8-
"github.com/go-deck/routeflow/handler"
9-
"github.com/go-deck/routeflow/loader"
4+
"github.com/gin-contrib/cors"
5+
framework "github.com/gin-gonic/gin"
6+
"github.com/go-deck/routeflow/internal/ctx"
7+
"github.com/go-deck/routeflow/internal/loader"
8+
log "github.com/sirupsen/logrus"
109
"gorm.io/gorm"
1110
)
1211

13-
// InitGinRouter initializes the Gin router
14-
func InitGinRouter(r *gin.Engine, cfg *loader.Config, handlerMap map[string]func(*ctx.Context) (interface{}, int), db *gorm.DB) {
12+
// Initialize Gin router with the provided configuration and handler map
13+
func InitGinRouter(r *framework.Engine, cfg *loader.Config, handlerMap map[string]ctx.HandlerFunc, db *gorm.DB, middlewareMap map[string]ctx.HandlerFunc) {
1514
for _, group := range cfg.Routes.Groups {
1615
api := r.Group(group.Base)
16+
for _, mw := range group.Middlewares.BuiltIn {
17+
switch mw {
18+
case "cors":
19+
corsConfig := cors.DefaultConfig()
20+
corsConfig.AllowOrigins = cfg.Server.AllowedOrigins
21+
api.Use(cors.New(corsConfig))
22+
case "logging":
23+
api.Use(framework.Logger())
24+
case "recovery":
25+
api.Use(framework.Recovery())
26+
}
27+
}
28+
for _, name := range group.Middlewares.Custom {
29+
if mw, exists := middlewareMap[name]; exists {
30+
api.Use(MiddlewareAdapter(db, mw))
31+
}
32+
}
33+
// Inside InitGinRouter function...
1734
for _, route := range group.Routes {
18-
userHandler, exists := handlerMap[route.Handler]
35+
handlers, exists := handlerMap[route.Handler]
1936
if !exists {
20-
log.Fatalf("Handler not found for route: %s", route.Handler)
37+
log.WithFields(log.Fields{
38+
"route": route.Path,
39+
"handler": route.Handler,
40+
}).Error("Handler not found")
41+
continue
42+
}
43+
44+
// Collect route-specific middlewares
45+
var routeHandlers []framework.HandlerFunc
46+
47+
// Add built-in middlewares for this route
48+
for _, mw := range route.Middlewares.BuiltIn {
49+
switch mw {
50+
case "cors":
51+
corsConfig := cors.DefaultConfig()
52+
corsConfig.AllowOrigins = cfg.Server.AllowedOrigins
53+
routeHandlers = append(routeHandlers, cors.New(corsConfig))
54+
case "logging":
55+
routeHandlers = append(routeHandlers, framework.Logger())
56+
case "recovery":
57+
routeHandlers = append(routeHandlers, framework.Recovery())
58+
}
2159
}
2260

23-
// Extract validation properties from YAML
24-
validation := make(map[string]interface{})
25-
if route.BodyParams != nil {
26-
for _, param := range route.BodyParams {
27-
validation[param.Name] = param.Validation
61+
// Add custom middlewares for this route
62+
for _, name := range route.Middlewares.Custom {
63+
if mw, exists := middlewareMap[name]; exists {
64+
routeHandlers = append(routeHandlers, MiddlewareAdapter(db, mw))
2865
}
2966
}
3067

31-
// Convert to a Gin-compatible handler
32-
wrappedHandler := handler.WrapHandler(userHandler, validation, db)
68+
// Add the actual handler as the final step
69+
routeHandlers = append(routeHandlers, MiddlewareAdapter(db, handlers))
3370

34-
// Register route dynamically
35-
api.Handle(route.Method, route.Path, wrappedHandler)
71+
// Register the route with collected middlewares and handler
72+
api.Handle(route.Method, route.Path, routeHandlers...)
3673
}
3774
}
3875
}

frameworks/ginserver/server.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,29 @@ package ginserver
22

33
import (
44
"fmt"
5-
"log"
65

7-
"github.com/gin-gonic/gin"
8-
"github.com/go-deck/routeflow/ctx"
9-
"github.com/go-deck/routeflow/loader"
6+
log "github.com/sirupsen/logrus"
7+
8+
framework "github.com/gin-gonic/gin"
9+
"github.com/go-deck/routeflow/internal/ctx"
10+
"github.com/go-deck/routeflow/internal/loader"
1011
"gorm.io/gorm"
1112
)
1213

1314
type GinServer struct{}
1415

15-
func (g *GinServer) Start(cfg *loader.Config, handlerMap map[string]func(*ctx.Context) (interface{}, int), db *gorm.DB) {
16-
r := gin.New()
16+
func (g *GinServer) Start(cfg *loader.Config, handlerMap map[string]ctx.HandlerFunc, db *gorm.DB, middlewareMap map[string]ctx.HandlerFunc) {
17+
r := framework.New()
18+
19+
r.Use(ctx.Middleware(db))
1720

18-
gin.SetMode(gin.ReleaseMode)
21+
framework.SetMode(framework.ReleaseMode)
1922

2023
// Load middleware
21-
LoadMiddlewares(r, cfg)
24+
LoadMiddlewares(r, cfg, middlewareMap, db)
2225

2326
// Register routes
24-
InitGinRouter(r, cfg, handlerMap, db)
27+
InitGinRouter(r, cfg, handlerMap, db, middlewareMap)
2528

2629
// Start server
2730
port := fmt.Sprintf(":%d", cfg.Server.Port)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.3
55
require (
66
github.com/gin-contrib/cors v1.7.3
77
github.com/gin-gonic/gin v1.10.0
8+
github.com/sirupsen/logrus v1.9.3
89
gopkg.in/yaml.v3 v3.0.1
910
gorm.io/driver/mysql v1.5.7
1011
gorm.io/driver/postgres v1.5.11

go.sum

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
7373
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7474
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
7575
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
76+
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
77+
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
7678
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7779
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
7880
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -89,11 +91,13 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
8991
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
9092
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
9193
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
92-
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
93-
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
94-
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
95-
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
96-
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
94+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
95+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
96+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
97+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
98+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
99+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
100+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97101
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
98102
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99103
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=

ctx/base.go renamed to internal/ctx/base.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ type Context struct {
1313
BodyData map[string]interface{} // JSON body data
1414
DB *gorm.DB // Database connection
1515
}
16+
17+
// HandlerFunc defines the function signature for handlers
18+
type HandlerFunc func(*Context) (interface{}, int)

internal/ctx/context.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package ctx
2+
3+
import (
4+
framework "github.com/gin-gonic/gin"
5+
"gorm.io/gorm"
6+
)
7+
8+
const ContextKey = "RF_CONTEXT"
9+
10+
// Get (update existing NewContext to this)
11+
func Get(c *framework.Context) *Context {
12+
return c.MustGet(ContextKey).(*Context)
13+
}
14+
15+
// Initialize middleware (add this new function)
16+
func Middleware(db *gorm.DB) framework.HandlerFunc {
17+
return func(c *framework.Context) {
18+
// Create context once per request
19+
cc := &Context{
20+
GinContext: c,
21+
PathParams: make(map[string]string),
22+
QueryParams: make(map[string]string),
23+
BodyData: make(map[string]interface{}),
24+
DB: db,
25+
}
26+
27+
// Parse data once
28+
for _, param := range c.Params {
29+
cc.PathParams[param.Key] = param.Value
30+
}
31+
32+
for key, values := range c.Request.URL.Query() {
33+
cc.QueryParams[key] = values[0]
34+
}
35+
36+
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
37+
if err := c.ShouldBindJSON(&cc.BodyData); err != nil {
38+
cc.BodyData = map[string]interface{}{"error": "Invalid JSON"}
39+
}
40+
}
41+
42+
c.Set(ContextKey, cc)
43+
c.Next()
44+
}
45+
}
46+
47+
// NewContext creates a new `routeflow.Context` from Gin context
48+
func NewContext(c *framework.Context, db *gorm.DB) *Context {
49+
// Extract path parameters
50+
pathParams := make(map[string]string)
51+
for _, param := range c.Params {
52+
pathParams[param.Key] = param.Value
53+
}
54+
55+
// Extract query parameters
56+
queryParams := make(map[string]string)
57+
for key, value := range c.Request.URL.Query() {
58+
queryParams[key] = value[0] // Take first value
59+
}
60+
61+
// Extract JSON body data
62+
bodyData := make(map[string]interface{})
63+
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
64+
if err := c.ShouldBindJSON(&bodyData); err != nil {
65+
bodyData = map[string]interface{}{"error": "Invalid JSON"}
66+
}
67+
}
68+
69+
return &Context{
70+
GinContext: c,
71+
PathParams: pathParams,
72+
QueryParams: queryParams,
73+
BodyData: bodyData,
74+
DB: db,
75+
}
76+
}

db/db.go renamed to internal/db/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"log"
66
"time"
77

8-
"github.com/go-deck/routeflow/loader"
8+
"github.com/go-deck/routeflow/internal/loader"
99
"gorm.io/driver/mysql"
1010
"gorm.io/driver/postgres"
1111
"gorm.io/driver/sqlite"

handler/handler.go renamed to internal/handler/handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"net/http"
55

66
"github.com/gin-gonic/gin"
7-
"github.com/go-deck/routeflow/ctx"
8-
"github.com/go-deck/routeflow/validator"
7+
"github.com/go-deck/routeflow/internal/ctx"
8+
"github.com/go-deck/routeflow/internal/validator"
99
"gorm.io/gorm"
1010
)
1111

0 commit comments

Comments
 (0)