[bugfix] harden countdown snapshot serialization and error messaging
- guard instant-to-epoch conversion against pre-boot instants - serialize snapshot duration as milliseconds (u128) in DTO/commands - derive Display/Error for CountdownError and return user-facing error strings - remove unused command result alias and minor snapshot lock cleanup
This commit is contained in:
@@ -17,7 +17,10 @@ impl ClockAnchor {
|
||||
}
|
||||
|
||||
pub fn instant_to_epoch_ms(&self, instant: tokio::time::Instant) -> u128 {
|
||||
instant.duration_since(self.boot_instant).as_millis() + self.boot_epoch_ms
|
||||
match instant.checked_duration_since(self.boot_instant) {
|
||||
Some(duration) => duration.as_millis() + self.boot_epoch_ms,
|
||||
None => return 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: implement the handling of multiple countdowns
|
||||
|
||||
@@ -4,15 +4,13 @@ use crate::AppState;
|
||||
use tauri::{command, State};
|
||||
use tokio::time::Instant;
|
||||
|
||||
type CmdResult<T> = Result<T, String>;
|
||||
|
||||
#[command]
|
||||
pub async fn countdown_start(state: State<'_, AppState>) -> Result<(), String> {
|
||||
state
|
||||
.countdown_service
|
||||
.start(Instant::now())
|
||||
.await
|
||||
.map_err(|e: CountdownError| format!("{e:?}"))?;
|
||||
.map_err(|e: CountdownError| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -28,7 +26,7 @@ pub async fn countdown_pause(state: State<'_, AppState>) -> Result<(), String> {
|
||||
.countdown_service
|
||||
.pause(Instant::now())
|
||||
.await
|
||||
.map_err(|e: CountdownError| format!("{e:?}"))?;
|
||||
.map_err(|e: CountdownError| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -38,7 +36,7 @@ pub async fn countdown_resume(state: State<'_, AppState>) -> Result<(), String>
|
||||
.countdown_service
|
||||
.resume(Instant::now())
|
||||
.await
|
||||
.map_err(|e: CountdownError| format!("{e:?}"))?;
|
||||
.map_err(|e: CountdownError| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -58,7 +56,7 @@ pub async fn countdown_snapshot(
|
||||
Ok(CountdownSnapshotDto {
|
||||
id: countdown_snapshot.id,
|
||||
label: countdown_snapshot.label,
|
||||
duration: countdown_snapshot.duration,
|
||||
duration: countdown_snapshot.duration.as_millis(),
|
||||
state: countdown_snapshot.state,
|
||||
start_epoch_ms: start,
|
||||
target_epoch_ms: target,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use tokio::time::Duration;
|
||||
|
||||
use crate::countdown::model::CountdownState;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CountdownSnapshotDto {
|
||||
pub id: u64,
|
||||
pub label: String,
|
||||
pub duration: Duration,
|
||||
pub duration: u128,
|
||||
pub state: CountdownState,
|
||||
pub start_epoch_ms: Option<u128>,
|
||||
pub target_epoch_ms: Option<u128>,
|
||||
@@ -16,7 +14,7 @@ impl CountdownSnapshotDto {
|
||||
pub fn new(
|
||||
id: u64,
|
||||
label: String,
|
||||
duration: Duration,
|
||||
duration: u128,
|
||||
state: CountdownState,
|
||||
start_epoch_ms: Option<u128>,
|
||||
target_epoch_ms: Option<u128>,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::countdown::model::CountdownState;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum CountdownError {
|
||||
#[error("Invalid transition from {from:?} to {action:?}")]
|
||||
InvalidTransition {
|
||||
from: CountdownState,
|
||||
action: &'static str,
|
||||
},
|
||||
#[error("Time overflow")]
|
||||
TimeOverflow,
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl CountdownService {
|
||||
}
|
||||
|
||||
pub async fn snapshot(&self, now: Instant) -> CountdownSnapshot {
|
||||
let countdown = self.countdown.lock().await.clone();
|
||||
let countdown = self.countdown.lock().await;
|
||||
CountdownSnapshot {
|
||||
id: countdown.id(),
|
||||
label: countdown.label().to_string(),
|
||||
|
||||
Reference in New Issue
Block a user