use anyhow::{ Context, }; use askama::Template; use axum::{ extract::{ Path, State, }, http::{ StatusCode, }, response::{ IntoResponse, Response, }, Router, routing::{ get, }, }; use crate::{ storage::{ Storage, }, templates::{ FlakeListTemplate, FlakeTemplate, IndexTemplate, RevisionTemplate, }, }; struct AppError(anyhow::Error); impl std::fmt::Display for AppError { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, formatter) } } impl std::fmt::Debug for AppError { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Debug::fmt(&self.0, formatter) } } impl IntoResponse for AppError { fn into_response(self) -> Response { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Internal server error:\n{:?}", self), ) .into_response() } } impl From for AppError where E: Into { fn from(err: E) -> Self { Self(err.into()) } } fn render_template(t: &T) -> anyhow::Result { let body = t.render() .context("Failed to render template")?; Ok(( [ ("Content-Type", T::MIME_TYPE), ], body, ).into_response()) } #[derive(Clone)] struct AppState { storage: Storage, } pub fn make_router(storage: Storage) -> anyhow::Result { let state = AppState { storage, }; let app = Router::new() .route("/", get(route_index)) .route("/flakes", get(route_flakes)) .route("/f/{uri}", get(route_flake)) .route("/r/{revision_uri}", get(route_revision)) .with_state(state); Ok(app) } async fn route_index() -> Result { Ok(render_template(&IndexTemplate {})?) } async fn route_flakes( State(state): State, ) -> Result { Ok(render_template(&FlakeListTemplate { flakes: state.storage.flakes().await?, })?) } async fn route_flake( State(state): State, Path(uri): Path, ) -> Result { Ok(render_template(&FlakeTemplate { uri: uri.clone(), revisions: state.storage.revisions_from_flake(&uri).await?, current_inputs: state.storage.current_inputs_for_flake(&uri).await?, })?) } async fn route_revision( State(state): State, Path(revision_uri): Path, ) -> Result { Ok(render_template(&RevisionTemplate { revision_uri: revision_uri.clone(), inputs: state.storage.inputs_for_revision(&revision_uri).await?, input_of: state.storage.input_of_for_revision(&revision_uri).await?, })?) }