1use std::net::SocketAddr;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::time::Instant;
4
5use rustio_core::auth::{authenticate, require_admin, require_auth};
6use rustio_core::defaults::with_defaults;
7use rustio_core::{resolve, text, Error, Next, Request, Response, Router, Server};
8
9#[derive(Debug)]
10struct RequestId(u64);
11
12static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
13
14async fn request_id(mut req: Request, next: Next) -> Result<Response, Error> {
15 let id = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
16 req.ctx_mut().insert(RequestId(id));
17 let mut resp = resolve(next.run(req).await);
18 if let Ok(header) = format!("req-{id}").parse() {
19 resp.headers_mut().insert("x-request-id", header);
20 }
21 Ok(resp)
22}
23
24async fn logger(req: Request, next: Next) -> Result<Response, Error> {
25 let method = req.method().clone();
26 let path = req.uri().path().to_owned();
27 let id = req.ctx().get::<RequestId>().map(|r| r.0);
28 let user = rustio_core::auth::identity(req.ctx()).map(|i| i.user_id.clone());
29 let started = Instant::now();
30 let result = next.run(req).await;
31 let status = match &result {
32 Ok(resp) => resp.status().as_u16(),
33 Err(err) => err.status(),
34 };
35 let id_display = id.map(|i| format!("req-{i}")).unwrap_or_else(|| "-".into());
36 let user_display = user.unwrap_or_else(|| "-".into());
37 eprintln!(
38 "[{:>3}] {:>4} {} id={} user={} ({:?})",
39 status,
40 method,
41 path,
42 id_display,
43 user_display,
44 started.elapsed()
45 );
46 result
47}
48
49#[tokio::main]
50async fn main() -> std::io::Result<()> {
51 let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
52 let router = with_defaults(Router::new())
53 .get("/whoami", |req, _params| async move {
54 let id = req
55 .ctx()
56 .get::<RequestId>()
57 .map(|r| r.0.to_string())
58 .unwrap_or_else(|| "unknown".into());
59 Ok::<Response, Error>(text(format!("your request id is req-{id}\n")))
60 })
61 .get("/me", |req, _params| async move {
62 let id = require_auth(req.ctx())?;
63 Ok::<Response, Error>(text(format!("hello {}\n", id.user_id)))
64 })
65 .get("/admin-only", |req, _params| async move {
66 let id = require_admin(req.ctx())?;
67 Ok::<Response, Error>(text(format!("hello admin {}\n", id.user_id)))
68 })
69 .get("/crash", |_req, _params| async {
70 Err::<Response, Error>(Error::Internal("simulated failure".into()))
71 })
72 .get("/unauth", |_req, _params| async {
73 Err::<Response, Error>(Error::Unauthorized)
74 })
75 .wrap(request_id)
76 .wrap(authenticate)
77 .wrap(logger);
78 Server::bind(addr).serve_router(router).await
79}