From 93aeb80c56dd710bb7bb195c55ce36c87acc145c Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Thu, 9 May 2024 08:39:46 +0200 Subject: [PATCH] opds error handling --- rusty-library/src/handlers/author.rs | 9 ++++-- rusty-library/src/handlers/cover.rs | 7 ++-- rusty-library/src/handlers/download.rs | 7 ++-- rusty-library/src/handlers/error.rs | 36 +++++++++++++-------- rusty-library/src/handlers/opds.rs | 15 +++++---- rusty-library/src/handlers/paginated.rs | 8 ++--- rusty-library/src/handlers/recents.rs | 7 ++-- rusty-library/src/handlers/series_single.rs | 12 +++++-- rusty-library/src/main.rs | 6 ++-- rusty-library/src/opds/error.rs | 15 +++++++++ rusty-library/src/opds/feed.rs | 18 +++++------ rusty-library/src/opds/mod.rs | 1 + 12 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 rusty-library/src/opds/error.rs diff --git a/rusty-library/src/handlers/author.rs b/rusty-library/src/handlers/author.rs index bfaffdb..aee053b 100644 --- a/rusty-library/src/handlers/author.rs +++ b/rusty-library/src/handlers/author.rs @@ -9,7 +9,7 @@ use poem::{ use tera::Context; use crate::{ - app_state::AppState, data::book::Book, handlers::error::SqliteError, templates::TEMPLATES, + app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES, }; #[handler] @@ -17,11 +17,14 @@ pub async fn handler( id: Path, state: Data<&Arc>, ) -> Result, poem::Error> { - let author = state.calibre.scalar_author(*id).map_err(SqliteError)?; + let author = state + .calibre + .scalar_author(*id) + .map_err(HandlerError::DataError)?; let books = state .calibre .author_books(*id, u32::MAX.into(), None, SortOrder::ASC) - .map_err(SqliteError)?; + .map_err(HandlerError::DataError)?; let books = books .iter() .filter_map(|x| Book::full_book(x, &state)) diff --git a/rusty-library/src/handlers/cover.rs b/rusty-library/src/handlers/cover.rs index fb40428..022cb3d 100644 --- a/rusty-library/src/handlers/cover.rs +++ b/rusty-library/src/handlers/cover.rs @@ -7,14 +7,17 @@ use poem::{ IntoResponse, }; -use crate::{app_state::AppState, handlers::error::SqliteError}; +use crate::{app_state::AppState, handlers::error::HandlerError}; #[handler] pub async fn handler( id: Path, state: Data<&Arc>, ) -> Result>, poem::Error> { - let book = state.calibre.scalar_book(*id).map_err(SqliteError)?; + let book = state + .calibre + .scalar_book(*id) + .map_err(HandlerError::DataError)?; let cover_path = state.config.library_path.join(book.path).join("cover.jpg"); let mut cover = File::open(cover_path).map_err(|_| NotFoundError)?; diff --git a/rusty-library/src/handlers/download.rs b/rusty-library/src/handlers/download.rs index 5c7dcbb..bbb2e25 100644 --- a/rusty-library/src/handlers/download.rs +++ b/rusty-library/src/handlers/download.rs @@ -7,14 +7,17 @@ use poem::{ IntoResponse, }; -use crate::{app_state::AppState, data::book::Book, handlers::error::SqliteError}; +use crate::{app_state::AppState, data::book::Book, handlers::error::HandlerError}; #[handler] pub async fn handler( Path((id, format)): Path<(u64, String)>, state: Data<&Arc>, ) -> Result>>, poem::Error> { - let book = state.calibre.scalar_book(id).map_err(SqliteError)?; + let book = state + .calibre + .scalar_book(id) + .map_err(HandlerError::DataError)?; let book = Book::full_book(&book, &state).ok_or(NotFoundError)?; let format: &str = format.as_str(); let file_name = book.formats.get(format).ok_or(NotFoundError)?; diff --git a/rusty-library/src/handlers/error.rs b/rusty-library/src/handlers/error.rs index cf0b966..bd98a4f 100644 --- a/rusty-library/src/handlers/error.rs +++ b/rusty-library/src/handlers/error.rs @@ -1,32 +1,40 @@ use calibre_db::data::error::DataStoreError; use poem::{error::ResponseError, http::StatusCode, Body, Response}; +use thiserror::Error; use tracing::error; use uuid::Uuid; -#[derive(Debug, thiserror::Error)] -#[error("sqlite error")] -pub struct SqliteError(pub DataStoreError); +use crate::opds::error::OpdsError; -impl From for SqliteError { - fn from(item: DataStoreError) -> Self { - SqliteError(item) - } +#[derive(Error, Debug)] +#[error("opds error")] +pub enum HandlerError { + #[error("opds error")] + OpdsError(#[from] OpdsError), + #[error("data error")] + DataError(#[from] DataStoreError), } -impl ResponseError for SqliteError { +impl ResponseError for HandlerError { fn status(&self) -> StatusCode { - match &self.0 { - DataStoreError::NoResults(_) => StatusCode::NOT_FOUND, - _ => StatusCode::INTERNAL_SERVER_ERROR, + match &self { + HandlerError::OpdsError(e) => StatusCode::INTERNAL_SERVER_ERROR, + HandlerError::DataError(e) => match e { + DataStoreError::NoResults(_) => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }, } } fn as_response(&self) -> Response { let id = Uuid::new_v4(); let internal_msg = format!("{:?}", self); - let external_msg = match &self.0 { - DataStoreError::NoResults(_) => "item not found", - _ => "internal server error", + let external_msg = match &self { + HandlerError::OpdsError(e) => "internal server error", + HandlerError::DataError(e) => match e { + DataStoreError::NoResults(_) => "item not found", + _ => "internal server error", + }, }; error!("{id}: {internal_msg}"); diff --git a/rusty-library/src/handlers/opds.rs b/rusty-library/src/handlers/opds.rs index ce224c2..a17236b 100644 --- a/rusty-library/src/handlers/opds.rs +++ b/rusty-library/src/handlers/opds.rs @@ -2,14 +2,14 @@ use std::sync::Arc; use poem::{ handler, - web::{headers::ContentType, Data, WithContentType}, + web::{Data, WithContentType}, IntoResponse, }; -use quick_xml::se::to_string; -use time::macros::datetime; +use time::OffsetDateTime; use crate::{ app_state::AppState, + handlers::error::HandlerError, opds::{ author::Author, content::Content, entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation, @@ -18,6 +18,8 @@ use crate::{ #[handler] pub async fn handler(state: Data<&Arc>) -> Result, poem::Error> { + let now = OffsetDateTime::now_utc(); + let author = Author { name: "Thallian".to_string(), uri: "https://code.vanwa.ch/shu/rusty-library".to_string(), @@ -40,7 +42,7 @@ pub async fn handler(state: Data<&Arc>) -> Result>) -> Result( template: &str, @@ -22,7 +22,7 @@ where P: Fn(&str) -> Result, M: Fn(&str) -> Result, { - let items = fetcher().map_err(SqliteError)?; + let items = fetcher().map_err(HandlerError::DataError)?; let mut context = Context::new(); @@ -32,8 +32,8 @@ where let (backward_cursor, forward_cursor) = (sort_field(first_item), sort_field(last_item)); - let has_previous = has_previous(&backward_cursor).map_err(SqliteError)?; - let has_more = has_more(&forward_cursor).map_err(SqliteError)?; + let has_previous = has_previous(&backward_cursor).map_err(HandlerError::DataError)?; + let has_more = has_more(&forward_cursor).map_err(HandlerError::DataError)?; context.insert("has_previous", &has_previous); context.insert("has_more", &has_more); diff --git a/rusty-library/src/handlers/recents.rs b/rusty-library/src/handlers/recents.rs index b345a66..1261542 100644 --- a/rusty-library/src/handlers/recents.rs +++ b/rusty-library/src/handlers/recents.rs @@ -8,12 +8,15 @@ use poem::{ use tera::Context; use crate::{ - app_state::AppState, data::book::Book, handlers::error::SqliteError, templates::TEMPLATES, + app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES, }; #[handler] pub async fn handler(state: Data<&Arc>) -> Result, poem::Error> { - let recent_books = state.calibre.recent_books(25).map_err(SqliteError)?; + let recent_books = state + .calibre + .recent_books(25) + .map_err(HandlerError::DataError)?; let recent_books = recent_books .iter() .filter_map(|x| Book::full_book(x, &state)) diff --git a/rusty-library/src/handlers/series_single.rs b/rusty-library/src/handlers/series_single.rs index 764afe4..600fd99 100644 --- a/rusty-library/src/handlers/series_single.rs +++ b/rusty-library/src/handlers/series_single.rs @@ -8,7 +8,7 @@ use poem::{ use tera::Context; use crate::{ - app_state::AppState, data::book::Book, handlers::error::SqliteError, templates::TEMPLATES, + app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES, }; #[handler] @@ -16,8 +16,14 @@ pub async fn handler( id: Path, state: Data<&Arc>, ) -> Result, poem::Error> { - let series = state.calibre.scalar_series(*id).map_err(SqliteError)?; - let books = state.calibre.series_books(*id).map_err(SqliteError)?; + let series = state + .calibre + .scalar_series(*id) + .map_err(HandlerError::DataError)?; + let books = state + .calibre + .series_books(*id) + .map_err(HandlerError::DataError)?; let books = books .iter() .filter_map(|x| Book::full_book(x, &state)) diff --git a/rusty-library/src/main.rs b/rusty-library/src/main.rs index f0283cc..97021c1 100644 --- a/rusty-library/src/main.rs +++ b/rusty-library/src/main.rs @@ -6,7 +6,8 @@ use clap::Parser; use cli::Cli; use config::Config; use poem::{ - endpoint::EmbeddedFilesEndpoint, get, listener::TcpListener, EndpointExt, Route, Server, + endpoint::EmbeddedFilesEndpoint, get, listener::TcpListener, middleware::Tracing, EndpointExt, + Route, Server, }; use rust_embed::RustEmbed; @@ -70,7 +71,8 @@ async fn main() -> Result<(), std::io::Error> { .at("/cover/:id", get(handlers::cover::handler)) .at("/book/:id/:format", get(handlers::download::handler)) .nest("/static", EmbeddedFilesEndpoint::::new()) - .data(app_state); + .data(app_state) + .with(Tracing); Server::new(TcpListener::bind("[::]:3000")) .name("cops-web") diff --git a/rusty-library/src/opds/error.rs b/rusty-library/src/opds/error.rs new file mode 100644 index 0000000..c644cab --- /dev/null +++ b/rusty-library/src/opds/error.rs @@ -0,0 +1,15 @@ +use std::string::FromUtf8Error; + +use quick_xml::DeError; +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("opds error")] +pub enum OpdsError { + #[error("failed to serialize struct")] + SerializingError(#[from] DeError), + #[error("xml failure")] + XmlError(#[from] quick_xml::Error), + #[error("failed to decode as utf-8")] + Utf8Error(#[from] FromUtf8Error), +} diff --git a/rusty-library/src/opds/feed.rs b/rusty-library/src/opds/feed.rs index c944874..70dd68d 100644 --- a/rusty-library/src/opds/feed.rs +++ b/rusty-library/src/opds/feed.rs @@ -8,7 +8,7 @@ use quick_xml::{ use serde::Serialize; use time::OffsetDateTime; -use super::{author::Author, entry::Entry, link::Link}; +use super::{author::Author, entry::Entry, error::OpdsError, link::Link}; #[derive(Debug, Serialize)] #[serde(rename = "feed")] @@ -26,14 +26,14 @@ pub struct Feed { } impl Feed { - pub fn as_xml(&self) -> String { - let xml = to_string(&self).unwrap(); + pub fn as_xml(&self) -> Result { + let xml = to_string(&self)?; let mut reader = Reader::from_str(&xml); reader.trim_text(true); let declaration = BytesDecl::new("1.0", Some("UTF-8"), None); let mut writer = Writer::new(Cursor::new(Vec::new())); - writer.write_event(Event::Decl(declaration)).unwrap(); + writer.write_event(Event::Decl(declaration))?; let mut feed_start = BytesStart::new("feed"); feed_start.push_attribute(("xmlns", "http://www.w3.org/2005/Atom")); @@ -45,15 +45,15 @@ impl Feed { loop { match reader.read_event() { - Ok(Event::Start(e)) if e.name().as_ref() == b"feed" => writer - .write_event(Event::Start(feed_start.clone())) - .unwrap(), + Ok(Event::Start(e)) if e.name().as_ref() == b"feed" => { + writer.write_event(Event::Start(feed_start.clone()))? + } Ok(Event::Eof) => break, - Ok(e) => writer.write_event(e).unwrap(), + Ok(e) => writer.write_event(e)?, Err(e) => (), } } let result = writer.into_inner().into_inner(); - String::from_utf8(result).unwrap() + Ok(String::from_utf8(result)?) } } diff --git a/rusty-library/src/opds/mod.rs b/rusty-library/src/opds/mod.rs index d21cb98..dcd52e0 100644 --- a/rusty-library/src/opds/mod.rs +++ b/rusty-library/src/opds/mod.rs @@ -1,6 +1,7 @@ pub mod author; pub mod content; pub mod entry; +pub mod error; pub mod feed; pub mod link; pub mod media_type;