Refactor error handling to use anyhow throughout codebase

Switch all custom error types and handler signatures from `Box<dyn
std::error::Error + Send + Sync>` to `anyhow::Result`. Add anyhow as a
dependency. Fix typos and update function names for consistency.
This commit is contained in:
2025-06-22 16:26:27 +02:00
parent 39ff5f01e7
commit 2f33b41359
16 changed files with 74 additions and 109 deletions

1
Cargo.lock generated
View File

@@ -368,6 +368,7 @@ dependencies = [
name = "book_bot"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"axum-prometheus",
"base64",

View File

@@ -54,3 +54,4 @@ smartstring = { version = "1.0.1", features = ["serde"] }
moka = { version = "0.12.10", features = ["future"] }
sentry = { version = "0.40.0", features = ["debug-images"] }
anyhow = "1.0.98"

View File

@@ -24,8 +24,8 @@ use self::{
};
use super::{
bots_manager::get_manager_handler, ignore_channel_messages, ignore_chat_member_update,
ignore_user_edited_message, ingore_chat_join_request, BotCommands, BotHandler,
bots_manager::get_manager_handler, ignore_channel_messages, ignore_chat_join_request,
ignore_chat_member_update, ignore_user_edited_message, BotCommands, BotHandler,
};
async fn _update_activity(me: teloxide::types::Me, user: teloxide::types::User) -> Option<()> {
@@ -87,7 +87,7 @@ pub fn get_approved_handler() -> (BotHandler, BotCommands) {
.branch(ignore_channel_messages())
.branch(ignore_chat_member_update())
.branch(ignore_user_edited_message())
.branch(ingore_chat_join_request())
.branch(ignore_chat_join_request())
.branch(update_user_activity_handler())
.branch(get_help_handler())
.branch(get_settings_handler())

View File

@@ -46,7 +46,7 @@ pub async fn send_annotation_handler<T, Fut>(
) -> BotHandlerInternal
where
T: AnnotationFormat,
Fut: std::future::Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<T>>,
{
let id = match command {
AnnotationCommand::Book { id } => id,
@@ -63,7 +63,7 @@ where
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
Err(err) => Err(err.into()),
};
};
@@ -87,10 +87,11 @@ where
};
if !annotation.is_normal_text() {
return Err(Box::new(AnnotationFormatError {
return Err(AnnotationFormatError {
_command: command,
_text: annotation.get_text().to_string(),
}));
}
.into());
}
let annotation_text = annotation.get_text();
@@ -120,7 +121,7 @@ pub async fn annotation_pagination_handler<T, Fut>(
) -> BotHandlerInternal
where
T: AnnotationFormat,
Fut: std::future::Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<T>>,
{
let (id, page) = match callback_data {
AnnotationCallbackData::Book { id, page } => (id, page),

View File

@@ -39,7 +39,7 @@ async fn send_book_handler<T, P, Fut>(
where
T: Format + Clone + Debug,
P: FormatTitle + Clone + Debug,
Fut: std::future::Future<Output = Result<Page<T, P>, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<Page<T, P>>>,
{
let id = match command {
BookCommand::Author { id } => id,
@@ -59,7 +59,7 @@ where
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
Err(err) => Err(err.into()),
}
}
};
@@ -110,7 +110,7 @@ async fn send_pagination_book_handler<T, P, Fut>(
where
T: Format + Clone + Debug,
P: FormatTitle + Clone + Debug,
Fut: std::future::Future<Output = Result<Page<T, P>, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<Page<T, P>>>,
{
let (id, page) = match callback_data {
BookCallbackData::Author { id, page } => (id, page),

View File

@@ -77,7 +77,7 @@ async fn _send_cached(
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
Err(err) => Err(err.into()),
}
}
@@ -145,7 +145,7 @@ async fn _send_downloaded_file(
Ok(_) => (),
Err(err) => {
log::error!("Download error: {:?} | {:?}", filename, err);
return Err(Box::new(err));
return Err(err.into());
}
}
@@ -163,27 +163,22 @@ async fn send_with_download_from_channel(
download_data: DownloadQueryData,
need_delete_message: bool,
) -> BotHandlerInternal {
match download_file(&download_data).await {
Ok(v) => {
let download_file = match v {
Some(v) => v,
None => {
return Ok(());
}
};
_send_downloaded_file(&message, bot.clone(), download_file).await?;
if need_delete_message {
if let MaybeInaccessibleMessage::Regular(message) = message {
bot.delete_message(message.chat.id, message.id).await?;
};
}
Ok(())
let downloaded_file = match download_file(&download_data).await? {
Some(v) => v,
None => {
return Ok(());
}
Err(err) => Err(err),
};
_send_downloaded_file(&message, bot.clone(), downloaded_file).await?;
if need_delete_message {
if let MaybeInaccessibleMessage::Regular(message) = message {
bot.delete_message(message.chat.id, message.id).await?;
};
}
Ok(())
}
async fn download_handler(

View File

@@ -38,7 +38,7 @@ pub async fn help_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotH
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
Err(err) => Err(err.into()),
}
}

View File

@@ -70,7 +70,7 @@ async fn random_handler(
async fn get_random_item_handler_internal<T>(
cq: CallbackQuery,
bot: CacheMe<Throttle<Bot>>,
item: Result<T, Box<dyn std::error::Error + Send + Sync>>,
item: anyhow::Result<T>,
) -> BotHandlerInternal
where
T: Format,
@@ -118,7 +118,7 @@ async fn get_random_item_handler<T, Fut>(
) -> BotHandlerInternal
where
T: Format,
Fut: std::future::Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<T>>,
{
let allowed_langs = get_user_or_default_lang_codes(cq.from.id).await;

View File

@@ -40,7 +40,7 @@ async fn generic_search_pagination_handler<T, P, Fut>(
where
T: Format + Clone + Debug,
P: FormatTitle + Clone + Debug,
Fut: std::future::Future<Output = Result<Page<T, P>, Box<dyn std::error::Error + Send + Sync>>>,
Fut: std::future::Future<Output = anyhow::Result<Page<T, P>>>,
{
let chat_id = cq.chat_id();
let user_id = cq.from.id;
@@ -106,11 +106,11 @@ where
};
}
let formated_page = items_page.format(page, 4096);
let formatted_page = items_page.format(page, 4096);
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true);
bot.edit_message_text(chat_id, message_id, formated_page)
bot.edit_message_text(chat_id, message_id, formatted_page)
.reply_markup(keyboard)
.send()
.await?;

View File

@@ -69,7 +69,7 @@ async fn update_log_command(message: Message, bot: CacheMe<Throttle<Bot>>) -> Bo
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
Err(err) => Err(err.into()),
}
}

View File

@@ -43,9 +43,7 @@ pub struct Task {
pub content_size: Option<u64>,
}
pub async fn create_task(
data: CreateTaskData,
) -> Result<Task, Box<dyn std::error::Error + Send + Sync>> {
pub async fn create_task(data: CreateTaskData) -> anyhow::Result<Task> {
Ok(CLIENT
.post(format!("{}/api/", &config::CONFIG.batch_downloader_url))
.body(serde_json::to_string(&data).unwrap())
@@ -58,7 +56,7 @@ pub async fn create_task(
.await?)
}
pub async fn get_task(task_id: String) -> Result<Task, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_task(task_id: String) -> anyhow::Result<Task> {
Ok(CLIENT
.get(format!(
"{}/api/check_archive/{task_id}",

View File

@@ -43,7 +43,7 @@ pub async fn get_cached_message(
pub async fn download_file(
download_data: &DownloadQueryData,
) -> Result<Option<DownloadFile>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<Option<DownloadFile>> {
let DownloadQueryData::DownloadData {
book_id: id,
file_type: format,
@@ -93,7 +93,7 @@ pub async fn download_file(
pub async fn download_file_by_link(
filename: String,
link: String,
) -> Result<Option<DownloadFile>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<Option<DownloadFile>> {
let response = CLIENT.get(link).send().await?;
if response.status() != StatusCode::OK {

View File

@@ -23,10 +23,7 @@ fn get_allowed_langs_params(
.collect()
}
async fn _make_request<T>(
url: &str,
params: Vec<(&str, SmartString)>,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
async fn _make_request<T>(url: &str, params: Vec<(&str, SmartString)>) -> anyhow::Result<T>
where
T: DeserializeOwned,
{
@@ -42,19 +39,19 @@ where
Ok(v) => Ok(v),
Err(err) => {
log::error!("Failed serialization: url={:?} err={:?}", url, err);
Err(Box::new(err))
Err(err.into())
}
}
}
pub async fn get_book(id: u32) -> Result<types::Book, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_book(id: u32) -> anyhow::Result<types::Book> {
_make_request(&format!("/api/v1/books/{id}"), vec![]).await
}
pub async fn get_random_book_by_genre(
allowed_langs: SmallVec<[SmartString; 3]>,
genre: Option<u32>,
) -> Result<types::Book, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Book> {
let mut params = get_allowed_langs_params(allowed_langs);
if let Some(v) = genre {
@@ -66,13 +63,13 @@ pub async fn get_random_book_by_genre(
pub async fn get_random_book(
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Book, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Book> {
get_random_book_by_genre(allowed_langs, None).await
}
pub async fn get_random_author(
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Author, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Author> {
let params = get_allowed_langs_params(allowed_langs);
_make_request("/api/v1/authors/random", params).await
@@ -80,19 +77,17 @@ pub async fn get_random_author(
pub async fn get_random_sequence(
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Sequence, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Sequence> {
let params = get_allowed_langs_params(allowed_langs);
_make_request("/api/v1/sequences/random", params).await
}
pub async fn get_genre_metas() -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_genre_metas() -> anyhow::Result<Vec<String>> {
_make_request("/api/v1/genres/metas", vec![]).await
}
pub async fn get_genres(
meta: SmartString,
) -> Result<types::Page<types::Genre, Empty>, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_genres(meta: SmartString) -> anyhow::Result<types::Page<types::Genre, Empty>> {
let params = vec![("meta", meta)];
_make_request("/api/v1/genres", params).await
@@ -104,7 +99,7 @@ pub async fn search_book(
query: String,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::SearchBook, Empty>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Page<types::SearchBook, Empty>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -117,7 +112,7 @@ pub async fn search_author(
query: String,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::Author, Empty>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Page<types::Author, Empty>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -130,7 +125,7 @@ pub async fn search_sequence(
query: String,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::Sequence, Empty>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Page<types::Sequence, Empty>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -143,7 +138,7 @@ pub async fn search_translator(
query: String,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::Translator, Empty>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Page<types::Translator, Empty>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -156,15 +151,11 @@ pub async fn search_translator(
.await
}
pub async fn get_book_annotation(
id: u32,
) -> Result<types::BookAnnotation, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_book_annotation(id: u32) -> anyhow::Result<types::BookAnnotation> {
_make_request(format!("/api/v1/books/{id}/annotation").as_str(), vec![]).await
}
pub async fn get_author_annotation(
id: u32,
) -> Result<types::AuthorAnnotation, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_author_annotation(id: u32) -> anyhow::Result<types::AuthorAnnotation> {
_make_request(format!("/api/v1/authors/{id}/annotation").as_str(), vec![]).await
}
@@ -172,10 +163,7 @@ pub async fn get_author_books(
id: u32,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<
types::Page<types::AuthorBook, types::BookAuthor>,
Box<dyn std::error::Error + Send + Sync>,
> {
) -> anyhow::Result<types::Page<types::AuthorBook, types::BookAuthor>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -188,10 +176,7 @@ pub async fn get_translator_books(
id: u32,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<
types::Page<types::TranslatorBook, types::BookTranslator>,
Box<dyn std::error::Error + Send + Sync>,
> {
) -> anyhow::Result<types::Page<types::TranslatorBook, types::BookTranslator>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -204,10 +189,7 @@ pub async fn get_sequence_books(
id: u32,
page: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<
types::Page<types::SequenceBook, types::Sequence>,
Box<dyn std::error::Error + Send + Sync>,
> {
) -> anyhow::Result<types::Page<types::SequenceBook, types::Sequence>> {
let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into()));
@@ -220,7 +202,7 @@ pub async fn get_uploaded_books(
page: u32,
uploaded_gte: SmartString,
uploaded_lte: SmartString,
) -> Result<types::Page<types::SearchBook, Empty>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<types::Page<types::SearchBook, Empty>> {
let params = vec![
("page", page.to_string().into()),
("size", PAGE_SIZE.to_string().into()),
@@ -235,7 +217,7 @@ pub async fn get_uploaded_books(
pub async fn get_author_books_available_types(
id: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<Vec<String>> {
let params = get_allowed_langs_params(allowed_langs);
_make_request(
@@ -248,7 +230,7 @@ pub async fn get_author_books_available_types(
pub async fn get_translator_books_available_types(
id: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<Vec<String>> {
let params = get_allowed_langs_params(allowed_langs);
_make_request(
@@ -261,7 +243,7 @@ pub async fn get_translator_books_available_types(
pub async fn get_sequence_books_available_types(
id: u32,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<Vec<String>> {
let params = get_allowed_langs_params(allowed_langs);
_make_request(

View File

@@ -79,7 +79,7 @@ pub async fn create_or_update_user_settings(
username: String,
source: String,
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<UserSettings, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<UserSettings> {
USER_LANGS_CACHE.invalidate(&user_id).await;
let body = json!({
@@ -103,7 +103,7 @@ pub async fn create_or_update_user_settings(
Ok(response.json::<UserSettings>().await?)
}
pub async fn get_langs() -> Result<Vec<Lang>, Box<dyn std::error::Error + Send + Sync>> {
pub async fn get_langs() -> anyhow::Result<Vec<Lang>> {
let response = CLIENT
.get(format!("{}/languages/", &config::CONFIG.user_settings_url))
.header("Authorization", &config::CONFIG.user_settings_api_key)
@@ -114,9 +114,7 @@ pub async fn get_langs() -> Result<Vec<Lang>, Box<dyn std::error::Error + Send +
Ok(response.json::<Vec<Lang>>().await?)
}
pub async fn update_user_activity(
user_id: UserId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pub async fn update_user_activity(user_id: UserId) -> anyhow::Result<()> {
CLIENT
.post(format!(
"{}/users/{user_id}/update_activity",
@@ -133,7 +131,7 @@ pub async fn update_user_activity(
pub async fn is_need_donate_notifications(
chat_id: ChatId,
is_private: bool,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
) -> anyhow::Result<bool> {
let response = CLIENT
.get(format!(
"{}/donate_notifications/{chat_id}/is_need_send?is_private={is_private}",
@@ -147,9 +145,7 @@ pub async fn is_need_donate_notifications(
Ok(response.json::<bool>().await?)
}
pub async fn mark_donate_notification_sent(
chat_id: ChatId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pub async fn mark_donate_notification_sent(chat_id: ChatId) -> anyhow::Result<()> {
CLIENT
.post(format!(
"{}/donate_notifications/{chat_id}",

View File

@@ -1,21 +1,17 @@
use anyhow;
use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*,
types::ReplyParameters,
};
use std::error::Error;
use self::{strings::format_registered_message, utils::get_token};
pub mod register;
pub mod strings;
pub mod utils;
pub async fn message_handler(
message: Message,
bot: CacheMe<Throttle<Bot>>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> anyhow::Result<()> {
let from_user = message.clone().from.unwrap();
let text = message.text().unwrap_or("");
@@ -35,11 +31,8 @@ pub async fn message_handler(
Ok(())
}
pub fn get_manager_handler() -> Handler<
'static,
Result<(), Box<dyn Error + Send + Sync>>,
teloxide::dispatching::DpHandlerDescription,
> {
pub fn get_manager_handler(
) -> Handler<'static, anyhow::Result<()>, teloxide::dispatching::DpHandlerDescription> {
Update::filter_message().branch(
Message::filter_text()
.chain(dptree::filter(|message: Message| {

View File

@@ -1,11 +1,9 @@
mod approved_bot;
pub mod bots_manager;
use std::error::Error;
use teloxide::prelude::*;
pub type BotHandlerInternal = Result<(), Box<dyn Error + Send + Sync>>;
pub type BotHandlerInternal = anyhow::Result<()>;
type BotHandler = Handler<'static, BotHandlerInternal, teloxide::dispatching::DpHandlerDescription>;
@@ -27,7 +25,7 @@ fn ignore_user_edited_message() -> crate::bots::BotHandler {
dptree::entry().branch(Update::filter_edited_message().endpoint(|| async { Ok(()) }))
}
fn ingore_chat_join_request() -> crate::bots::BotHandler {
fn ignore_chat_join_request() -> crate::bots::BotHandler {
dptree::entry().branch(Update::filter_chat_join_request().endpoint(|| async { Ok(()) }))
}