1use crate::{
4 platform::prelude::*, util::PopulateString, Run, Segment as LiveSplitSegment, Time, TimeSpan,
5};
6use alloc::borrow::Cow;
7use core::result::Result as StdResult;
8use serde::{Deserialize, Serialize};
9use serde_json::Error as JsonError;
10
11#[derive(Debug, snafu::Snafu)]
14#[snafu(context(suffix(false)))]
15pub enum Error {
16 Json {
18 #[cfg_attr(not(feature = "std"), snafu(source(false)))]
20 source: JsonError,
21 },
22}
23
24pub type Result<T> = StdResult<T, Error>;
26
27#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
29#[serde(rename = "duration")]
30struct Duration {
31 #[serde(rename = "gametimeMS")]
33 gametime_ms: Option<f64>,
34 #[serde(rename = "realtimeMS")]
36 realtime_ms: Option<f64>,
37}
38#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
41#[serde(rename = "runTime")]
42struct RunTime {
43 #[serde(rename = "gametimeMS")]
45 gametime_ms: Option<f64>,
46 #[serde(rename = "realtimeMS")]
48 realtime_ms: Option<f64>,
49}
50#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
51struct Attempt {
52 #[serde(rename = "attemptNumber")]
56 attempt_number: i64,
57 duration: Option<Duration>,
58}
59#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
60struct Attempts {
61 histories: Option<Vec<Attempt>>,
63 total: Option<u32>,
65}
66#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
67struct CategoryLinks<'a> {
68 #[serde(rename = "speedruncomID")]
70 #[serde(borrow)]
71 speedruncom_id: Option<Cow<'a, str>>,
72 #[serde(rename = "splitsioID")]
74 #[serde(borrow)]
75 splitsio_id: Option<Cow<'a, str>>,
76}
77#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
78struct Category<'a> {
79 links: Option<CategoryLinks<'a>>,
81 #[serde(borrow)]
83 longname: Cow<'a, str>,
84 #[serde(borrow)]
87 shortname: Option<Cow<'a, str>>,
88}
89#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
90struct GameLinks<'a> {
91 #[serde(rename = "speedruncomID")]
93 #[serde(borrow)]
94 speedruncom_id: Option<Cow<'a, str>>,
95 #[serde(rename = "splitsioID")]
97 #[serde(borrow)]
98 splitsio_id: Option<Cow<'a, str>>,
99}
100#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
101struct Game<'a> {
102 links: Option<GameLinks<'a>>,
104 #[serde(borrow)]
106 longname: Cow<'a, str>,
107 #[serde(borrow)]
110 shortname: Option<Cow<'a, str>>,
111}
112#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
113struct RunLinks<'a> {
114 #[serde(rename = "speedruncomID")]
117 #[serde(borrow)]
118 speedruncom_id: Option<Cow<'a, str>>,
119 #[serde(rename = "splitsioID")]
122 #[serde(borrow)]
123 splitsio_id: Option<Cow<'a, str>>,
124}
125#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
126struct Pause<'a> {
127 #[serde(rename = "endedAt")]
129 #[serde(borrow)]
130 ended_at: Option<Cow<'a, str>>,
131 #[serde(rename = "startedAt")]
134 #[serde(borrow)]
135 started_at: Cow<'a, str>,
136}
137#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
138struct RunnerLinks<'a> {
139 #[serde(rename = "speedruncomID")]
141 #[serde(borrow)]
142 speedruncom_id: Option<Cow<'a, str>>,
143 #[serde(rename = "splitsioID")]
145 #[serde(borrow)]
146 splitsio_id: Option<Cow<'a, str>>,
147 #[serde(rename = "twitchID")]
149 #[serde(borrow)]
150 twitch_id: Option<Cow<'a, str>>,
151 #[serde(rename = "twitterID")]
153 #[serde(borrow)]
154 twitter_id: Option<Cow<'a, str>>,
155}
156#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
157struct Runner<'a> {
158 links: Option<RunnerLinks<'a>>,
160 #[serde(borrow)]
162 longname: Option<Cow<'a, str>>,
163 #[serde(borrow)]
166 shortname: Cow<'a, str>,
167}
168#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
169struct SegmentHistoryElement {
170 #[serde(rename = "attemptNumber")]
175 attempt_number: i64,
176 #[serde(rename = "endedAt")]
177 ended_at: Option<RunTime>,
178 #[serde(rename = "isReset")]
181 is_reset: Option<bool>,
182 #[serde(rename = "isSkipped")]
185 is_skipped: Option<bool>,
186}
187#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
188struct Segment<'a> {
189 #[serde(rename = "bestDuration")]
190 best_duration: Option<Duration>,
191 #[serde(rename = "endedAt")]
192 ended_at: Option<RunTime>,
193 histories: Option<Vec<SegmentHistoryElement>>,
195 #[serde(rename = "isReset")]
198 is_reset: Option<bool>,
199 #[serde(rename = "isSkipped")]
202 is_skipped: Option<bool>,
203 #[serde(borrow)]
205 name: Option<Cow<'a, str>>,
206}
207#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
208struct Timer<'a> {
209 #[serde(borrow)]
211 longname: Cow<'a, str>,
212 #[serde(borrow)]
215 shortname: Cow<'a, str>,
216 #[serde(borrow)]
219 version: Cow<'a, str>,
220 #[serde(borrow)]
222 website: Option<Cow<'a, str>>,
223}
224#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
225struct Splits<'a> {
226 #[serde(rename = "_schemaVersion")]
229 #[serde(borrow)]
230 _schemaversion: Cow<'a, str>,
231 attempts: Option<Attempts>,
234 category: Option<Category<'a>>,
236 #[serde(rename = "endedAt")]
238 #[serde(borrow)]
239 ended_at: Option<Cow<'a, str>>,
240 game: Option<Game<'a>>,
242 #[serde(rename = "imageURL")]
246 #[serde(borrow)]
247 image_url: Option<Cow<'a, str>>,
248 links: Option<RunLinks<'a>>,
250 pauses: Option<Vec<Pause<'a>>>,
252 runners: Option<Vec<Runner<'a>>>,
255 segments: Option<Vec<Segment<'a>>>,
257 #[serde(rename = "startedAt")]
259 #[serde(borrow)]
260 started_at: Option<Cow<'a, str>>,
261 timer: Timer<'a>,
263 #[serde(rename = "videoURL")]
265 #[serde(borrow)]
266 video_url: Option<Cow<'a, str>>,
267}
268
269impl From<Option<Duration>> for Time {
270 fn from(duration: Option<Duration>) -> Time {
271 duration.map(Into::into).unwrap_or_default()
272 }
273}
274
275impl From<Duration> for Time {
276 fn from(duration: Duration) -> Time {
277 let mut time = Time::new();
278 if let Some(ms) = duration.realtime_ms {
279 time.real_time = Some(TimeSpan::from_milliseconds(ms));
280 }
281 if let Some(ms) = duration.gametime_ms {
282 time.game_time = Some(TimeSpan::from_milliseconds(ms));
283 }
284 time
285 }
286}
287
288impl From<Option<RunTime>> for Time {
289 fn from(time: Option<RunTime>) -> Time {
290 time.map(Into::into).unwrap_or_default()
291 }
292}
293
294impl From<RunTime> for Time {
295 fn from(run_time: RunTime) -> Time {
296 let mut time = Time::new();
297 if let Some(ms) = run_time.realtime_ms {
298 time.real_time = Some(TimeSpan::from_milliseconds(ms));
299 }
300 if let Some(ms) = run_time.gametime_ms {
301 time.game_time = Some(TimeSpan::from_milliseconds(ms));
302 }
303 time
304 }
305}
306
307pub fn parse(source: &str) -> Result<(Run, Cow<'_, str>)> {
309 let splits: Splits<'_> =
310 serde_json::from_str(source).map_err(|source| Error::Json { source })?;
311
312 let mut run = Run::new();
313
314 if let Some(game) = splits.game {
315 run.set_game_name(game.longname);
316 }
317 if let Some(category) = splits.category {
318 run.set_category_name(category.longname);
319 }
320 if let Some(attempts) = splits.attempts {
321 if let Some(total) = attempts.total {
322 run.set_attempt_count(total);
323 }
324 for attempt in attempts.histories.into_iter().flatten() {
325 run.add_attempt_with_index(
326 attempt.duration.into(),
327 attempt.attempt_number as i32,
328 None,
329 None,
330 None,
331 );
332 }
333 }
334
335 if let Some(runner) = splits
336 .runners
337 .and_then(|runners| runners.into_iter().next())
338 {
339 let name = runner.longname.unwrap_or(runner.shortname);
340 if !name.trim_start().is_empty() {
341 run.metadata_mut()
342 .custom_variable_mut("Runner")
343 .permanent()
344 .set_value(name);
345 }
346 if let Some(links) = runner.links {
347 if let Some(twitter_id) = links.twitter_id {
348 run.metadata_mut()
349 .custom_variable_mut("Twitter")
350 .permanent()
351 .set_value(twitter_id);
352 }
353 if let Some(twitch_id) = links.twitch_id {
354 run.metadata_mut()
355 .custom_variable_mut("Twitch")
356 .permanent()
357 .set_value(twitch_id);
358 }
359 if let Some(speedruncom_id) = links.speedruncom_id {
360 run.metadata_mut()
361 .custom_variable_mut("speedrun.com")
362 .permanent()
363 .set_value(speedruncom_id);
364 }
365 if let Some(splitsio_id) = links.splitsio_id {
366 run.metadata_mut()
367 .custom_variable_mut("Splits I/O")
368 .permanent()
369 .set_value(splitsio_id);
370 }
371 }
372 }
373
374 if let Some(segments) = splits.segments {
375 run.segments_mut().extend(segments.into_iter().map(|split| {
376 let mut segment = LiveSplitSegment::new(split.name.unwrap_or_default());
377 segment.set_personal_best_split_time(split.ended_at.into());
378 segment.set_best_segment_time(split.best_duration.into());
379 if let Some(mut history) = split.histories {
380 let segment_history = segment.segment_history_mut();
381 history.sort_unstable_by_key(|x| x.attempt_number);
382 for element in history {
383 segment_history.insert(element.attempt_number as i32, element.ended_at.into());
384 }
385 }
386 segment
387 }));
388 }
389
390 if let Some(links) = splits.links {
391 if let Some(link) = links.speedruncom_id {
392 link.populate(&mut run.metadata_mut().run_id);
393 }
394 }
395
396 let timer = if splits.timer.longname.is_empty() {
397 "Generic Timer".into()
398 } else {
399 splits.timer.longname
400 };
401
402 Ok((run, timer))
403}