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

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:

  1. This capability does not change adapter scoping semantics. adapter.Group() is still logical, adapter-managed prefix scoping.
  2. 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 panicking
  • Scope(...) must return errors instead of panicking
  • Param(...) 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