# Test a router endpoint directly

URL: /docs/cookbook/router_endpoint
Markdown: /docs/cookbook/router_endpoint.md

> Call the router as a function to test dispatch, request validation and response handling.

Generated by `cargo run -p agent_docs` from `compiler/examples/`. Edit the example files, then regenerate.

Call the router as a function to test dispatch, request validation and response handling.

`Request.json` validates a refined name and caps the body at 256 bytes. The handler
maps `BadRequest` to a generic 400 response. `Router.handle` dispatches by method
and path, producing 404 or 405 when appropriate. There is no listener, socket,
`Serve` handler or permission to configure.

The tests call the actual router, not just the endpoint function. They assert the
successful response body, rejection of malformed JSON, an invalid refinement and
an oversized body, plus missing-route and wrong-method statuses. The harness uses
test mode and never starts a server.

[Source project](https://github.com/icantcodefyi/sakuralang/tree/master/compiler/examples/router_endpoint) · harness mode: `test`.

From the repository root, with this checkout's `sakura` on `PATH`:

```sh
cd compiler/examples/router_endpoint
sakura check --json
sakura test --json
```

### Manifest and permissions [#manifest-and-permissions]

```toml
[package]
name = "router_endpoint"
version = "0.1.0"
edition = "2026"

[permissions]
```

### Source and tests [#source-and-tests]

`src/main.sakura` includes all required imports and tests.

```sakura test id=cookbook-router_endpoint project=router_endpoint
// milestone: M3
// mode: test
// spec: design §17.145

import std/request.{BadRequest, Get, Request}
import std/response.{Response, Status}
import std/router.{Router}
import std/url.{Url}

type Greeting = { name: String where String.len(it) > 0 && String.len(it) <= 40 }

fn greet(req: Request) -> Response {
  try {
    let input: Greeting = Request.json(req, 256)
    Response.status(200, "Hello, {input.name}!")
  } catch {
    BadRequest { .. } => Response.status(400, "expected a short name"),
  }
}

fn app() -> Router<{}> { Router.new().post("/greet", greet) }

test "route dispatch calls the endpoint without a server" {
  let response = Router.handle(
    app(),
    Request.post("/greet", Json.encode(Greeting { name: "Mina" })),
  )
  expect_eq(response.status, Status.from(200))
  expect_eq(response.body, "Hello, Mina!")
}

test "malformed, invalid and oversized bodies return 400" {
  for body in ["not json", "\{\"name\":\"\"\}", String.repeat("x", 257)] {
    expect_eq(Router.handle(app(), Request.post("/greet", body)).status, Status.from(400))
  }
}

test "unknown paths and wrong methods are distinct" {
  expect_eq(Router.handle(app(), Request.post("/missing", "")).status, Status.from(404))
  expect_eq(Router.handle(app(), Request.of(Get, Url.from("/greet"))).status, Status.from(405))
}
```

### Compiler provenance and regeneration [#compiler-provenance-and-regeneration]

These eight projects teach supported Sakura APIs on the JavaScript backend. Each
project is exercised offline by `cargo test -p sakura_cli --test examples`;
HTTP and LLM exchanges are synthetic committed cassettes, with no network or API keys.
Use Node 26 for execution, including its built-in SQLite implementation.

The initial API baseline is compiler revision
[5cae349516c21714d90d36e87f1cfd6debe980c6](https://github.com/icantcodefyi/sakuralang/commit/5cae349516c21714d90d36e87f1cfd6debe980c6).
CI reruns these projects against the compiler in the same checkout. Build that
checkout's CLI and run `sakura --version` and `sakura prompt` before adapting a recipe;
pre-alpha APIs can change. An installed skill's frontmatter records its compiler
version and build revision. These teaching examples are separate from the agent
evaluation tasks.

The source of each page is its example's `recipe.md`, `sakura.toml`,
`src/main.sakura` and fixture files. Run `cargo run -p agent_docs` from `compiler/`
to regenerate the cookbook and skill reference. Edit those sources, not the rendered pages.
