improved error handling #1

This commit is contained in:
2025-04-13 06:12:26 +02:00
parent a4992a163d
commit 53d0baa4a7
5 changed files with 125 additions and 81 deletions
+22 -13
View File
@@ -4,8 +4,6 @@ mod set_hat;
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::{BufRead, BufReader},
net::TcpStream,
sync::{mpsc, Arc}, sync::{mpsc, Arc},
}; };
@@ -39,7 +37,7 @@ impl Session {
std::thread::spawn(move || match stream.read() { std::thread::spawn(move || match stream.read() {
Some(session_token) => token_send.send(Ok(session_token)), Some(session_token) => token_send.send(Ok(session_token)),
_ => token_send.send(Err(crate::response::Error::InvalidHandshake)), _ => token_send.send(Err(crate::response::Error::InvalidHandshake("Failed to read session token".to_string()))),
}); });
match token_recv.recv_timeout(std::time::Duration::from_secs(20)) { match token_recv.recv_timeout(std::time::Duration::from_secs(20)) {
@@ -48,17 +46,17 @@ impl Session {
let response = minreq::get("https://api.minecraftservices.com/minecraft/profile") let response = minreq::get("https://api.minecraftservices.com/minecraft/profile")
.with_header("Authorization", &format!("Bearer {session_token}")) .with_header("Authorization", &format!("Bearer {session_token}"))
.send() .send()
.map_err(|_| crate::response::Error::InvalidSession)?; .map_err(|_| crate::response::Error::InvalidSession("Failed to validate session".to_string()))?;
// If the session is invalid, return an error // If the session is invalid, return an error
if response.status_code != 200 { if response.status_code != 200 {
return Err(crate::response::Error::InvalidSession); return Err(crate::response::Error::InvalidSession(format!("Invalid session status code: {}", response.status_code)));
} }
// Parse the player data // Parse the player data
let local_player: LocalPlayer = response let local_player: LocalPlayer = response
.json() .json()
.map_err(|_| crate::response::Error::InvalidSession)?; .map_err(|_| crate::response::Error::InvalidSession("Failed to parse player data".to_string()))?;
println!("[MOJANG] {} successfully logged on", &local_player.name); println!("[MOJANG] {} successfully logged on", &local_player.name);
@@ -74,8 +72,8 @@ impl Session {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout), Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout("Session handshake timed out".to_string())),
_ => Err(crate::response::Error::InvalidHandshake), _ => Err(crate::response::Error::InvalidHandshake("Failed to receive handshake response".to_string())),
} }
} }
@@ -91,18 +89,29 @@ impl Session {
"players" => { "players" => {
let mut players: Vec<PlayerResponse> = Vec::new(); let mut players: Vec<PlayerResponse> = Vec::new();
for uuid in params.parse_param::<String>("uuids")?.split("$") { let uuids = params.parse_param::<String>("uuids")?;
if uuids.is_empty() {
return Err(crate::response::Error::InvalidParameter {
param: "uuids".to_string(),
reason: "UUIDs list cannot be empty".to_string(),
});
}
for uuid in uuids.split("$") {
if uuid.is_empty() {
return Err(crate::response::Error::InvalidParameter {
param: "uuids".to_string(),
reason: "UUID cannot be empty".to_string(),
});
}
match player::player(self, uuid.to_string())? { match player::player(self, uuid.to_string())? {
Response::Player(p) => { Response::Player(p) => players.push(p),
players.push(p);
}
_ => {} _ => {}
} }
} }
Ok(Response::Players(players)) Ok(Response::Players(players))
} }
_ => Err(crate::response::Error::InvalidRequest), _ => Err(crate::response::Error::InvalidMethod(method.to_string())),
} }
} }
} }
+25 -19
View File
@@ -7,26 +7,32 @@ use super::{player, Session};
pub fn set_cloak(session: &Session, cloak: String) -> Result { pub fn set_cloak(session: &Session, cloak: String) -> Result {
match player::player(session, session.local_player.id.clone())? { match player::player(session, session.local_player.id.clone())? {
Response::Player(crate::response::PlayerResponse::Player { cloaks, .. }) => { Response::Player(crate::response::PlayerResponse::Player { cloaks, .. }) => {
if cloaks.contains(&cloak) || cloak.is_empty() { if !cloaks.contains(&cloak) && !cloak.is_empty() {
session return Err(Error::ValidationError(
.database format!("Player does not own cloak: {}", cloak)
.players ));
.update_one(
doc! {
"uuid": session.local_player.id.clone(),
},
doc! {
"$set": {
"cloak": cloak,
},
},
)
.run()?;
Ok(Response::Success)
} else {
Err(Error::SomethingWentWrong)
} }
session
.database
.players
.update_one(
doc! {
"uuid": session.local_player.id.clone(),
},
doc! {
"$set": {
"cloak": cloak.clone(),
},
},
)
.run()
.map_err(|e| Error::DatabaseError(
format!("Failed to update cloak: {}", e)
))?;
Ok(Response::Success)
} }
_ => Err(Error::SomethingWentWrong), _ => Err(Error::ValidationError("Invalid player data received".to_string())),
} }
} }
+25 -20
View File
@@ -7,27 +7,32 @@ use super::{player, Session};
pub fn set_hat(session: &Session, hat: String) -> Result { pub fn set_hat(session: &Session, hat: String) -> Result {
match player::player(session, session.local_player.id.clone())? { match player::player(session, session.local_player.id.clone())? {
Response::Player(crate::response::PlayerResponse::Player { hats, .. }) => { Response::Player(crate::response::PlayerResponse::Player { hats, .. }) => {
if hats.contains(&hat) || hat.is_empty() { if !hats.contains(&hat) && !hat.is_empty() {
session return Err(Error::ValidationError(
.database format!("Player does not own hat: {}", hat)
.players ));
.update_one(
doc! {
"uuid": session.local_player.id.clone(),
},
doc! {
"$set": {
"hat": hat,
},
},
)
.run()?;
Ok(Response::Success)
} else {
Err(Error::SomethingWentWrong)
} }
session
.database
.players
.update_one(
doc! {
"uuid": session.local_player.id.clone(),
},
doc! {
"$set": {
"hat": hat.clone(),
},
},
)
.run()
.map_err(|e| Error::DatabaseError(
format!("Failed to update hat: {}", e)
))?;
Ok(Response::Success)
} }
_ => Err(Error::SomethingWentWrong), _ => Err(Error::ValidationError("Invalid player data received".to_string())),
} }
} }
+30 -10
View File
@@ -7,23 +7,43 @@ pub trait ParamMap {
impl ParamMap for HashMap<String, String> { impl ParamMap for HashMap<String, String> {
fn parse_param<T: FromStr>(&self, param: &str) -> Result<T, crate::response::Error> { fn parse_param<T: FromStr>(&self, param: &str) -> Result<T, crate::response::Error> {
match self.get(param) { match self.get(param) {
Some(value) => T::from_str(value).map_err(|_| crate::response::Error::InvalidParameter), Some(value) => T::from_str(value).map_err(|_| crate::response::Error::InvalidParameter {
None => Err(crate::response::Error::InvalidParameter), param: param.to_string(),
reason: "Failed to parse parameter value".to_string(),
}),
None => Err(crate::response::Error::ParameterNotFound(param.to_string())),
} }
} }
} }
pub fn parse(s: &str) -> std::io::Result<(String, HashMap<String, String>)> { pub fn parse(s: &str) -> Result<(String, HashMap<String, String>), crate::response::Error> {
let mut parts = s.split("@"); let mut parts = s.split("@");
let method = parts.next().unwrap().trim().to_lowercase(); let method = parts.next()
.ok_or_else(|| crate::response::Error::InvalidRequest("Empty request".to_string()))?
.trim()
.to_lowercase();
let params = parts if method.is_empty() {
.map(|p| { return Err(crate::response::Error::InvalidRequest("Method name cannot be empty".to_string()));
let parts = p.split("=").collect::<Vec<&str>>(); }
(parts[0].trim().to_string(), parts[1].trim().to_string())
}) let mut params = HashMap::new();
.collect::<HashMap<String, String>>(); for part in parts {
let key_value: Vec<&str> = part.split("=").collect();
match key_value.as_slice() {
[key, value] => {
let key = key.trim();
if key.is_empty() {
return Err(crate::response::Error::InvalidRequest("Parameter key cannot be empty".to_string()));
}
params.insert(key.to_string(), value.trim().to_string());
},
_ => return Err(crate::response::Error::InvalidRequest(
format!("Invalid parameter format in part: {}", part)
)),
}
}
Ok((method, params)) Ok((method, params))
} }
+23 -19
View File
@@ -62,29 +62,33 @@ impl std::fmt::Display for Response {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Error { pub enum Error {
InvalidRequest, InvalidRequest(String),
InvalidMethod, InvalidMethod(String),
InvalidParameter, InvalidParameter { param: String, reason: String },
ParameterNotFound, ParameterNotFound(String),
InvalidSession, InvalidSession(String),
InvalidHandshake, InvalidHandshake(String),
DatabaseError, DatabaseError(String),
SomethingWentWrong, NetworkError(String),
Timeout, Timeout(String),
AuthenticationError(String),
ValidationError(String),
} }
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Error::InvalidRequest => write!(f, "Invalid request"), Error::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
Error::InvalidMethod => write!(f, "Invalid method"), Error::InvalidMethod(method) => write!(f, "Invalid method: {}", method),
Error::InvalidParameter => write!(f, "Invalid parameter"), Error::InvalidParameter { param, reason } => write!(f, "Invalid parameter '{}': {}", param, reason),
Error::ParameterNotFound => write!(f, "Parameter not found"), Error::ParameterNotFound(param) => write!(f, "Required parameter not found: {}", param),
Error::InvalidSession => write!(f, "Invalid session"), Error::InvalidSession(details) => write!(f, "Invalid session: {}", details),
Error::InvalidHandshake => write!(f, "Invalid handshake"), Error::InvalidHandshake(details) => write!(f, "Handshake failed: {}", details),
Error::DatabaseError => write!(f, "Database error"), Error::DatabaseError(details) => write!(f, "Database error: {}", details),
Error::SomethingWentWrong => write!(f, "Something went wrong"), Error::NetworkError(details) => write!(f, "Network error: {}", details),
Error::Timeout => write!(f, "Timeout"), Error::Timeout(operation) => write!(f, "Operation timed out: {}", operation),
Error::AuthenticationError(details) => write!(f, "Authentication failed: {}", details),
Error::ValidationError(details) => write!(f, "Validation failed: {}", details),
} }
} }
} }
@@ -92,7 +96,7 @@ impl std::fmt::Display for Error {
impl From<mongodb::error::Error> for Error { impl From<mongodb::error::Error> for Error {
fn from(e: mongodb::error::Error) -> Self { fn from(e: mongodb::error::Error) -> Self {
println!("{e}"); println!("{e}");
Error::DatabaseError Error::DatabaseError(e.to_string())
} }
} }