package ginext import ( "github.com/gin-gonic/gin" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/rext" "net/http" "path" "reflect" "regexp" "runtime" "strings" ) var anyMethods = []string{ http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect, http.MethodTrace, } type GinRoutesWrapper struct { wrapper *GinWrapper routes gin.IRouter absPath string defaultHandler []gin.HandlerFunc } type GinRouteBuilder struct { routes *GinRoutesWrapper method string relPath string absPath string handlers []gin.HandlerFunc } func (w *GinWrapper) Routes() *GinRoutesWrapper { return &GinRoutesWrapper{ wrapper: w, routes: w.engine, absPath: "", defaultHandler: make([]gin.HandlerFunc, 0), } } func (w *GinRoutesWrapper) Group(relativePath string) *GinRoutesWrapper { return &GinRoutesWrapper{ wrapper: w.wrapper, routes: w.routes.Group(relativePath), defaultHandler: langext.ArrCopy(w.defaultHandler), absPath: joinPaths(w.absPath, relativePath), } } func (w *GinRoutesWrapper) Use(middleware ...gin.HandlerFunc) *GinRoutesWrapper { defHandler := langext.ArrCopy(w.defaultHandler) defHandler = append(defHandler, middleware...) return &GinRoutesWrapper{wrapper: w.wrapper, routes: w.routes, defaultHandler: defHandler} } func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder { return w._route(http.MethodGet, relativePath) } func (w *GinRoutesWrapper) POST(relativePath string) *GinRouteBuilder { return w._route(http.MethodPost, relativePath) } func (w *GinRoutesWrapper) DELETE(relativePath string) *GinRouteBuilder { return w._route(http.MethodDelete, relativePath) } func (w *GinRoutesWrapper) PATCH(relativePath string) *GinRouteBuilder { return w._route(http.MethodPatch, relativePath) } func (w *GinRoutesWrapper) PUT(relativePath string) *GinRouteBuilder { return w._route(http.MethodPut, relativePath) } func (w *GinRoutesWrapper) OPTIONS(relativePath string) *GinRouteBuilder { return w._route(http.MethodOptions, relativePath) } func (w *GinRoutesWrapper) HEAD(relativePath string) *GinRouteBuilder { return w._route(http.MethodHead, relativePath) } func (w *GinRoutesWrapper) COUNT(relativePath string) *GinRouteBuilder { return w._route("COUNT", relativePath) } func (w *GinRoutesWrapper) Any(relativePath string) *GinRouteBuilder { return w._route("*", relativePath) } func (w *GinRoutesWrapper) _route(method string, relativePath string) *GinRouteBuilder { return &GinRouteBuilder{ routes: w, method: method, relPath: relativePath, absPath: joinPaths(w.absPath, relativePath), handlers: langext.ArrCopy(w.defaultHandler), } } func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder { w.handlers = append(w.handlers, middleware...) return w } func (w *GinRouteBuilder) WithJSONFilter(filter string) *GinRouteBuilder { w.handlers = append(w.handlers, func(g *gin.Context) { g.Set("ginext.jsonfilter", filter) }) return w } func (w *GinRouteBuilder) Handle(handler WHandlerFunc) { if w.routes.wrapper.bufferBody { arr := make([]gin.HandlerFunc, 0, len(w.handlers)+1) arr = append(arr, BodyBuffer) arr = append(arr, w.handlers...) w.handlers = arr } middlewareNames := langext.ArrMap(w.handlers, func(v gin.HandlerFunc) string { return nameOfFunction(v) }) handlerName := nameOfFunction(handler) w.handlers = append(w.handlers, Wrap(w.routes.wrapper, handler)) methodName := w.method if w.method == "*" { methodName = "ANY" for _, method := range anyMethods { w.routes.routes.Handle(method, w.relPath, w.handlers...) } } else { w.routes.routes.Handle(w.method, w.relPath, w.handlers...) } w.routes.wrapper.routeSpecs = append(w.routes.wrapper.routeSpecs, ginRouteSpec{ Method: methodName, URL: w.absPath, Middlewares: middlewareNames, Handler: handlerName, }) } func (w *GinWrapper) NoRoute(handler WHandlerFunc) { handlers := make([]gin.HandlerFunc, 0) if w.bufferBody { handlers = append(handlers, BodyBuffer) } middlewareNames := langext.ArrMap(handlers, func(v gin.HandlerFunc) string { return nameOfFunction(v) }) handlerName := nameOfFunction(handler) handlers = append(handlers, Wrap(w, handler)) w.engine.NoRoute(handlers...) w.routeSpecs = append(w.routeSpecs, ginRouteSpec{ Method: "ANY", URL: "[NO_ROUTE]", Middlewares: middlewareNames, Handler: handlerName, }) } func nameOfFunction(f any) string { fname := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() split := strings.Split(fname, "/") if len(split) == 0 { return "" } fname = split[len(split)-1] // https://stackoverflow.com/a/32925345/1761622 if strings.HasSuffix(fname, "-fm") { fname = fname[:len(fname)-len("-fm")] } suffix := rext.W(regexp.MustCompile(`\.func[0-9]+(?:\.[0-9]+)*$`)) if match, ok := suffix.MatchFirst(fname); ok { fname = fname[:len(fname)-match.FullMatch().Length()] } return fname } // joinPaths is copied verbatim from gin@v1.9.1/gin.go func joinPaths(absolutePath, relativePath string) string { if relativePath == "" { return absolutePath } finalPath := path.Join(absolutePath, relativePath) if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' { return finalPath + "/" } return finalPath } func lastChar(str string) uint8 { if str == "" { panic("The length of the string can't be 0") } return str[len(str)-1] }