moonpool_explorer/
replay.rs1use std::fmt;
8
9#[derive(Debug)]
11pub struct ParseTimelineError {
12 pub message: String,
14}
15
16impl fmt::Display for ParseTimelineError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 write!(f, "parse timeline error: {}", self.message)
19 }
20}
21
22impl std::error::Error for ParseTimelineError {}
23
24#[must_use]
37pub fn format_timeline(recipe: &[(u64, u64)]) -> String {
38 recipe
39 .iter()
40 .map(|(count, seed)| format!("{count}@{seed}"))
41 .collect::<Vec<_>>()
42 .join(" -> ")
43}
44
45pub fn parse_timeline(s: &str) -> Result<Vec<(u64, u64)>, ParseTimelineError> {
63 let trimmed = s.trim();
64 if trimmed.is_empty() {
65 return Ok(Vec::new());
66 }
67
68 trimmed
69 .split(" -> ")
70 .map(|segment| {
71 let segment = segment.trim();
72 let at_pos = segment.find('@').ok_or_else(|| ParseTimelineError {
73 message: format!("missing '@' in segment: {segment}"),
74 })?;
75
76 let count_str = &segment[..at_pos];
77 let seed_str = &segment[at_pos + 1..];
78
79 let count = count_str.parse::<u64>().map_err(|e| ParseTimelineError {
80 message: format!("invalid count '{count_str}': {e}"),
81 })?;
82
83 let seed = seed_str.parse::<u64>().map_err(|e| ParseTimelineError {
84 message: format!("invalid seed '{seed_str}': {e}"),
85 })?;
86
87 Ok((count, seed))
88 })
89 .collect()
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_format_empty() {
98 assert_eq!(format_timeline(&[]), "");
99 }
100
101 #[test]
102 fn test_format_single() {
103 assert_eq!(format_timeline(&[(42, 12345)]), "42@12345");
104 }
105
106 #[test]
107 fn test_format_multiple() {
108 let recipe = vec![(42, 12345), (17, 67890), (100, 999)];
109 assert_eq!(format_timeline(&recipe), "42@12345 -> 17@67890 -> 100@999");
110 }
111
112 #[test]
113 fn test_roundtrip() {
114 let original = vec![(42, 12345), (17, 67890)];
115 let formatted = format_timeline(&original);
116 let parsed = parse_timeline(&formatted).expect("parse failed");
117 assert_eq!(original, parsed);
118 }
119
120 #[test]
121 fn test_parse_empty() {
122 let result = parse_timeline("").expect("parse failed");
123 assert!(result.is_empty());
124 }
125
126 #[test]
127 fn test_parse_whitespace() {
128 let result = parse_timeline(" ").expect("parse failed");
129 assert!(result.is_empty());
130 }
131
132 #[test]
133 fn test_parse_error_missing_at() {
134 let result = parse_timeline("42-12345");
135 assert!(result.is_err());
136 }
137
138 #[test]
139 fn test_parse_error_non_numeric() {
140 let result = parse_timeline("abc@12345");
141 assert!(result.is_err());
142 }
143}