Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/routes/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use askama_web::WebTemplate;
use axum::extract::State;

use crate::extractors::user::User;
use crate::state::{AppState, AppStateContext, error::AppStateError};
use crate::state::{AppState, AppStateContext, error::*};

use std::collections::HashMap;

Expand Down
46 changes: 38 additions & 8 deletions src/state/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use askama::Template;
use askama_web::WebTemplate;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use snafu::ErrorCompat;
use snafu::prelude::*;

use super::free_space::FreeSpaceError;
Expand All @@ -15,30 +16,43 @@ pub enum AppStateError {
API { source: hightorrent_api::ApiError },
#[snafu(display("Failed to get free space information"))]
FreeSpace { source: FreeSpaceError },
#[snafu(display("An other error occurred"))]
Other {
source: Box<dyn snafu::Error + Send + Sync + 'static>,
},
}

impl AppStateError {
pub fn inner_errors(&self) -> Vec<Box<dyn std::error::Error + '_>> {
let mut inner_errors = vec![];
for error in self.iter_chain().skip(1) {
inner_errors.push(Box::new(error).into());
}
inner_errors
}
}

/// Global error page generated from an [AppStateError].
#[derive(Clone, Debug, Template, WebTemplate)]
#[derive(Debug, Template, WebTemplate)]
#[template(path = "error.html")]
pub struct AppStateErrorContext {
state: AppStateErrorContextInner,
}

/// Helper struct so we can reuse base.html
/// with all it's `state.foo` expressions.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct AppStateErrorContextInner {
// TODO: typed errors
// errors: Vec<AppStateError>,
errors: Vec<String>,
// TODO: askama doesn't handle recursion well, so we convert
// all errors to strings. Maybe related to:
// https://github.com/askama-rs/askama/issues/393
errors: Vec<AppStateError>,
}

impl From<AppStateError> for AppStateErrorContext {
fn from(e: AppStateError) -> Self {
Self {
state: AppStateErrorContextInner {
errors: vec![e.to_string()],
},
state: AppStateErrorContextInner { errors: vec![e] },
}
}
}
Expand All @@ -49,3 +63,19 @@ impl IntoResponse for AppStateError {
(StatusCode::INTERNAL_SERVER_ERROR, error_context).into_response()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn can_extract_from_any_error() {
let res: Result<_, AppStateError> =
std::fs::read("/tmp/qsjlkdjsqlkdsqfsqhsjklfhalkjfkjh.toml")
.boxed()
.context(OtherSnafu);

assert!(res.is_err());
assert_eq!(&res.unwrap_err().to_string(), "An other error occurred");
}
}
3 changes: 2 additions & 1 deletion src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ pub struct AppState {
pub struct AppStateContext {
// TODO: proper categories
pub categories: Vec<String>,
pub errors: Vec<String>,
pub errors: Vec<AppStateError>,
// pub errors: Vec<String>,
pub free_space: FreeSpace,
}

Expand Down
10 changes: 8 additions & 2 deletions templates/base.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{#{% import "menus/macro.html" as menus %}#}
<!DOCTYPE html>
<html>
<head>
Expand All @@ -17,7 +16,14 @@
<div class="container">
{% for error in state.errors %}
<div class="notification is-danger">
<h2 style="font-weight: bold; text-align: center;">{{ error }}</h2>
<details>
<summary>{{ error }}</summary>
<ul class="ml-5">
{% for inner_error in error.inner_errors() %}
<li>→ {{ inner_error }}</li>
{% endfor %}
</ul>
</details>
</div>
{% endfor %}
</div>
Expand Down