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",
"console-subscriber",
"once_cell",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
@ -1089,6 +1090,26 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "thread_local"
version = "1.1.9"

View file

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

View file

@ -1,9 +1,8 @@
use std::collections::BTreeSet;
use anyhow::Result;
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)]
pub struct Channel {
@ -28,7 +27,7 @@ impl Channel {
user: User,
writer: &mut BufWriter<TcpStream>,
hostname: &str,
) -> Result<()> {
) -> Result<(), SenderError> {
let mut members = Vec::new();
for member in self.clone().joined_users {
@ -58,7 +57,7 @@ impl Channel {
user: User,
writer: &mut BufWriter<TcpStream>,
hostname: &str,
) -> Result<()> {
) -> Result<(), SenderError> {
IrcResponseCodes::NoTopic
.into_irc_response(
user.nickname.clone().unwrap(),

View file

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

View file

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

View file

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