use crate::events::{GameEvent, TruncationEvent};
use crate::log::entry::{EntryHeader, LogEntry};
const OBJECT_COUNT_PREFIX: &str = ":: GameObject Count = ";
const ANNOTATION_COUNT_PREFIX: &str = ":: Annotation Count = ";
pub fn try_parse(
entry: &LogEntry,
timestamp: Option<chrono::DateTime<chrono::Utc>>,
) -> Option<GameEvent> {
if entry.header != EntryHeader::TruncationMarker {
return None;
}
let object_count = extract_count(&entry.body, OBJECT_COUNT_PREFIX)?;
let annotation_count = extract_count(&entry.body, ANNOTATION_COUNT_PREFIX)?;
Some(GameEvent::Truncation(TruncationEvent::new_truncation(
timestamp,
object_count,
annotation_count,
)))
}
fn extract_count(body: &str, prefix: &str) -> Option<u32> {
body.lines()
.find_map(|line| line.trim_start().strip_prefix(prefix))
.and_then(|tail| tail.trim().parse::<u32>().ok())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parsers::test_helpers::test_timestamp;
fn truncation_entry(body: &str) -> LogEntry {
LogEntry {
header: EntryHeader::TruncationMarker,
body: body.to_owned(),
}
}
fn marker_block(object_count: u32, annotation_count: u32) -> String {
format!(
"[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]\n\
::: GameStateMessage\n\
:: GameObject Count = {object_count}\n\
:: Annotation Count = {annotation_count}\n\
::: ActionsAvailableReq"
)
}
#[test]
fn test_try_parse_emits_truncation_event() {
let entry = truncation_entry(&marker_block(63, 4));
let event = try_parse(&entry, Some(test_timestamp()));
assert!(matches!(event, Some(GameEvent::Truncation(_))));
}
#[test]
fn test_try_parse_extracts_object_count() {
let entry = truncation_entry(&marker_block(63, 4));
let Some(GameEvent::Truncation(event)) = try_parse(&entry, Some(test_timestamp())) else {
unreachable!("expected Truncation event");
};
assert_eq!(event.object_count(), Some(63));
}
#[test]
fn test_try_parse_extracts_annotation_count() {
let entry = truncation_entry(&marker_block(63, 4));
let Some(GameEvent::Truncation(event)) = try_parse(&entry, Some(test_timestamp())) else {
unreachable!("expected Truncation event");
};
assert_eq!(event.annotation_count(), Some(4));
}
#[test]
fn test_try_parse_passes_through_timestamp() {
let entry = truncation_entry(&marker_block(63, 4));
let ts = Some(test_timestamp());
let event = try_parse(&entry, ts);
let Some(GameEvent::Truncation(event)) = event else {
unreachable!("expected Truncation event");
};
assert_eq!(event.metadata().timestamp(), ts);
}
#[test]
fn test_try_parse_emits_with_no_timestamp() {
let entry = truncation_entry(&marker_block(7, 11));
let event = try_parse(&entry, None);
let Some(GameEvent::Truncation(event)) = event else {
unreachable!("expected Truncation event even without timestamp");
};
assert!(event.metadata().timestamp().is_none());
assert_eq!(event.object_count(), Some(7));
assert_eq!(event.annotation_count(), Some(11));
}
#[test]
fn test_try_parse_wrong_header_returns_none() {
let entry = LogEntry {
header: EntryHeader::UnityCrossThreadLogger,
body: marker_block(63, 4),
};
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_missing_object_count_returns_none() {
let body = "[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]\n\
::: GameStateMessage\n\
:: Annotation Count = 4\n\
::: ActionsAvailableReq";
let entry = truncation_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_missing_annotation_count_returns_none() {
let body = "[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]\n\
::: GameStateMessage\n\
:: GameObject Count = 63\n\
::: ActionsAvailableReq";
let entry = truncation_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_unparseable_count_returns_none() {
let body = "[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]\n\
:: GameObject Count = NaN\n\
:: Annotation Count = 4";
let entry = truncation_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
#[test]
fn test_try_parse_leading_whitespace_tolerated() {
let body = "[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]\n\
\t:: GameObject Count = 51\n\
\t:: Annotation Count = 0";
let entry = truncation_entry(body);
let Some(GameEvent::Truncation(event)) = try_parse(&entry, Some(test_timestamp())) else {
unreachable!("expected Truncation event");
};
assert_eq!(event.object_count(), Some(51));
assert_eq!(event.annotation_count(), Some(0));
}
#[test]
fn test_try_parse_marker_only_body_returns_none() {
let body = "[Message summarized because one or more GameStateMessages \
exceeded the 50 GameObject or 50 Annotation limit.]";
let entry = truncation_entry(body);
assert!(try_parse(&entry, Some(test_timestamp())).is_none());
}
}