use core::panic; use std::{convert::Infallible, fmt::Display}; use handlebars::{Handlebars, RenderError}; use serde::Serialize; use warp::{hyper::StatusCode, reject::Reject, Filter, Rejection, Reply}; #[derive(Debug, Clone)] pub struct Server { hb: Handlebars<'static>, profiler: Profiler, } #[derive(Debug, Clone)] struct Profiler; impl Profiler { fn profile(&self, username: String) -> Result { Ok(Profile { username }) } } impl Server { pub fn new() -> Self { let mut hb = Handlebars::new(); hb.register_template_string("profile", include_str!("../templates/html/profile.html")) .expect("profile template"); let profiler = Profiler; Self { hb, profiler } } pub async fn listen_and_serve(self, port: u16) -> ! { println!("starting server on port {}", port); warp::serve(self.html().recover(Self::handle_rejection)) .run(([127, 0, 0, 1], port)) .await; panic!("server stopped prematurely") } fn html(&self) -> impl Filter + Clone { Self::index().or(self.profile()) } fn index() -> impl Filter + Clone { warp::get().and(warp::path::end().map(move || { warp::reply::html(include_str!("../templates/html/index.html").to_owned()) })) } fn with_server( srv: Server, ) -> impl Filter + Clone { warp::any().map(move || srv.clone()) } fn profile(&self) -> impl Filter + Clone { warp::get().and( warp::path!("@" / String) .and(Self::with_server(self.clone())) .and_then(|username: String, srv: Server| async move { match srv.hb.render( "profile", &serde_json::json!(srv.profiler.profile(username)?), ) { Ok(html) => Ok(warp::reply::html(html)), Err(err) => Err(InternalError::reject(err.to_string())), } }), ) } async fn handle_rejection(err: Rejection) -> Result { if let Some(internal) = err.find::() { println!("internal error: {}", internal); return Ok(warp::reply::with_status( "internal server error", StatusCode::INTERNAL_SERVER_ERROR, )); } panic!() } } #[derive(Clone, Debug)] struct InternalError { inner: String, } impl InternalError { fn reject(err: String) -> Rejection { warp::reject::custom(Self { inner: err }) } } impl Reject for InternalError {} impl From for InternalError { fn from(r: RenderError) -> Self { Self { inner: r.to_string(), } } } impl Display for InternalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.inner) } } #[derive(Serialize)] struct Profile { username: String, }