feat: use thiserror for better error handling

This commit is contained in:
user0-07161 2025-10-07 18:27:14 +02:00
parent 86910cb29a
commit 8eed67ab69
8 changed files with 99 additions and 23 deletions

21
Cargo.lock generated
View file

@ -617,6 +617,7 @@ dependencies = [
"clap", "clap",
"console-subscriber", "console-subscriber",
"once_cell", "once_cell",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -1089,6 +1090,26 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.9" version = "1.1.9"

View file

@ -4,7 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.100"
async-trait = "0.1.89" async-trait = "0.1.89"
clap = { version = "4.5.48", features = ["derive"] } clap = { version = "4.5.48", features = ["derive"] }
once_cell = "1.21.3" once_cell = "1.21.3"
@ -12,6 +11,8 @@ tokio = { version = "1.47.1", features = ["full"] }
console-subscriber = { version = "0.4.1", optional = true } console-subscriber = { version = "0.4.1", optional = true }
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.20" tracing-subscriber = "0.3.20"
thiserror = "2.0.17"
anyhow = "1.0.100"
[features] [features]
tokio-console = ["tokio/tracing", "console-subscriber"] tokio-console = ["tokio/tracing", "console-subscriber"]

View file

@ -1,9 +1,8 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use anyhow::Result;
use tokio::{io::BufWriter, net::TcpStream}; use tokio::{io::BufWriter, net::TcpStream};
use crate::{sender::IrcResponseCodes, user::User}; use crate::{error_structs::SenderError, sender::IrcResponseCodes, user::User};
#[derive(Clone, Debug, Hash, Eq, PartialEq)] #[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct Channel { pub struct Channel {
@ -28,7 +27,7 @@ impl Channel {
user: User, user: User,
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
hostname: &str, hostname: &str,
) -> Result<()> { ) -> Result<(), SenderError> {
let mut members = Vec::new(); let mut members = Vec::new();
for member in self.clone().joined_users { for member in self.clone().joined_users {
@ -58,7 +57,7 @@ impl Channel {
user: User, user: User,
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
hostname: &str, hostname: &str,
) -> Result<()> { ) -> Result<(), SenderError> {
IrcResponseCodes::NoTopic IrcResponseCodes::NoTopic
.into_irc_response( .into_irc_response(
user.nickname.clone().unwrap(), user.nickname.clone().unwrap(),

View file

@ -1,7 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::{Result, anyhow};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::{io::BufWriter, net::TcpStream, sync::broadcast::Sender}; use tokio::{io::BufWriter, net::TcpStream, sync::broadcast::Sender};
@ -11,6 +10,7 @@ use crate::{
commands::{ commands::{
cap::Cap, join::Join, nick::Nick, ping::Ping, privmsg::PrivMsg, user::User as UserHandler, cap::Cap, join::Join, nick::Nick, ping::Ping, privmsg::PrivMsg, user::User as UserHandler,
}, },
error_structs::CommandExecError,
messages::Message, messages::Message,
sender::IrcResponse, sender::IrcResponse,
user::User, user::User,
@ -94,7 +94,7 @@ impl IrcCommand {
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
hostname: &str, hostname: &str,
user_state: &mut User, user_state: &mut User,
) -> Result<()> { ) -> Result<(), CommandExecError> {
let mut command_map: HashMap<String, &dyn IrcHandler> = HashMap::new(); let mut command_map: HashMap<String, &dyn IrcHandler> = HashMap::new();
let broadcast_sender = SENDER.lock().await.clone().unwrap(); let broadcast_sender = SENDER.lock().await.clone().unwrap();
@ -111,7 +111,7 @@ impl IrcCommand {
let command_to_execute = command_map let command_to_execute = command_map
.get(&self.command.to_uppercase()) .get(&self.command.to_uppercase())
.map(|v| *v) .map(|v| *v)
.ok_or(anyhow!("unknown command!"))?; .ok_or(CommandExecError::NonexistantCommand)?;
let action = command_to_execute let action = command_to_execute
.handle( .handle(

43
src/error_structs.rs Normal file
View file

@ -0,0 +1,43 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum HandlerError {
#[error("std::io error")]
StdIoError(#[from] std::io::Error),
}
#[derive(Error, Debug)]
pub enum ListenerError {
#[error("connection error")]
ConnectionError,
#[error("user has not identified yet")]
UserIsUnidentified,
}
#[derive(Error, Debug)]
pub enum SenderError {
#[error("std::io error")]
StdIoError(#[from] std::io::Error),
}
#[derive(Error, Debug)]
pub enum CommandExecError {
#[error("command does not exist")]
NonexistantCommand,
}
// Conversion impls here
impl From<SenderError> for ListenerError {
fn from(value: SenderError) -> Self {
match value {
SenderError::StdIoError(_) => Self::ConnectionError,
}
}
}
impl From<std::io::Error> for ListenerError {
fn from(_: std::io::Error) -> Self {
Self::ConnectionError
}
}

View file

@ -1,13 +1,12 @@
use anyhow::Result;
use tokio::{io::BufWriter, net::TcpStream}; use tokio::{io::BufWriter, net::TcpStream};
use crate::{ServerInfo, sender::IrcResponseCodes, user::User}; use crate::{ServerInfo, error_structs::SenderError, sender::IrcResponseCodes, user::User};
pub async fn send_motd( pub async fn send_motd(
server_info: ServerInfo, server_info: ServerInfo,
user_info: User, user_info: User,
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
) -> Result<()> { ) -> Result<(), SenderError> {
let user_info = user_info.unwrap_all(); let user_info = user_info.unwrap_all();
let server_version = &format!("IRS-v{}", env!("CARGO_PKG_VERSION")) as &str; let server_version = &format!("IRS-v{}", env!("CARGO_PKG_VERSION")) as &str;

View file

@ -4,7 +4,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
use anyhow::{Result, bail}; use anyhow::Error as AnyhowError;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter}, io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter},
@ -19,6 +19,7 @@ use tracing::instrument;
use crate::{ use crate::{
channels::Channel, channels::Channel,
error_structs::{HandlerError, ListenerError},
login::send_motd, login::send_motd,
messages::Message, messages::Message,
sender::{IrcResponse, IrcResponseCodes}, sender::{IrcResponse, IrcResponseCodes},
@ -27,6 +28,7 @@ use crate::{
mod channels; mod channels;
mod commands; mod commands;
mod error_structs;
mod login; mod login;
mod messages; mod messages;
mod sender; mod sender;
@ -49,7 +51,7 @@ struct ServerInfo {
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<(), AnyhowError> {
#[cfg(feature = "tokio-console")] #[cfg(feature = "tokio-console")]
console_subscriber::init(); console_subscriber::init();
@ -83,7 +85,11 @@ async fn main() -> Result<()> {
} }
#[instrument] #[instrument]
async fn handle_connection(stream: TcpStream, info: ServerInfo, tx: Sender<Message>) -> Result<()> { async fn handle_connection(
stream: TcpStream,
info: ServerInfo,
tx: Sender<Message>,
) -> Result<(), HandlerError> {
let stream_tcp = stream.try_clone()?; let stream_tcp = stream.try_clone()?;
let mut message_receiver = tx.clone().subscribe(); let mut message_receiver = tx.clone().subscribe();
let mut tcp_reader = TokioBufReader::new(TokioTcpStream::from_std(stream.try_clone()?)?); let mut tcp_reader = TokioBufReader::new(TokioTcpStream::from_std(stream.try_clone()?)?);
@ -106,8 +112,14 @@ async fn handle_connection(stream: TcpStream, info: ServerInfo, tx: Sender<Messa
result = message_listener(&state, &mut message_receiver, &mut tcp_writer) => { result = message_listener(&state, &mut message_receiver, &mut tcp_writer) => {
match result { match result {
Ok(_) => {}, Ok(_) => {},
Err(_) => { Err(err) => {
// break; match err {
ListenerError::ConnectionError => {
break;
}
_ => {}
};
} }
} }
}, },
@ -124,21 +136,21 @@ async fn tcp_listener(
mut state: User, mut state: User,
info: &ServerInfo, info: &ServerInfo,
reader: &mut TokioBufReader<TokioTcpStream>, reader: &mut TokioBufReader<TokioTcpStream>,
) -> Result<User> { ) -> Result<User, ListenerError> {
let mut buffer = String::new(); let mut buffer = String::new();
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?); let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
buffer.clear(); buffer.clear();
match reader.read_line(&mut buffer).await { match reader.read_line(&mut buffer).await {
Ok(0) => bail!("invalid response"), Ok(0) => return Err(ListenerError::ConnectionError),
Ok(_) => {} Ok(_) => {}
Err(_) => { Err(_) => {
let mut conneted_users = CONNECTED_USERS.lock().await; let mut conneted_users = CONNECTED_USERS.lock().await;
let _ = conneted_users.remove(&state.clone().unwrap_all()); let _ = conneted_users.remove(&state.clone().unwrap_all());
bail!("client disconnected") return Err(ListenerError::ConnectionError);
} }
} }
@ -177,9 +189,9 @@ async fn message_listener(
user_wrapped: &User, user_wrapped: &User,
receiver: &mut Receiver<Message>, receiver: &mut Receiver<Message>,
writer: &mut TokioBufWriter<TokioTcpStream>, writer: &mut TokioBufWriter<TokioTcpStream>,
) -> Result<()> { ) -> Result<(), ListenerError> {
if !user_wrapped.is_populated() { if !user_wrapped.is_populated() {
bail!("user has not registered yet, returning..."); return Err(ListenerError::UserIsUnidentified);
} }
let user = user_wrapped.clone().unwrap_all(); let user = user_wrapped.clone().unwrap_all();

View file

@ -1,9 +1,10 @@
use anyhow::Result;
use tokio::{ use tokio::{
io::{AsyncWriteExt, BufWriter}, io::{AsyncWriteExt, BufWriter},
net::TcpStream, net::TcpStream,
}; };
use crate::error_structs::SenderError;
#[derive(Clone)] #[derive(Clone)]
pub struct IrcResponse { pub struct IrcResponse {
pub sender: Option<String>, pub sender: Option<String>,
@ -32,7 +33,7 @@ impl IrcResponse {
hostname: &str, hostname: &str,
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
prepend_column: bool, prepend_column: bool,
) -> Result<()> { ) -> Result<(), SenderError> {
let sender = format!(":{}", self.sender.clone().unwrap_or(hostname.to_string())); let sender = format!(":{}", self.sender.clone().unwrap_or(hostname.to_string()));
let mut full_response = Vec::new(); let mut full_response = Vec::new();