The GIN adapter
The gin adapter wires jetwarp’s portable adapter.Adapter API to the gin-gonic/gin router.
This adapter is a good fit when you want to stay in the Gin ecosystem, but still keep your application routing code portable across other adapters (stdlib, chi, echo, fiber, …).
Install
Add the adapter module to your project:
go get codeberg.org/iaconlabs/jetwarp/adapter/gin@latest
This pulls in:
codeberg.org/iaconlabs/jetwarp(core adapter + registry)codeberg.org/iaconlabs/jetwarp/drivers/v1/gin(the gin driver)github.com/gin-gonic/gin(the underlying router)
Create a router
package main
import (
"log"
"net/http"
twgin "codeberg.org/iaconlabs/jetwarp/adapter/gin"
)
func main() {
r := twgin.New()
r.HandleFunc(http.MethodGet, "/healthz", func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok"))
})
// Always check accumulated registration errors before serving traffic.
if err := r.Err(); err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", r))
}
Route patterns and parameters
jetwarp’s canonical pattern syntax uses ServeMux-style {name} parameters.
The Gin driver converts {id} to Gin/httprouter-style :id internally, but you keep writing the canonical form:
r.HandleFunc(http.MethodGet, "/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_, _ = w.Write([]byte("id=" + id))
})
Why PathValue works with Gin
Gin exposes parameters via its own context API (c.Param(...)), not via *http.Request.
jetwarp bridges captured parameters into the request so handlers can read them portably using
(*http.Request).PathValue("id"), regardless of which adapter is underneath.
If you ever see empty PathValue params on the gin adapter, treat it as a regression: it should be covered by tests.
Important gotcha: suffix/prefix patterns are rejected
The Gin driver does not support “in-segment” suffix/prefix patterns such as:
/files/{id}.json/prefix-{id}
Why? In Gin/httprouter-style routers, :id.json would be interpreted as a parameter named id.json.
That breaks jetwarp’s canonical semantics where the parameter name is id.
So jetwarp chooses a loud, safe behavior:
- registration fails (recorded in
Err()) - the router remains usable after the failure
Portable alternative: use a plain segment param and validate the suffix yourself in the handler:
- ✅
/files/{id}then validate/parse - ❌
/files/{id}.json(not portable on gin)
MethodAny ("*") support
jetwarp supports method-agnostic routes via the canonical method token "*":
r.HandleFunc("*", "/any", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("ANY:" + r.Method))
})
On Gin, the driver uses two internal engines:
- primary: method-specific routes (
GET,POST, …) - any: MethodAny routes (
"*")
Request dispatch tries primary first and falls back to any. This guarantees:
- explicit method routes win over
"*"routes for the same path "*"handles methods that do not have an explicit registration
This rule is enforced by tests and does not depend on registration order.
Middleware with Gin
Prefer portable net/http middleware
Gin middleware (gin.HandlerFunc) is not the same shape as net/http middleware.
For maximum portability, prefer standard net/http middleware wrapped with adapter.HTTP(...):
Note: the built-in jetwarp adapters currently support portable middleware only. If you attempt to register framework-native middleware through jetwarp, it will be recorded as
jetwarp.ErrNativeMWUnsupported. If you truly needgin.HandlerFuncmiddleware, attach it via the engine escape hatch (see below).
import (
"net/http"
"codeberg.org/iaconlabs/jetwarp/adapter"
)
r.Use(adapter.HTTPNamed("example", func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// before
next.ServeHTTP(w, r)
// after
})
}))
Ordering is deterministic across adapters:
Use → Group → With → per-route → handler
Gotcha: Gin defaults are not installed
The gin driver constructs engines with gin.New() (not gin.Default()), so Gin’s default logger/recovery middleware
is not installed automatically.
That’s usually what you want in a portable net/http-first design: you bring your own middleware explicitly. If you want recovery/logging, add it via portable middleware (recommended), or use the engine escape hatch for Gin-specific wiring (see below).
Accessing the underlying engine (escape hatch)
Sometimes you need Gin-specific integration (debugging, profiling hooks, or other tooling). jetwarp exposes an Engine() any
escape hatch on the concrete router.
For Gin, the engine value includes both underlying *gin.Engine instances (primary + any). Example:
package main
import (
twdrv "codeberg.org/iaconlabs/jetwarp/drivers/v1/gin"
)
type engineProvider interface {
Engine() any
}
func useEngine(r any) {
ep, ok := r.(engineProvider)
if !ok {
return
}
if eng, ok := ep.Engine().(twdrv.Engine); ok {
_ = eng.Primary // *gin.Engine
_ = eng.Any // *gin.Engine
}
}
Be careful with this escape hatch:
- registering routes directly on Gin will bypass jetwarp’s registry (and therefore OpenAPI generation)
- relying on Gin-specific behaviors reduces portability
Common pitfalls
- Forgetting
Err(): registration errors accumulate instead of panicking; always fail fast after setup. - Using
{id}.jsonpatterns: gin intentionally rejects these to avoid silently wrong param names. - Trying to use
gin.HandlerFuncmiddleware directly: built-in adapters do not apply native middleware; you will getjetwarp.ErrNativeMWUnsupported. Useadapter.HTTP(...)or the engine escape hatch. - Registering routes directly on Gin: useful occasionally, but bypasses jetwarp’s registry and tooling.