use std::net::Ipv4Addr;
use bytes::Bytes;
use http_body_util::BodyExt;
use kynos::{
http::{self, body::Body},
middleware::{Continued, Interceptor, Next, limits::BodySize},
prelude::*,
server::Server,
};
use serde::{Deserialize, Serialize};
#[derive(Schema, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
}
struct Print;
impl Print {
async fn drain(body: Body) -> Option<Bytes> {
body.collect()
.await
.ok()
.map(http_body_util::Collected::to_bytes)
}
fn show(label: &str, bytes: &Bytes) {
if bytes.is_empty() {
println!("{label}: <empty>");
} else {
println!("{label}: {}", String::from_utf8_lossy(bytes));
}
}
}
#[derive(Debug, thiserror::Error, ApiError)]
enum Unreadable {
#[error("the request body could not be read")]
#[problem(status = 400)]
Request,
#[error("the response body could not be read")]
#[problem(status = 500)]
Response,
}
impl<C: Sync + 'static> Interceptor<C> for Print {
type Reads = ();
type Adds = ();
type Short = Unreadable;
async fn intercept(
&self,
request: http::Request,
reads: (),
context: &C,
next: Next<'_, C>,
) -> Result<Continued<()>, Unreadable> {
let _ = (context, reads);
let route = next.route();
println!(
"--> {} {} ({})",
route.method(),
route.path(),
route.operation_id(),
);
for (name, value) in request.headers() {
println!("--> {name}: {}", String::from_utf8_lossy(value.as_bytes()));
}
let (parts, body) = request.into_parts();
let Some(bytes) = Self::drain(body).await else {
return Err(Unreadable::Request);
};
Self::show("-->", &bytes);
let mut continued = next
.run(http::Request::from_parts(parts, Body::from_bytes(bytes)))
.await;
println!("<-- {}", continued.status());
for (name, value) in continued.headers() {
println!("<-- {name}: {}", String::from_utf8_lossy(value.as_bytes()));
}
let Some(bytes) = Self::drain(continued.take_body()).await else {
return Err(Unreadable::Response);
};
Self::show("<--", &bytes);
continued.set_body(Body::from_bytes(bytes));
Ok(continued)
}
}
#[kynos::post("/users")]
async fn create_user(Json(user): Json<User>) -> Created<Json<User>> {
Created::at(format!("/users/{}", user.id), Json(user))
}
#[kynos::get("/users")]
async fn list_users() -> Json<Vec<User>> {
Json(vec![User {
id: 1,
name: "Ada Lovelace".to_owned(),
}])
}
#[tokio::main]
async fn main() -> kynos::Result<()> {
let router = Router::<()>::new()
.intercept(BodySize::new(64 * 1_024))
.intercept(Print)
.mount(kynos::routes![create_user, list_users]);
let document = router.openapi()?;
println!("{}", document.to_json()?);
Server::new(router.build(())?)
.bind((Ipv4Addr::UNSPECIFIED, 3000))
.serve()
.await
}