use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use ts_rs::TS;
pub mod summary_keys {
pub const DISTANCE: &str = "distance";
pub const AVG_HR: &str = "avg_hr";
pub const MAX_HR: &str = "max_hr";
pub const AVG_SPEED: &str = "avg_speed";
pub const MAX_SPEED: &str = "max_speed";
pub const AVG_CADENCE: &str = "avg_cadence";
pub const AVG_POWER: &str = "avg_power";
pub const MAX_POWER: &str = "max_power";
pub const TOTAL_CALORIES: &str = "total_calories";
pub const ELEVATION_GAIN: &str = "elevation_gain";
pub const ELEVATION_LOSS: &str = "elevation_loss";
pub const DURATION: &str = "duration";
pub const STROKES: &str = "strokes";
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../../bindings/napi/generated/")]
pub enum LapTrigger {
Manual,
Distance,
Time,
HeartRateZone,
Auto,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../../bindings/napi/generated/")]
pub struct Lap {
#[serde(skip_serializing_if = "Option::is_none")]
pub start_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_index: Option<usize>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub summary: BTreeMap<String, f64>,
pub trigger: LapTrigger,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lap_round_trip() {
let mut summary = BTreeMap::new();
summary.insert(summary_keys::DISTANCE.into(), 1000.0);
summary.insert(summary_keys::AVG_HR.into(), 135.0);
summary.insert(summary_keys::DURATION.into(), 300.0);
let lap = Lap {
start_index: Some(0),
end_index: Some(119),
summary,
trigger: LapTrigger::Distance,
name: Some("km 1".into()),
};
let json = serde_json::to_string_pretty(&lap).unwrap();
let back: Lap = serde_json::from_str(&json).unwrap();
assert_eq!(back, lap);
assert!(json.contains(r#""distance""#));
assert!(json.contains("1000.0"));
}
#[test]
fn lap_without_indices() {
let mut summary = BTreeMap::new();
summary.insert(summary_keys::DISTANCE.into(), 5000.0);
summary.insert(summary_keys::DURATION.into(), 1650.0);
let lap = Lap {
start_index: None,
end_index: None,
summary,
trigger: LapTrigger::Auto,
name: Some("Full run".into()),
};
let json = serde_json::to_string_pretty(&lap).unwrap();
assert!(!json.contains("start_index"));
assert!(!json.contains("end_index"));
let back: Lap = serde_json::from_str(&json).unwrap();
assert_eq!(back, lap);
}
#[test]
fn lap_trigger_variants_round_trip() {
let variants = vec![
LapTrigger::Manual,
LapTrigger::Distance,
LapTrigger::Time,
LapTrigger::HeartRateZone,
LapTrigger::Auto,
LapTrigger::Custom("swim_length".into()),
];
for trigger in variants {
let json = serde_json::to_string(&trigger).unwrap();
let back: LapTrigger = serde_json::from_str(&json).unwrap();
assert_eq!(back, trigger);
}
}
#[test]
fn lap_empty_summary_omitted() {
let lap = Lap {
start_index: Some(0),
end_index: Some(10),
summary: BTreeMap::new(),
trigger: LapTrigger::Manual,
name: None,
};
let json = serde_json::to_string(&lap).unwrap();
assert!(!json.contains("summary"));
assert!(!json.contains("name"));
}
}