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.
This commit is contained in:
Torsten Schulz (local)
2026-03-04 23:41:32 +01:00
parent 8285466ba2
commit 4cf5f2f713

View File

@@ -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<CreateRoomArgs, &'static str> {
Ok(parsed)
}
fn parse_create_room_args_from_command(command: &Command) -> Result<CreateRoomArgs, &'static str> {
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<bool> {
match value.trim().to_lowercase().as_str() {
"1" | "true" | "yes" | "y" | "on" => Some(true),
@@ -1416,6 +1507,32 @@ fn parse_bool(value: &str) -> Option<bool> {
}
}
fn value_as_bool(value: &Value) -> Option<bool> {
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<i32> {
match value {
Value::Number(n) => n.as_i64().and_then(|v| i32::try_from(v).ok()),
Value::String(s) => s.trim().parse::<i32>().ok(),
_ => None,
}
}
fn value_as_string(value: &Value) -> Option<String> {
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<Option<i32>> {
match value.trim().to_lowercase().as_str() {
"m" | "male" => Some(Some(1)),