Drivers overview
Drivers are the portability layer between jetwarp’s adapter and concrete HTTP routers.
A driver implements drv.Drv and advertises its supported behavior via capability bits. Core does not guess.
If a behavior matters to correctness, it is represented explicitly in Caps() and enforced by the conformance suite.
That is the central idea to keep in mind while reading the rest of this page:
Kind()is for labeling and diagnostics.Caps()is for behavior.
What a driver is responsible for
The adapter owns the portable setup experience:
- path joining and logical scoping
- deterministic middleware composition
- registry recording
- accumulated setup errors via
Err()
The driver owns the router-facing edge:
- route registration on a concrete engine
- capability reporting
- param extraction
- optional router-native scoping via
Scope() - panic hardening around engine registration
In other words: the adapter defines the portable contract; the driver is the translation layer that makes that contract real on top of a concrete router.
Capability bits are promises
A capability bit is not a “maybe” or a wish. It is a promise that the suite actively verifies.
If a driver cannot provide correct semantics, it should not claim the capability. Prefer loud failure (ordinary setup errors) over silently wrong behavior.
Example: a router that would interpret :id.json as a parameter named id.json should not claim CapParamSuffix unless
it can preserve jetwarp’s canonical meaning of {id}.
The built-in capabilities
CapScope
The driver supports Scope(prefix) with correct router-native semantics.
Routes registered on the returned driver behave as if mounted under prefix.
This is a driver feature. The adapter does not call Scope() in v1.x; Group(...) remains logical prefix scoping
managed by core.
CapParams
The driver supports canonical {name} params and can return values via:
d.Param(r, "id")
The key is always the jetwarp name ("id"), even if the underlying router internally uses a different syntax such as
:id.
CapParamSuffix
The driver supports in-segment suffix/prefix parameter patterns such as:
/files/{name}.json
This requires CapParams. If a driver cannot preserve the canonical param meaning exactly, it should reject such
registrations instead of claiming the capability.
CapAnyMethod
The driver supports method-agnostic registration via drv.MethodAny ("*").
The behavioral contract is:
- a method-specific route wins over an any-method route for the same pattern
- this precedence must not depend on registration order
Most built-in drivers implement this with a dual-engine / fallback strategy.
CapNativeScopeMW
The driver supports framework-native middleware registration at the current driver scope via the corresponding
mw/<driver> wrapper module.
Examples from the built-in drivers:
- gin:
mw/gin - echo:
mw/echo - chi:
mw/chi - fiber:
mw/fiber
A driver that claims this capability exposes an optional, driver-specific hook (for example UseGinHandler(...) or
UseEchoMiddleware(...)). These hooks are not part of drv.Drv; the coupling is intentionally confined to the
matching mw/<driver> package.
Two important notes:
- This capability does not change adapter scoping semantics.
adapter.Group()is still logical, adapter-managed prefix scoping. - Portable middleware ordering remains contractual:
Use → Group → With → per-route → handler.
Native middleware ordering is driver-specific and best-effort. Portable ↔ native interleaving is also best-effort and is not part of the cross-driver portability contract.
Logical scoping vs router-native scoping
This distinction matters more than it first appears.
Adapter logical scoping
When you write:
api := r.Group("/api")
v1 := api.Group("/v1")
the adapter joins prefixes logically and registers the final path on the underlying driver.
This behavior is:
- portable
- deterministic
- covered by adapter conformance tests
Driver-native scoping
A driver may also support:
sub, err := d.Scope("/api")
That creates a derived driver with router-native sub-router semantics.
This is useful for direct driver users and for router-specific experiments, but it is not how adapter Group(...)
works in v1.x.
Native middleware support in v1.2 does not change this split. The adapter still owns logical scoping; Scope() remains
a direct-driver capability.
No-panic contract
Drivers must never let bad registrations crash the process.
At minimum:
Handle(...)must return errors instead of panickingScope(...)must return errors instead of panickingParam(...)must be defensive around nil requests / empty keys- invalid registrations must not leave the driver unusable
This is not just a style preference. The suite explicitly checks that drivers remain usable after a bad registration.
Engine escape hatch
Every driver also exposes Engine() any.
That is the intentional escape hatch for router-specific features that jetwarp does not model directly. It is useful, but it is outside the portability contract. Once code starts depending on the engine shape, that code is no longer portable in the strict sense.
Use it consciously.
Where to go next
- Custom drivers — a worked example of implementing a
drv.Drv - Writing a new driver — contributor checklist and workflow