pub(crate) fn is_api_response(body: &str, method_name: &str) -> bool {
let mut marker = String::with_capacity(5 + method_name.len());
marker.push_str("<== ");
marker.push_str(method_name);
marker.push('(');
body.contains(&marker)
}
pub(crate) fn is_api_request(body: &str, method_name: &str) -> bool {
let mut marker = String::with_capacity(5 + method_name.len());
marker.push_str("==> ");
marker.push_str(method_name);
marker.push(' ');
body.contains(&marker)
}
pub(crate) fn extract_json_from_body(body: &str) -> Option<&str> {
let search_start = if body.starts_with('[') {
body.find(']').map_or(0, |pos| pos + 1)
} else {
0
};
let search_region = &body[search_start..];
let json_start = search_region.find(['{', '['])?;
let json_start = search_start + json_start;
let candidate = &body[json_start..];
let first_byte = candidate.as_bytes().first().copied()?;
let (open_char, close_char) = if first_byte == b'{' {
('{', '}')
} else {
('[', ']')
};
let mut depth: i32 = 0;
let mut in_string = false;
let mut escape_next = false;
let mut end_pos = None;
for (i, ch) in candidate.char_indices() {
if escape_next {
escape_next = false;
continue;
}
match ch {
'\\' if in_string => {
escape_next = true;
}
'"' => {
in_string = !in_string;
}
c if !in_string && c == open_char => {
depth += 1;
}
c if !in_string && c == close_char => {
depth -= 1;
if depth == 0 {
end_pos = Some(i + 1);
break;
}
}
_ => {}
}
}
end_pos.map(|end| &candidate[..end])
}
pub(crate) fn parse_json_from_body(body: &str, context: &str) -> Option<serde_json::Value> {
let json_str = extract_json_from_body(body)?;
match serde_json::from_str(json_str) {
Ok(v) => Some(v),
Err(e) => {
::log::warn!("Malformed JSON payload: context={context}, error={e}");
None
}
}
}
pub(crate) fn extract_deck_id(obj: &serde_json::Value) -> Option<String> {
obj.get("DeckId")
.and_then(serde_json::Value::as_str)
.map(str::to_owned)
}
pub(crate) fn parse_nested_json(
v: &serde_json::Value,
field: &str,
context: Option<&str>,
) -> Option<serde_json::Value> {
let nested = v.get(field)?.as_str()?;
match serde_json::from_str(nested) {
Ok(parsed) => Some(parsed),
Err(e) => {
if let Some(ctx) = context {
::log::warn!("Malformed nested JSON: context={ctx}, field={field}, error={e}");
}
None
}
}
}
pub(crate) fn extract_event_name(parsed: &serde_json::Value) -> String {
if let Some(name) = parsed
.get("EventName")
.or_else(|| parsed.get("InternalEventName"))
.and_then(serde_json::Value::as_str)
{
return name.to_owned();
}
for field in ["Course", "PickInfo"] {
if let Some(name) = parsed.get(field).and_then(|obj| {
obj.get("InternalEventName")
.or_else(|| obj.get("EventName"))
.and_then(serde_json::Value::as_str)
}) {
return name.to_owned();
}
}
if let Some(request_json) = parse_nested_json(parsed, "request", None) {
let name = extract_event_name(&request_json);
if !name.is_empty() {
return name;
}
}
String::new()
}
#[cfg(test)]
mod tests {
use super::*;
mod deck_id {
use super::*;
#[test]
fn test_extract_deck_id_present_returns_string() {
let obj = serde_json::json!({"DeckId": "abc-123"});
assert_eq!(extract_deck_id(&obj), Some("abc-123".to_owned()));
}
#[test]
fn test_extract_deck_id_missing_returns_none() {
let obj = serde_json::json!({"Other": "value"});
assert!(extract_deck_id(&obj).is_none());
}
#[test]
fn test_extract_deck_id_non_string_returns_none() {
let obj = serde_json::json!({"DeckId": 42});
assert!(extract_deck_id(&obj).is_none());
}
#[test]
fn test_extract_deck_id_null_returns_none() {
let obj = serde_json::json!({"DeckId": null});
assert!(extract_deck_id(&obj).is_none());
}
}
mod api_response {
use super::*;
#[test]
fn test_is_api_response_matches_method() {
let body = "[UnityCrossThreadLogger]2/22/2026 11:59:51 AM\n\
<== StartHook(e3f1a2b4-5678-9abc-def0-123456789abc)\n\
{\"InventoryInfo\": {}}";
assert!(is_api_response(body, "StartHook"));
}
#[test]
fn test_is_api_response_no_match_wrong_method() {
let body = "<== RankGetCombinedRankInfo(uuid)\n{}";
assert!(!is_api_response(body, "StartHook"));
}
#[test]
fn test_is_api_response_no_match_request_arrow() {
let body = "==> StartHook {\"data\": 1}";
assert!(!is_api_response(body, "StartHook"));
}
#[test]
fn test_is_api_response_no_match_empty() {
assert!(!is_api_response("", "StartHook"));
}
#[test]
fn test_is_api_response_no_match_prefix_method() {
let body = "<== StartHookV2(uuid)\n{}";
assert!(!is_api_response(body, "StartHook"));
}
}
mod api_request {
use super::*;
#[test]
fn test_is_api_request_matches_method() {
let body = "[UnityCrossThreadLogger]==> EventJoin {\"request\": \"{}\"}";
assert!(is_api_request(body, "EventJoin"));
}
#[test]
fn test_is_api_request_no_match_wrong_method() {
let body = "==> EventClaimPrize {}";
assert!(!is_api_request(body, "EventJoin"));
}
#[test]
fn test_is_api_request_no_match_response_arrow() {
let body = "<== EventJoin(uuid)\n{}";
assert!(!is_api_request(body, "EventJoin"));
}
#[test]
fn test_is_api_request_no_match_empty() {
assert!(!is_api_request("", "EventJoin"));
}
#[test]
fn test_is_api_request_no_match_prefix_method() {
let body = "==> EventJoinV2 {\"data\": 1}";
assert!(!is_api_request(body, "EventJoin"));
}
}
mod json_extraction {
use super::*;
#[test]
fn test_extract_json_object() {
let body = "header line\n{\"key\": \"value\"}";
assert_eq!(extract_json_from_body(body), Some("{\"key\": \"value\"}"));
}
#[test]
fn test_extract_json_array() {
let body = "header line\n[1, 2, 3]";
assert_eq!(extract_json_from_body(body), Some("[1, 2, 3]"));
}
#[test]
fn test_extract_json_with_bracket_header() {
let body = "[UnityCrossThreadLogger]some text\n{\"data\": 1}";
assert_eq!(extract_json_from_body(body), Some("{\"data\": 1}"));
}
#[test]
fn test_extract_json_nested_objects() {
let body = "header\n{\"outer\": {\"inner\": 1}}";
assert_eq!(
extract_json_from_body(body),
Some("{\"outer\": {\"inner\": 1}}")
);
}
#[test]
fn test_extract_json_with_string_braces() {
let body = "header\n{\"msg\": \"hello {world}\"}";
assert_eq!(
extract_json_from_body(body),
Some("{\"msg\": \"hello {world}\"}")
);
}
#[test]
fn test_extract_json_no_json() {
assert!(extract_json_from_body("no json here").is_none());
}
#[test]
fn test_extract_json_multiline() {
let body = "[UnityCrossThreadLogger]2/22/2026 11:59:51 AM\n\
<== StartHook(uuid)\n\
{\n\
\"InventoryInfo\": {\"Gems\": 1234}\n\
}";
let json = extract_json_from_body(body);
assert!(json.is_some());
let parsed: serde_json::Value =
serde_json::from_str(json.unwrap_or("{}")).unwrap_or_default();
assert_eq!(parsed["InventoryInfo"]["Gems"], 1234);
}
#[test]
fn test_extract_json_unclosed_brace() {
let body = "header {\"key\": \"value\"";
assert!(extract_json_from_body(body).is_none());
}
#[test]
fn test_extract_json_brace_in_string() {
let body = r#"text {"key": "value with { braces }"}"#;
assert_eq!(
extract_json_from_body(body),
Some(r#"{"key": "value with { braces }"}"#)
);
}
#[test]
fn test_extract_json_escaped_quote_in_string() {
let body = r#"prefix {"key": "val\"ue"}"#;
assert_eq!(extract_json_from_body(body), Some(r#"{"key": "val\"ue"}"#));
}
}
mod parse_json {
use super::*;
#[test]
fn test_parse_json_from_body_valid_object() {
let body = "header\n{\"key\": 42}";
let result = parse_json_from_body(body, "test");
assert_eq!(result, Some(serde_json::json!({"key": 42})));
}
#[test]
fn test_parse_json_from_body_no_json_returns_none() {
assert!(parse_json_from_body("no json", "test").is_none());
}
#[test]
fn test_parse_json_from_body_malformed_json_returns_none() {
let body = "header\n{invalid}";
assert!(parse_json_from_body(body, "test").is_none());
}
#[test]
fn test_parse_json_from_body_valid_array() {
let body = "header\n[1, 2, 3]";
let result = parse_json_from_body(body, "test");
assert_eq!(result, Some(serde_json::json!([1, 2, 3])));
}
}
mod nested_json {
use super::*;
#[test]
fn test_parse_nested_json_valid_string_returns_json() {
let v = serde_json::json!({"Payload": "{\"key\":\"value\"}"});
let result = parse_nested_json(&v, "Payload", Some("test"));
assert_eq!(result, Some(serde_json::json!({"key": "value"})));
}
#[test]
fn test_parse_nested_json_missing_field_returns_none() {
let v = serde_json::json!({"Other": "data"});
assert!(parse_nested_json(&v, "Payload", Some("test")).is_none());
}
#[test]
fn test_parse_nested_json_non_string_returns_none() {
let v = serde_json::json!({"Payload": {"key": "value"}});
assert!(parse_nested_json(&v, "Payload", Some("test")).is_none());
}
#[test]
fn test_parse_nested_json_invalid_json_returns_none() {
let v = serde_json::json!({"Payload": "not json"});
assert!(parse_nested_json(&v, "Payload", Some("test")).is_none());
}
}
mod event_name {
use super::*;
#[test]
fn test_extract_event_name_top_level_event_name_returns_name() {
let parsed = serde_json::json!({"EventName": "DirectEvent"});
assert_eq!(extract_event_name(&parsed), "DirectEvent");
}
#[test]
fn test_extract_event_name_top_level_internal_name_returns_name() {
let parsed = serde_json::json!({"InternalEventName": "InternalTest"});
assert_eq!(extract_event_name(&parsed), "InternalTest");
}
#[test]
fn test_extract_event_name_course_nested_returns_name() {
let parsed = serde_json::json!({
"Course": {"InternalEventName": "CourseInternal"}
});
assert_eq!(extract_event_name(&parsed), "CourseInternal");
}
#[test]
fn test_extract_event_name_string_escaped_request_returns_name() {
let parsed = serde_json::json!({
"id": "test",
"request": "{\"EventName\":\"NestedRequest\"}"
});
assert_eq!(extract_event_name(&parsed), "NestedRequest");
}
#[test]
fn test_extract_event_name_top_level_wins_over_course_and_request_returns_top_level() {
let parsed = serde_json::json!({
"EventName": "TopLevel",
"Course": {"EventName": "CourseLevel"},
"request": "{\"EventName\":\"RequestLevel\"}"
});
assert_eq!(extract_event_name(&parsed), "TopLevel");
}
#[test]
fn test_extract_event_name_pick_info_nested_returns_name() {
let parsed = serde_json::json!({
"PickInfo": {"EventName": "PickInfoTest"}
});
assert_eq!(extract_event_name(&parsed), "PickInfoTest");
}
#[test]
fn test_extract_event_name_nested_pick_info_in_request_returns_name() {
let parsed = serde_json::json!({
"request": "{\"PickInfo\":{\"EventName\":\"NestedPickInfo\"}}"
});
assert_eq!(extract_event_name(&parsed), "NestedPickInfo");
}
#[test]
fn test_extract_event_name_no_matching_field_returns_empty() {
let parsed = serde_json::json!({"id": "test"});
assert_eq!(extract_event_name(&parsed), "");
}
#[test]
fn test_extract_event_name_malformed_request_json_returns_empty() {
let parsed = serde_json::json!({"request": "not json"});
assert_eq!(extract_event_name(&parsed), "");
}
}
}