diff --git a/src/methods/mod.rs b/src/methods/mod.rs index 14d9004..7294a35 100644 --- a/src/methods/mod.rs +++ b/src/methods/mod.rs @@ -4,8 +4,6 @@ mod set_hat; use std::{ collections::HashMap, - io::{BufRead, BufReader}, - net::TcpStream, sync::{mpsc, Arc}, }; @@ -39,7 +37,7 @@ impl Session { std::thread::spawn(move || match stream.read() { 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)) { @@ -48,17 +46,17 @@ impl Session { let response = minreq::get("https://api.minecraftservices.com/minecraft/profile") .with_header("Authorization", &format!("Bearer {session_token}")) .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 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 let local_player: LocalPlayer = response .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); @@ -74,8 +72,8 @@ impl Session { Err(e) => Err(e), } } - Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout), - _ => Err(crate::response::Error::InvalidHandshake), + Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout("Session handshake timed out".to_string())), + _ => Err(crate::response::Error::InvalidHandshake("Failed to receive handshake response".to_string())), } } @@ -91,18 +89,29 @@ impl Session { "players" => { let mut players: Vec = Vec::new(); - for uuid in params.parse_param::("uuids")?.split("$") { + let uuids = params.parse_param::("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())? { - Response::Player(p) => { - players.push(p); - } + Response::Player(p) => players.push(p), _ => {} } } Ok(Response::Players(players)) } - _ => Err(crate::response::Error::InvalidRequest), + _ => Err(crate::response::Error::InvalidMethod(method.to_string())), } } } diff --git a/src/methods/set_cloak.rs b/src/methods/set_cloak.rs index f02be9c..175bcda 100644 --- a/src/methods/set_cloak.rs +++ b/src/methods/set_cloak.rs @@ -7,26 +7,32 @@ use super::{player, Session}; pub fn set_cloak(session: &Session, cloak: String) -> Result { match player::player(session, session.local_player.id.clone())? { Response::Player(crate::response::PlayerResponse::Player { cloaks, .. }) => { - if cloaks.contains(&cloak) || cloak.is_empty() { - session - .database - .players - .update_one( - doc! { - "uuid": session.local_player.id.clone(), - }, - doc! { - "$set": { - "cloak": cloak, - }, - }, - ) - .run()?; - Ok(Response::Success) - } else { - Err(Error::SomethingWentWrong) + if !cloaks.contains(&cloak) && !cloak.is_empty() { + return Err(Error::ValidationError( + format!("Player does not own cloak: {}", cloak) + )); } + + 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())), } } diff --git a/src/methods/set_hat.rs b/src/methods/set_hat.rs index b880adc..95a657b 100644 --- a/src/methods/set_hat.rs +++ b/src/methods/set_hat.rs @@ -7,27 +7,32 @@ use super::{player, Session}; pub fn set_hat(session: &Session, hat: String) -> Result { match player::player(session, session.local_player.id.clone())? { Response::Player(crate::response::PlayerResponse::Player { hats, .. }) => { - if hats.contains(&hat) || hat.is_empty() { - session - .database - .players - .update_one( - doc! { - "uuid": session.local_player.id.clone(), - }, - doc! { - "$set": { - "hat": hat, - }, - }, - ) - .run()?; - - Ok(Response::Success) - } else { - Err(Error::SomethingWentWrong) + if !hats.contains(&hat) && !hat.is_empty() { + return Err(Error::ValidationError( + format!("Player does not own hat: {}", hat) + )); } + + 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())), } } diff --git a/src/parser.rs b/src/parser.rs index 341ea10..db63652 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,23 +7,43 @@ pub trait ParamMap { impl ParamMap for HashMap { fn parse_param(&self, param: &str) -> Result { match self.get(param) { - Some(value) => T::from_str(value).map_err(|_| crate::response::Error::InvalidParameter), - None => Err(crate::response::Error::InvalidParameter), + Some(value) => T::from_str(value).map_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)> { +pub fn parse(s: &str) -> Result<(String, HashMap), crate::response::Error> { 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 - .map(|p| { - let parts = p.split("=").collect::>(); - (parts[0].trim().to_string(), parts[1].trim().to_string()) - }) - .collect::>(); + if method.is_empty() { + return Err(crate::response::Error::InvalidRequest("Method name cannot be empty".to_string())); + } + + let mut params = HashMap::new(); + 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)) } diff --git a/src/response.rs b/src/response.rs index 7578bc6..1891df8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -62,29 +62,33 @@ impl std::fmt::Display for Response { #[derive(Debug, Clone)] pub enum Error { - InvalidRequest, - InvalidMethod, - InvalidParameter, - ParameterNotFound, - InvalidSession, - InvalidHandshake, - DatabaseError, - SomethingWentWrong, - Timeout, + InvalidRequest(String), + InvalidMethod(String), + InvalidParameter { param: String, reason: String }, + ParameterNotFound(String), + InvalidSession(String), + InvalidHandshake(String), + DatabaseError(String), + NetworkError(String), + Timeout(String), + AuthenticationError(String), + ValidationError(String), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Error::InvalidRequest => write!(f, "Invalid request"), - Error::InvalidMethod => write!(f, "Invalid method"), - Error::InvalidParameter => write!(f, "Invalid parameter"), - Error::ParameterNotFound => write!(f, "Parameter not found"), - Error::InvalidSession => write!(f, "Invalid session"), - Error::InvalidHandshake => write!(f, "Invalid handshake"), - Error::DatabaseError => write!(f, "Database error"), - Error::SomethingWentWrong => write!(f, "Something went wrong"), - Error::Timeout => write!(f, "Timeout"), + Error::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg), + Error::InvalidMethod(method) => write!(f, "Invalid method: {}", method), + Error::InvalidParameter { param, reason } => write!(f, "Invalid parameter '{}': {}", param, reason), + Error::ParameterNotFound(param) => write!(f, "Required parameter not found: {}", param), + Error::InvalidSession(details) => write!(f, "Invalid session: {}", details), + Error::InvalidHandshake(details) => write!(f, "Handshake failed: {}", details), + Error::DatabaseError(details) => write!(f, "Database error: {}", details), + Error::NetworkError(details) => write!(f, "Network error: {}", details), + 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 for Error { fn from(e: mongodb::error::Error) -> Self { println!("{e}"); - Error::DatabaseError + Error::DatabaseError(e.to_string()) } }