Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 need gin.HandlerFunc middleware, 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}.json patterns: gin intentionally rejects these to avoid silently wrong param names.
  • Trying to use gin.HandlerFunc middleware directly: built-in adapters do not apply native middleware; you will get jetwarp.ErrNativeMWUnsupported. Use adapter.HTTP(...) or the engine escape hatch.
  • Registering routes directly on Gin: useful occasionally, but bypasses jetwarp’s registry and tooling.

See also