A small JSON API
A complete HTTP service in under fifty lines: one order type whose `json:` tags are the entire codec, a read handler, a stateful intake handler, and a router mounted on `std::http::Server`. Every section below concatenates to the exact program this build checked, and the one deliberate mistake shows the compiler catching the classic API bug — trusting a request body — at build time.
The type is the schema. `from_json` parses a document into an `Order` in one pass — a missing field is an error naming the field — and `to_json` writes it back. There is no separate schema language, no derive ceremony, and no map-of-anything passing through the handlers.
type Order {
id: Int `json:"id"`;
item: String `json:"item"`;
price: Int `json:"px"`;
}A route handler is an ordinary locus with a `handle` method — the interface is structural, so there is nothing to implement or register beyond the method itself. The path capture arrives through `ctx.params`, the parse failure is addressed inline with a default, and the response is a literal.
locus OrderRead {
fn handle(ctx: std::http::Context) -> std::http::Response {
let id = std::str::parse_int(std::http::path_param(ctx.params, "id")) or 0;
let o = Order { id: id, item: "espresso", price: 350 };
return std::http::Response {
status: 200,
body: Order::to_json(o),
content_type: "application/json",
};
}
}State is what a locus is for: `placed` is a param, private to the handler, no shared-mutability story required. The request body is attacker-controlled input, and `from_json` is fallible accordingly — the or-block turning a bad body into a 400 is not optional. Delete it and the build refuses:
locus OrderIntake {
params { placed: Int = 0; }
fn handle(ctx: std::http::Context) -> std::http::Response {
let o = Order::from_json(ctx.req.body) or {
return std::http::Response { status: 400, body: "not an order: " + err.field };
};
self.placed += 1;
return std::http::Response { status: 201, body: Order::to_json(o) };
}
}Trust the request body.
- let o = Order::from_json(ctx.req.body) or {
- return std::http::Response { status: 400, body: "not an order: " + err.field };
- };
+ let o = Order::from_json(ctx.req.body);$ hale check orders-api.hl
orders-api.hl:23:17: type error: error not addressed: this expression's fallible result must be handled with an `or` clause (`or raise`, `or <fallback>`, `or handler(err)`) or a `match`
let o = Order::from_json(ctx.req.body);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^The router is first-match-wins; the server is one statement. `max_accepts` bounds the accept loop, which is how a test drives the same server a deployment runs. Nothing on this page imported a framework — the server, router, JSON codec, and handler contract all ship in `std::http` and the language itself.
fn build_router() -> std::http::Router {
let r = std::http::Router { };
r.add("GET", "/orders/:id", OrderRead { });
r.add("POST", "/orders", OrderIntake { });
return r;
}
fn main() {
std::http::Server { port: 8080, handler: build_router(), max_accepts: 2 };
}