alux-http
alux-http describes typed HTTP programs independently of a web framework.
The crate is a specification. It carries no interpreter and depends only on
alux-ext: an HTTP surface is described here as first-order syntax plus
the capability algebras an interpreter must witness. Domain specifications provide their own algebras
and compose first-order operations into routes.
use ;
use ;
use Future;
/// A downstream specification owns its primitive domain meaning.
/// Derived operations become first-order values that preserve their argument names.
/// The route program is declared before any framework is chosen.
;
// The same program is constructible directly, without the convenience macro and without an
// interpreter, because a route program is an ordinary value.
let builder = HttpProgramBuilder;
let program = builder
.routes
// The same endpoint, declared without the convenience macro.
.get
.into_program;
let _nested = builder.routes.nest.into_program;
// Argument names and order survive from the authored method into the program.
assert_eq!;
Composing surfaces
The reason to keep a surface first-order is that programs compose before anything interprets them. Two crates that know nothing about each other can each declare part of a service, and a third can state the whole of it — no shared route table, no registry, no framework in the picture yet.
use ext;
use ;
use Future;
/// One surface fragment. Its bounds name only what it uses: status, and JSON output.
/// Another fragment, declared independently — plausibly in another crate.
/// The whole service: a coproduct of both fragments, with one of them under a path prefix.
Two things there are worth pausing on, because the authored text is not what runs:
- The declarations look like they return nothing. They do return something. The macro replaces the
written signature with
-> ServiceApiProgram<Alg>and the written body withServiceApiProgram::default(), so callingservice_apihands back a zero-sized program value. Writing no return type is the convention: the type is generated, and naming it by hand would only repeat the macro. mergeandnestare not receiving routes. The authored body is read as a description rather than executed, and a call to a sibling declaration is lifted into a nested program — the emitted body readsbuilder.merge(builder.program(builder.status_api::<Alg>())). That is what type-checks, and it is why one fragment composes with another without either knowing how the other was declared.
What that buys, and why it is not just tidiness:
- Fragments state their own dependencies.
status_apirequiresJsonOutAlg; a fragment returning a file would requireFileOutAlginstead. Neither imposes its needs on the other, andservice_apiinherits exactly the union. - Nesting is selector precomposition, not a router feature — so
/v1/itemsarises from composing/v1with a subtree that never mentions it. - Composition happens before interpretation.
service_apiis a value; every interpreter sees the same merged surface, so documentation and execution cannot drift apart. - The surface is closed under composition. A merged program is a program, so it can be merged or nested again without a special case.
A program declared this way compiles through any crate that witnesses its algebras:
alux-http-textdescribes the surface as documentation or metadata.alux-http-poemcompiles the same program into executable Poem routes.