From 4cf5f2f713bfcae043d14182356440622041540d Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 4 Mar 2026 23:41:32 +0100 Subject: [PATCH] Refactor room creation argument parsing and enhance error handling Introduced a new function, `parse_create_room_args_from_command`, to streamline the parsing of room creation arguments from the command structure. Improved error handling in `handle_create_room_command` to provide detailed debug logging for parsing failures and duplicate room names when debugging is enabled. Enhanced the validation of room creation parameters, including age restrictions and room type, ensuring robust input handling. --- src/commands.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index a0330db..9825e5f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -892,14 +892,15 @@ async fn handle_create_room_command( if !state::authorize(client_id, command, Arc::clone(&state)).await { return; } - let raw = command - .message - .clone() - .or_else(|| string_from_value(command.value.as_ref())) - .unwrap_or_default(); - let parsed = match parse_create_room_args(&raw) { + let parsed = match parse_create_room_args_from_command(command) { Ok(v) => v, Err(msg) => { + if room_debug_enabled() { + eprintln!( + "[yourchat2][room-debug][create] client_id={client_id} parse_failed error='{msg}' command={:?}", + command + ); + } send_error(client_id, Arc::clone(&state), msg).await; return; } @@ -920,6 +921,12 @@ async fn handle_create_room_command( let mut guard = state.write().await; if resolve_room_name(&guard, &parsed.room_name).is_some() { drop(guard); + if room_debug_enabled() { + eprintln!( + "[yourchat2][room-debug][create] client_id={client_id} room='{}' duplicate", + parsed.room_name + ); + } send_error(client_id, Arc::clone(&state), "room_already_exists").await; return; } @@ -1408,6 +1415,90 @@ fn parse_create_room_args(raw: &str) -> Result { Ok(parsed) } +fn parse_create_room_args_from_command(command: &Command) -> Result { + let raw = command + .message + .clone() + .or_else(|| string_from_value(command.value.as_ref())) + .unwrap_or_default(); + if !raw.trim().is_empty() { + return parse_create_room_args(&raw); + } + + let room_name = command + .room + .clone() + .or(command.name.clone()) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + if room_name.is_empty() { + return Err("missing_room_name"); + } + let mut parsed = CreateRoomArgs { + room_name, + is_public: true, + gender_restriction_id: None, + min_age: None, + max_age: None, + password: command.password.clone().filter(|v| !v.trim().is_empty()), + friends_of_owner_only: false, + required_user_right_id: None, + room_type_id: None, + }; + + if let Some(Value::Object(map)) = command.value.as_ref() { + if let Some(value) = map.get("public").and_then(value_as_bool) { + parsed.is_public = value; + } + if let Some(value) = map.get("is_public").and_then(value_as_bool) { + parsed.is_public = value; + } + if let Some(value) = map.get("friends_only").and_then(value_as_bool) { + parsed.friends_of_owner_only = value; + } + if let Some(value) = map.get("friends_of_owner_only").and_then(value_as_bool) { + parsed.friends_of_owner_only = value; + } + if let Some(value) = map + .get("gender") + .and_then(value_as_string) + .and_then(|v| parse_gender_mf(&v)) + { + parsed.gender_restriction_id = value; + } + if let Some(value) = map.get("min_age").and_then(value_as_i32) { + parsed.min_age = Some(value); + } + if let Some(value) = map.get("max_age").and_then(value_as_i32) { + parsed.max_age = Some(value); + } + if let Some(value) = map.get("password").and_then(value_as_string) { + if !value.trim().is_empty() { + parsed.password = Some(value); + } + } + if let Some(value) = map.get("required_user_right_id").and_then(value_as_i32) { + parsed.required_user_right_id = Some(value); + } + if let Some(value) = map.get("right_id").and_then(value_as_i32) { + parsed.required_user_right_id = Some(value); + } + if let Some(value) = map.get("room_type_id").and_then(value_as_i32) { + parsed.room_type_id = Some(value); + } + if let Some(value) = map.get("type_id").and_then(value_as_i32) { + parsed.room_type_id = Some(value); + } + } + + if let (Some(min_age), Some(max_age)) = (parsed.min_age, parsed.max_age) { + if min_age > max_age { + return Err("invalid_age_range"); + } + } + Ok(parsed) +} + fn parse_bool(value: &str) -> Option { match value.trim().to_lowercase().as_str() { "1" | "true" | "yes" | "y" | "on" => Some(true), @@ -1416,6 +1507,32 @@ fn parse_bool(value: &str) -> Option { } } +fn value_as_bool(value: &Value) -> Option { + match value { + Value::Bool(b) => Some(*b), + Value::String(s) => parse_bool(s), + Value::Number(n) => n.as_i64().map(|v| v != 0), + _ => None, + } +} + +fn value_as_i32(value: &Value) -> Option { + match value { + Value::Number(n) => n.as_i64().and_then(|v| i32::try_from(v).ok()), + Value::String(s) => s.trim().parse::().ok(), + _ => None, + } +} + +fn value_as_string(value: &Value) -> Option { + match value { + Value::String(s) => Some(s.clone()), + Value::Number(n) => Some(n.to_string()), + Value::Bool(b) => Some(b.to_string()), + _ => None, + } +} + fn parse_gender_mf(value: &str) -> Option> { match value.trim().to_lowercase().as_str() { "m" | "male" => Some(Some(1)),