hyperlane 22.1.0

A lightweight, high-performance, and cross-platform Rust HTTP server library built on Tokio. It simplifies modern web service development by providing built-in support for middleware, WebSocket, Server-Sent Events (SSE), and raw TCP communication. With a unified and ergonomic API across Windows, Linux, and MacOS, it enables developers to build robust, scalable, and event-driven network applications with minimal overhead and maximum flexibility.
Documentation
# The hyperlane plugin roadmap

A plugin is a crate whose hooks are registered into the framework's inventory.
The framework collects every registered hook when a server is built, so a
plugin becomes active by being a dependency of the application. This document
describes the contract a plugin follows, the standards it has to meet, and the
plugins that are planned, in the order they are worth building.

## The contract

The mechanism already exists; a plugin does not invent one.

- **Registration.** A plugin implements `ServerHook` and registers it with the
  `#[route]`, `#[request_middleware]` and `#[response_middleware]` attributes.
  Each attribute submits a `HookType` to the `inventory`, which is how the
  framework finds it.
- **Installation.** `#[hyperlane(server: Server)]` collects every submitted
  hook, checks that the explicit orders are unique, sorts by order and
  installs them. A plugin therefore needs no registration call of its own.
- **Order.** A hook carries an optional priority. The hooks run in the order
  of their priority, from the smallest to the largest, and a hook without a
  priority runs before every hook that has one. Two hooks of the same kind may
  not claim the same priority.
- **The order of a plugin.** The plugins that answer a request, such as the
  cross origin plugin, keep the negative priorities, and an application that
  gives its own middlewares positive priorities runs after them. A plugin that
  has to mark the answer before it is built, such as the compression plugin,
  therefore runs before the middleware of the application that sends it. A
  plugin that reports the answer, such as the logger plugin, runs after the
  middlewares of the application instead, so that it reports the answer that
  was really sent.
- **Configuration.** The registry is fixed at compile time, so a plugin's
  settings live in a process wide static, initialised before the server
  starts. The pattern is a `OnceLock` behind an `init` and a `get`, and the
  `GetOrInit` trait is the shape the application already uses.
- **Use in the application.** `#[import(path)]` brings the plugin's traits
  into scope without naming them, so an application talks to a plugin through
  its API and not through its internals.
- **Not configured means not in the way.** A hook that is registered but has
  no configuration returns `Status::Continue` and does nothing. A plugin that
  is a dependency but that the application did not configure must not change
  the server's behaviour.

The last point is the rule that shapes every plugin below: being a dependency
is not the same as being enabled.

## The standards a plugin has to meet

The repository's own gate checks every staged `.rs` and `.toml` file, so a
plugin is held to the same rules as the framework.

- **Layout.** A directory holds the keyword files only: `lib.rs`, `mod.rs`,
  `const.rs`, `fn.rs`, `struct.rs`, `enum.rs`, `impl.rs`, `trait.rs`, `type.rs`.
  The entry of a crate is `lib.rs`: the module declarations first, then the
  `pub use` and `pub(crate) use` re-exports, then the imports.
- **Visibility.** In `const.rs` and `static.rs` the `pub` items come before the
  `pub(crate)` ones and the private ones. A `pub` constant needs a reader
  outside its own module, which is why a plugin's settings are reached
  through an accessor rather than through a public field.
- **Strings.** Every literal lives in `const.rs`. The format string of a
  `format!` or a `println!` stays at the call site.
- **No panics.** `panic!`, `.expect()` and `.unwrap()` do not appear in
  production code. Errors travel as `Result`.
- **Types.** Every `let` binding and every closure parameter carries its type.
- **Fields.** A field is read through its accessor, never as `self.field`.
- **Documentation.** Every public item carries a doc comment, and a function
  that takes or returns something documents it under `# Arguments` and
  `# Returns`.
- **No `#[allow]`.**

## The plugins, in the order they are worth building

### P0 - the ground every service needs

| plugin | what it gives |
|---|---|
| `hyperlane-plugin-shutdown` | graceful shutdown: the framework already has the control hook, the plugin turns it into "configure it once and it is armed" |
| `hyperlane-plugin-logger` | logging, with a file backend |

Both are small, and the application already carries a working version of each,
so upstreaming them is a move rather than a rewrite. A service that cannot shut
down cleanly or cannot say what it is doing is not a service yet.

The logger is in the tree now, and it is published. The shutdown plugin is
not: the signal of the operating system is not framework state, so the plugin
that joined it to the control hook is written again when the need is proven.

### P1 - what a web service is asked for every day

| plugin | what it gives |
|---|---|
| `hyperlane-plugin-cors` | cross origin headers and preflight |
| `hyperlane-plugin-compression` | gzip and brotli on the response |
| `hyperlane-plugin-static-files` | serving files, with the content type table the framework already carries |

None of the three is in the tree. Their rules are not framework state either,
so they are written where they are needed, and a server that is not Hyperlane
can use the same rules.

The compression plugin is the cheapest of the three: `http-compress` is
already a dependency and the response builder already honours a
`Content-Encoding` header, so the plugin only has to set the header when the
client asked for it and the body is worth compressing.

The static file plugin registers a route and no middleware, because the path
the files are served under belongs to the application. The application
registers the route with the pattern the plugin builds, and the answer of a
file is built and sent by the middlewares of the application, like the answer
of any other route.

### P2 - the data layer

| plugin | what it gives |
|---|---|
| `hyperlane-plugin-database` | the connection and auto creation traits, with no driver behind them |
| `hyperlane-plugin-postgresql` | the PostgreSQL driver |
| `hyperlane-plugin-mysql` | the MySQL driver |
| `hyperlane-plugin-redis` | the Redis client and its pool |

The application already has this shape, down to the trait names. The base
crate carries no driver, so it stays small; the driver crates carry theirs,
so they stay large and are published separately.

### P3 - running a service in production

| plugin | what it gives |
|---|---|
| `hyperlane-plugin-env` | configuration loaded from the environment |
| `hyperlane-plugin-metrics` | counters and timers in a scrapeable format |
| `hyperlane-plugin-rate-limit` | a limit per client or per route |

### P4 - the rest, when the need is proven

`hyperlane-plugin-message-queue`, `hyperlane-plugin-jwt`,
`hyperlane-plugin-tracing` and `hyperlane-plugin-multipart`.

## The shape of a plugin

```
plugin/<name>/
  Cargo.toml
  README.md
  src/
    lib.rs      the entry: module declarations, re-exports, the imports
    const.rs    every string literal
    struct.rs   the types
    enum.rs     the enumerations
    trait.rs    the traits the application talks to
    impl.rs     the implementations
    fn.rs       the free functions
    mod.rs      a submodule, when the plugin has more than one
  tests/
    mod.rs      the test entry: the modules and the crate under test
```

The entry of a crate is `lib.rs`. A `mod.rs` holds a submodule instead, and
carries the module declarations first, then the `pub use` and `pub(crate) use`
re-exports. Every keyword file below the entry starts with `use super::*;` and
reaches the crate's imports through it, so an import is written once, in the
entry, and never again.

A plugin declares its dependency on the framework and on nothing else, unless
it carries a driver. Its `Cargo.toml` carries `publish = false` until it is
ready to be published, and it is added to the workspace members, to the
workspace dependencies and to the release job's package list.

Its public surface is small on purpose: an `init` that takes the
configuration, a `get` that hands the configuration back, and the traits the
application calls. Everything else is private.

## What is still to decide

- **Publishing.** The release job names the plugins of this tree, and the
  plugins that hold no framework state are published from the crates-dev family
  instead, so a server that is not Hyperlane can use the same rules.
- **How much configuration is enough.** Every plugin that needs a setting
  needs it set before the server starts, and that is a second thing the
  application has to remember. A single configuration entry point would be
  better than one per plugin, and it is a decision, not an implementation.