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