Extractors
Typed handler arguments that read shape out of a request — JSON, form, query, path, headers, cookies, JWT, and more.
Extractors
Extractors read shape out of a request and surface it as typed handler
arguments. Any type implementing
FromRequest
(consumes the body) or
FromRequestParts
(headers / URL only) can be used as a handler parameter. A handler
may take any number of extractors; Tako runs them in order before
calling the handler.
use serde::{Deserialize, Serialize};
use tako::Method;
use tako::extractors::json::Json;
use tako::extractors::path::Path;
use tako::extractors::query::Query;
use tako::extractors::state::State;
use tako::responder::Responder;
use tako::router::Router;
#[derive(Deserialize)]
struct ListQuery { page: u32, per_page: u32 }
#[derive(Deserialize)]
struct UserPath { id: u64 }
#[derive(Deserialize, Serialize)]
struct CreatePost { title: String, body: String }
#[derive(Clone)]
struct Db; // imagine a real pool here
async fn list_posts(
Path(UserPath { id }): Path<UserPath>,
Query(q): Query<ListQuery>,
State(_db): State<Db>,
) -> impl Responder {
format!("user={id}, page={}, per_page={}", q.page, q.per_page)
}
async fn create_post(
Path(UserPath { id }): Path<UserPath>,
Json(p): Json<CreatePost>,
) -> Json<CreatePost> {
println!("creating post for user={id}: {:?}", p.title);
Json(p)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
let mut router = Router::new();
router.with_state(Db);
router.route(Method::GET, "/users/{id}/posts", list_posts);
router.route(Method::POST, "/users/{id}/posts", create_post);
tako::serve(listener, router).await;
Ok(())
}How binding works
Each extractor is a wrapper type you destructure in the argument
position — Json(payload): Json<T>, Path(p): Path<T>, and so on. The
body extractors (FromRequest) consume the request body and must come
last in the argument list, because the body can only be read once. The
parts extractors (FromRequestParts) only touch headers and the URI,
so any number of them can run before the body extractor.
If extraction fails — a malformed JSON body, a missing required query
parameter, a bad Authorization header — the extractor's error type is
turned into a Responder (usually a 400 or 401) and the handler is
never called. Route-level error handlers and
use_problem_json() can reshape those errors before
they hit the wire.
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use tako::Method;
use tako::extractors::json::Json;
use tako::extractors::params::Params;
use tako::extractors::query::Query;
use tako::router::Router;
use tokio::net::TcpListener;
#[derive(Deserialize)]
struct Pagination {
page: u32,
per_page: u32,
}
#[derive(Deserialize)]
struct UserPath {
id: u64,
}
#[derive(Deserialize, Serialize, Clone)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct Created {
id: u64,
name: String,
email: String,
}
// GET /users/{id}/posts?per_page=10&page=2
// Demonstrates multiple extractors: Params + Query
async fn list_user_posts(Params(user): Params<UserPath>, Query(p): Query<Pagination>) -> String {
format!(
"user_id={}, page={}, per_page={}",
user.id, p.page, p.per_page
)
}
// POST /users with JSON body {"name":"...","email":"..."}
// Demonstrates a body extractor
async fn create(Json(user): Json<CreateUser>) -> Json<Created> {
// Normally you'd persist the user; here we just echo back with an id
Json(Created {
id: 1,
name: user.name,
email: user.email,
})
}
#[tokio::main]
async fn main() -> Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let mut router = Router::new();
router.route(Method::GET, "/users/{id}/posts", list_user_posts);
router.route(Method::POST, "/users", create);
tako::serve(listener, router).await;
Ok(())
}
Catalog
Tako ships 22+ bundled extractors. They fall into five groups, each with its own reference page.
Body — /docs/extractors/body
| Extractor | Description |
|---|---|
Json<T> | JSON body (with optional SIMD acceleration) |
Form<T> | URL-encoded form body |
Bytes | Raw request body stream |
Protobuf<T> | Protocol Buffers body (feature protobuf) |
SimdJson<T> | Force SIMD JSON parsing (feature simd) |
Multipart | Multipart form data (feature multipart) |
Request metadata — /docs/extractors/request-meta
| Extractor | Description |
|---|---|
Query<T> | URL query parameters |
Path<T> | Typed route path parameters |
Params<T> | Dynamic path params map |
HeaderMap | Full request headers |
Accept | Content negotiation |
AcceptLanguage | Language negotiation |
Range | HTTP Range header |
IpAddr | Client IP address |
Auth — /docs/extractors/auth
| Extractor | Description |
|---|---|
Basic | HTTP Basic credentials (BasicAuth) |
Bearer | Bearer token (BearerAuth) |
ApiKey | API key from header or query |
JwtClaimsUnverified<T> | JWT claims, signature not checked |
JwtClaimsVerified<T> | Signature-verified JWT claims |
Cookies — /docs/extractors/cookies
| Extractor | Description |
|---|---|
CookieJar | Cookie reading / writing |
CookieSigned | HMAC-signed cookies (SignedCookieJar) |
CookiePrivate | Encrypted cookies (PrivateCookieJar) |
State — /docs/state
| Extractor | Description |
|---|---|
State<T> | Shared application state, as Arc<T> |
Beyond the catalog
The crate also bundles QueryMulti<T> (repeated keys), RawPath /
RawQuery, MatchedPath, OriginalUri, Host, Scheme,
TypedHeader<H>, Extension<T>, ConnectInfo<T>, ContentLengthLimit<T, N>,
and Validated<T> (behind the validator / garde features). The
zero-copy-extractors feature enables borrowing variants of the body and
header extractors for hot-path handlers. See the
crate rustdoc for the
complete list.