// Package gin adapts Gin to GoOmni's transport-neutral router contract. // Business packages should import httpx and app, not this package. package gin import ( "net/http" "sort" "strings" "git.yuntuo.hk/k2safe/go-omni/httpx" "git.yuntuo.hk/k2safe/go-omni/transport/internal/routing" ginlib "github.com/gin-gonic/gin" ) // Router is the default Gin-backed GoOmni router. type Router struct { engine *ginlib.Engine routes []registeredRoute } type registeredRoute struct { method string segments []routing.Segment } // New creates a Gin router without Gin's default logger or recovery middleware. func New(mode string) *Router { if mode != "" { ginlib.SetMode(mode) } return NewWithEngine(ginlib.New()) } // NewWithEngine wraps an existing engine and applies GoOmni's routing policy. // Native middleware/routes are retained; redirect and fallback settings are // replaced. Register portable routes through Router, not Engine(). func NewWithEngine(engine *ginlib.Engine) *Router { if engine == nil { panic("go-omni/gin: nil engine") } engine.RedirectTrailingSlash = false engine.RedirectFixedPath = false engine.RemoveExtraSlash = false engine.UseRawPath = false // Gin 1.10 can carry backtracking state between method trees during its // 405 lookup. Derive Allow from registered patterns instead of that lookup. engine.HandleMethodNotAllowed = false router := &Router{engine: engine} engine.NoRoute(router.fallback) return router } func (r *Router) fallback(ctx *ginlib.Context) { allowed := make(map[string]bool) for _, route := range r.routes { if _, matches := routing.Match(route.segments, ctx.Request.URL.Path); matches { allowed[route.method] = true } } if len(allowed) == 0 { http.NotFound(ctx.Writer, ctx.Request) return } methods := make([]string, 0, len(allowed)) for method := range allowed { methods = append(methods, method) } sort.Strings(methods) ctx.Header("Allow", strings.Join(methods, ", ")) http.Error(ctx.Writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) } // Engine exposes the underlying Gin engine to adapter-specific integrations. func (r *Router) Engine() *ginlib.Engine { return r.engine } func (r *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) { r.engine.ServeHTTP(writer, request) } func (r *Router) Handle(method, path string, handler http.Handler) { segments := routing.Parse(method, path, handler) r.engine.Handle(method, path, adapt(handler)) r.routes = append(r.routes, registeredRoute{method: method, segments: segments}) } func (r *Router) Any(path string, handler http.Handler) { for _, method := range routing.Methods { r.Handle(method, path, handler) } } func (r *Router) Group(path string) httpx.RouterGroup { return &group{router: r, prefix: routing.Join("", path)} } type group struct { router *Router prefix string } func (g *group) Handle(method, path string, handler http.Handler) { g.router.Handle(method, routing.Join(g.prefix, path), handler) } func (g *group) Any(path string, handler http.Handler) { g.router.Any(routing.Join(g.prefix, path), handler) } func (g *group) Group(path string) httpx.RouterGroup { return &group{router: g.router, prefix: routing.Join(g.prefix, path)} } func adapt(handler http.Handler) ginlib.HandlerFunc { return func(ctx *ginlib.Context) { params := make(map[string]string, len(ctx.Params)) for _, param := range ctx.Params { params[param.Key] = param.Value } requestContext := httpx.WithRouteParams(ctx.Request.Context(), params) requestContext = httpx.WithRoutePattern(requestContext, ctx.FullPath()) request := ctx.Request.WithContext(requestContext) handler.ServeHTTP(ctx.Writer, request) } }