use core::panic; use std::{fmt::Display, net::SocketAddr}; use axum::{ http::StatusCode, response::{self, IntoResponse}, Extension, Router, }; use handlebars::{Handlebars, RenderError}; use tower_cookies::CookieManagerLayer; use crate::svc::{ auth::{Auth, AuthError}, profiles::{Profiler, UserError}, }; #[derive(Clone)] pub struct Server { pub(super) hb: Handlebars<'static>, pub(super) profiler: Profiler, pub(super) auth: Auth, } impl Server { pub fn new(profiler: Profiler, auth: Auth) -> Self { let mut hb = Handlebars::new(); hb.register_template_string("profile", include_str!("../../templates/html/profile.html")) .expect("profile template"); hb.register_template_string("login", include_str!("../../templates/html/login.html")) .expect("login template"); hb.register_template_string("signup", include_str!("../../templates/html/signup.html")) .expect("login template"); hb.register_template_string( "redirect", include_str!("../../templates/html/html-redirect.html"), ) .expect("redirect template"); hb.register_template_string("error-partial", r#"

{{message}}

"#) .expect("error-partial"); hb.register_template_string("success-partial", r#"

{{message}}

"#) .expect("success-partial"); hb.register_template_string("index", include_str!("../../templates/html/index.html")) .expect("index"); hb.register_template_string("err404", include_str!("../../templates/html/404.html")) .expect("err404"); hb.register_partial( "LoggedOut", include_str!("../../templates/html/nav-loggedout.html"), ) .expect("LoggedOut"); hb.register_partial( "LoggedIn", include_str!("../../templates/html/nav-loggedin.html"), ) .expect("LoggedIn"); Self { hb, profiler, auth } } pub async fn listen_and_serve(self, port: u16) -> ! { let router = Router::new(); let router = self .register_html(&router) .layer(Extension::(self.clone())) .layer(CookieManagerLayer::new()); let addr = SocketAddr::from(([127, 0, 0, 1], port)); println!("listening on {}", addr); axum::Server::bind(&addr) .serve(router.into_make_service()) .await .unwrap(); panic!("server stopped prematurely") } } #[derive(Clone, Debug)] pub(super) enum ServerError { Internal(String), NotFound, BadRequest(String), } impl From for ServerError { fn from(r: RenderError) -> Self { Self::Internal(r.to_string()) } } impl From for ServerError { fn from(u: UserError) -> Self { match u { UserError::Duplicate => Self::BadRequest("duplicate entry exists".to_owned()), UserError::NotFound => Self::NotFound, UserError::Other(o) => Self::Internal(format!("UserError: {}", o)), } } } impl From for ServerError { fn from(a: AuthError) -> Self { match a { AuthError::InvalidCredentials => { ServerError::BadRequest("invalid credentials".to_owned()) } AuthError::ServerError(err) => ServerError::Internal(err), AuthError::Expired => ServerError::BadRequest("expired token".to_owned()), } } } impl Display for ServerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) } } impl IntoResponse for ServerError { fn into_response(self) -> axum::response::Response { match self { ServerError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(), ServerError::NotFound => ( StatusCode::NOT_FOUND, response::Html(include_str!("../../templates/html/404.html")), ) .into_response(), ServerError::BadRequest(err) => (StatusCode::BAD_REQUEST, err).into_response(), } } }