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::{
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<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())? {
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())),
}
}
}
+25 -19
View File
@@ -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())),
}
}
+25 -20
View File
@@ -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())),
}
}
+30 -10
View File
@@ -7,23 +7,43 @@ pub trait ParamMap {
impl ParamMap for HashMap<String, String> {
fn parse_param<T: FromStr>(&self, param: &str) -> Result<T, crate::response::Error> {
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<String, String>)> {
pub fn parse(s: &str) -> Result<(String, HashMap<String, String>), 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::<Vec<&str>>();
(parts[0].trim().to_string(), parts[1].trim().to_string())
})
.collect::<HashMap<String, String>>();
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))
}
+23 -19
View File
@@ -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<mongodb::error::Error> for Error {
fn from(e: mongodb::error::Error) -> Self {
println!("{e}");
Error::DatabaseError
Error::DatabaseError(e.to_string())
}
}