use std::io::Read;
use std::io::Write;
use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use anyhow::Context as _;
use anyhow::Result;
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct Reminder {
pub time: SystemTime,
pub message: String,
}
impl Reminder {
pub fn write_to<W>(&self, mut w: W) -> Result<()>
where
W: Write,
{
let nanos = self
.time
.duration_since(UNIX_EPOCH)
.context("reminder time is before UNIX epoch")?
.as_nanos();
let nanos = u64::try_from(nanos).context("reminder time is too large")?;
let () = w.write_all(&nanos.to_ne_bytes())?;
let msg = self.message.as_bytes();
let len = msg.len();
let () = w.write_all(&len.to_ne_bytes())?;
let () = w.write_all(msg)?;
Ok(())
}
pub fn read_from<R>(mut r: R) -> Result<Self>
where
R: Read,
{
let mut nanos_buf = 0u64.to_ne_bytes();
let () = r.read_exact(&mut nanos_buf)?;
let nanos = u64::from_le_bytes(nanos_buf);
let time = UNIX_EPOCH
.checked_add(Duration::from_nanos(nanos))
.context("reminder time is too large")?;
let mut len_buf = 0usize.to_ne_bytes();
let () = r.read_exact(&mut len_buf)?;
let len = usize::from_le_bytes(len_buf);
let mut msg_buf = vec![0u8; len];
let () = r.read_exact(&mut msg_buf)?;
let message = String::from_utf8(msg_buf).context("reminder message is not valid UTF-8")?;
let reminder = Self { time, message };
Ok(reminder)
}
#[inline]
pub fn duration(&self) -> Option<Duration> {
self.time.duration_since(SystemTime::now()).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn round_trip_after_epoch() {
let reminder = Reminder {
time: UNIX_EPOCH + Duration::from_secs(1_234_567),
message: "Buy milk".to_string(),
};
let mut buf = Vec::new();
reminder.write_to(&mut buf).unwrap();
let decoded = Reminder::read_from(Cursor::new(buf)).unwrap();
assert_eq!(decoded, reminder);
}
#[test]
fn round_trip_unicode_message() {
let reminder = Reminder {
time: SystemTime::now(),
message: "☕ Привет 世界".to_string(),
};
let mut buf = Vec::new();
let () = reminder.write_to(&mut buf).unwrap();
let decoded = Reminder::read_from(Cursor::new(buf)).unwrap();
assert_eq!(decoded.message, reminder.message);
assert_eq!(decoded.time, reminder.time);
}
#[test]
fn truncated_input_fails() {
let reminder = Reminder {
time: UNIX_EPOCH,
message: "hello".to_string(),
};
let mut buf = Vec::new();
let () = reminder.write_to(&mut buf).unwrap();
let _byte = buf.pop();
let result = Reminder::read_from(Cursor::new(buf));
assert!(result.is_err(), "{result:?}");
}
}