use serde_json::Value; pub(crate) fn parse_api_error_message(raw_text: &str, fallback_message: &str) -> String { if let Ok(parsed) = serde_json::from_str::(raw_text) { for key in ["message", "detail", "error"] { if let Some(message) = find_first_string_by_key(&parsed, key) && !message.trim().is_empty() { return message; } } } raw_text .trim() .chars() .take(240) .collect::() .trim() .to_string() .chars() .next() .map(|_| raw_text.trim().chars().take(240).collect()) .unwrap_or_else(|| fallback_message.to_string()) } pub(crate) fn find_first_array_by_keys<'a>( value: &'a Value, keys: &[&str], ) -> Option<&'a Vec> { match value { Value::Object(object) => { for (key, value) in object { if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) && let Some(array) = value.as_array() { return Some(array); } if let Some(found) = find_first_array_by_keys(value, keys) { return Some(found); } } None } Value::Array(items) => items .iter() .find_map(|item| find_first_array_by_keys(item, keys)), _ => None, } } pub(crate) fn find_first_string_by_keys(value: &Value, keys: &[&str]) -> Option { keys.iter() .find_map(|key| find_first_string_by_key(value, key)) } pub(crate) fn find_first_f64_by_keys(value: &Value, keys: &[&str]) -> Option { match value { Value::Object(object) => { for (key, value) in object { if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) && let Some(number) = value.as_f64() { return Some(number); } if let Some(found) = find_first_f64_by_keys(value, keys) { return Some(found); } } None } Value::Array(items) => items .iter() .find_map(|item| find_first_f64_by_keys(item, keys)), _ => None, } } pub(crate) fn find_first_string_by_key(value: &Value, target_key: &str) -> Option { match value { Value::Object(object) => { for (key, value) in object { if key.eq_ignore_ascii_case(target_key) && let Some(text) = value.as_str() { return Some(text.trim().to_string()); } if let Some(found) = find_first_string_by_key(value, target_key) { return Some(found); } } None } Value::Array(items) => items .iter() .find_map(|item| find_first_string_by_key(item, target_key)), _ => None, } } pub(crate) fn find_root_string_by_keys(value: &Value, keys: &[&str]) -> Option { let object = value.as_object()?; for key in keys { if let Some(text) = object .iter() .find(|(candidate, _)| candidate.eq_ignore_ascii_case(key)) .and_then(|(_, value)| value.as_str()) .map(str::trim) .filter(|value| !value.is_empty()) { return Some(text.to_string()); } } None } pub(crate) fn collect_strings_by_keys(value: &Value, keys: &[&str]) -> Vec { let mut results = Vec::new(); collect_strings(value, keys, &mut results); let mut deduped = Vec::new(); for result in results { if !deduped.contains(&result) { deduped.push(result); } } deduped } fn collect_strings(value: &Value, keys: &[&str], output: &mut Vec) { match value { Value::Object(object) => { for (key, value) in object { if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) { match value { Value::String(text) if !text.trim().is_empty() => { output.push(text.trim().to_string()); } Value::Array(items) => { for item in items { if let Some(text) = item.as_str().map(str::trim) && !text.is_empty() { output.push(text.to_string()); } } } _ => {} } } collect_strings(value, keys, output); } } Value::Array(items) => { for item in items { collect_strings(item, keys, output); } } _ => {} } }