Skip to content

Commit

Permalink
implement Transformable for std::time::Instant
Browse files Browse the repository at this point in the history
  • Loading branch information
al8n committed Dec 23, 2023
1 parent 6bf1ca4 commit ca37dce
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 135 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# UNRELEASED

# 0.1.2 (January 6th, 2022)
# 0.1.2 (Dec 23rd, 2023)

FEATURES


- Implement `Transformable` for `std::time::Instant`.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "transformable"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
repository = "https://github.com/al8n/transformable"
homepage = "https://github.com/al8n/transformable"
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ Copyright (c) 2023 Al Liu.
[doc-url]: https://docs.rs/transformable
[crates-url]: https://crates.io/crates/transformable
[codecov-url]: https://app.codecov.io/gh/al8n/transformable/
[zh-cn-url]: https://github.com/al8n/transformable/tree/main/README-zh_CN.md
3 changes: 2 additions & 1 deletion ci/sanitizer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ cargo hack test --lib --each-feature

# Run thread sanitizer with cargo-hack
RUSTFLAGS="-Z sanitizer=thread" \
cargo hack -Zbuild-std test --lib --each-feature
cargo hack -Zbuild-std test --lib --each-feature --targe x86_64-unknown-linux-gnu

138 changes: 8 additions & 130 deletions src/impls/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,137 +126,15 @@ const fn decode_duration_unchecked(src: &[u8]) -> (usize, Duration) {
}

#[cfg(feature = "std")]
pub use _impl::*;

#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub use system_time::*;
#[cfg(feature = "std")]
mod _impl {
use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};

use super::*;

/// Error returned by [`SystemTime`] when transforming.
#[derive(Debug, Clone)]
pub enum SystemTimeTransformError {
/// The buffer is too small to encode the value.
EncodeBufferTooSmall,
/// Corrupted binary data.
Corrupted,
/// Invalid system time.
InvalidSystemTime(SystemTimeError),
}

impl core::fmt::Display for SystemTimeTransformError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EncodeBufferTooSmall => write!(
f,
"buffer is too small, use `Transformable::encoded_len` to pre-allocate a buffer with enough space"
),
Self::Corrupted => write!(f, "corrupted binary data"),
Self::InvalidSystemTime(e) => write!(f, "{e}"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for SystemTimeTransformError {}

impl Transformable for SystemTime {
type Error = SystemTimeTransformError;

fn encode(&self, dst: &mut [u8]) -> Result<(), Self::Error> {
if dst.len() < self.encoded_len() {
return Err(Self::Error::EncodeBufferTooSmall);
}

let buf = encode_duration_unchecked(
self
.duration_since(UNIX_EPOCH)
.map_err(Self::Error::InvalidSystemTime)?,
);
dst[..ENCODED_LEN].copy_from_slice(&buf);
Ok(())
}
mod system_time;

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn encode_to_writer<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
let mut buf = [0u8; ENCODED_LEN];
self
.encode(&mut buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
writer.write_all(&buf)
}

#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
async fn encode_to_async_writer<W: futures_util::io::AsyncWrite + Send + Unpin>(
&self,
writer: &mut W,
) -> std::io::Result<()>
where
Self::Error: Send + Sync + 'static,
{
use futures_util::AsyncWriteExt;

let mut buf = [0u8; ENCODED_LEN];
self
.encode(&mut buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
writer.write_all(&buf).await
}

fn encoded_len(&self) -> usize {
ENCODED_LEN
}

fn decode(src: &[u8]) -> Result<(usize, Self), Self::Error>
where
Self: Sized,
{
if src.len() < ENCODED_LEN {
return Err(Self::Error::Corrupted);
}

let (readed, dur) = decode_duration_unchecked(src);
Ok((readed, UNIX_EPOCH + dur))
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn decode_from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<(usize, Self)>
where
Self: Sized,
{
let mut buf = [0; ENCODED_LEN];
reader.read_exact(&mut buf)?;
let (readed, dur) = decode_duration_unchecked(&buf);
Ok((readed, UNIX_EPOCH + dur))
}

#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
async fn decode_from_async_reader<R: futures_util::io::AsyncRead + Send + Unpin>(
reader: &mut R,
) -> std::io::Result<(usize, Self)>
where
Self: Sized,
Self::Error: Send + Sync + 'static,
{
use futures_util::AsyncReadExt;

let mut buf = [0; ENCODED_LEN];
reader.read_exact(&mut buf).await?;
let (readed, dur) = decode_duration_unchecked(&buf);
Ok((readed, UNIX_EPOCH + dur))
}
}

test_transformable!(SystemTime => test_systemtime_transformable({
let now = SystemTime::now();
std::thread::sleep(std::time::Duration::from_millis(10));
now
}));
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub use instant::*;
#[cfg(feature = "std")]
mod instant;

test_transformable!(Duration => test_duration_transformable(Duration::new(10, 1080)));
155 changes: 155 additions & 0 deletions src/impls/time/instant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::{
sync::OnceLock,
time::{Instant, SystemTime, SystemTimeError},
};

use super::*;

/// Error returned by [`Instant`] when transforming.
#[derive(Debug, Clone)]
pub enum InstantTransformError {
/// The buffer is too small to encode the value.
EncodeBufferTooSmall,
/// Corrupted binary data.
Corrupted,
/// Invalid system time.
InvalidSystemTime(SystemTimeError),
}

impl core::fmt::Display for InstantTransformError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EncodeBufferTooSmall => write!(
f,
"buffer is too small, use `Transformable::encoded_len` to pre-allocate a buffer with enough space"
),
Self::Corrupted => write!(f, "corrupted binary data"),
Self::InvalidSystemTime(e) => write!(f, "{e}"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for InstantTransformError {}

impl Transformable for Instant {
type Error = InstantTransformError;

fn encode(&self, dst: &mut [u8]) -> Result<(), Self::Error> {
if dst.len() < self.encoded_len() {
return Err(Self::Error::EncodeBufferTooSmall);
}

let buf = encode_duration_unchecked(encode_instant_to_duration(*self));
dst[..ENCODED_LEN].copy_from_slice(&buf);
Ok(())
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn encode_to_writer<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
let mut buf = [0u8; ENCODED_LEN];
self
.encode(&mut buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
writer.write_all(&buf)
}

#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
async fn encode_to_async_writer<W: futures_util::io::AsyncWrite + Send + Unpin>(
&self,
writer: &mut W,
) -> std::io::Result<()>
where
Self::Error: Send + Sync + 'static,
{
use futures_util::AsyncWriteExt;

let mut buf = [0u8; ENCODED_LEN];
self
.encode(&mut buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
writer.write_all(&buf).await
}

fn encoded_len(&self) -> usize {
ENCODED_LEN
}

fn decode(src: &[u8]) -> Result<(usize, Self), Self::Error>
where
Self: Sized,
{
if src.len() < ENCODED_LEN {
return Err(Self::Error::Corrupted);
}

let (readed, instant) = decode_duration_unchecked(src);
Ok((readed, decode_instant_from_duration(instant)))
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn decode_from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<(usize, Self)>
where
Self: Sized,
{
let mut buf = [0; ENCODED_LEN];
reader.read_exact(&mut buf)?;
let (readed, instant) = decode_duration_unchecked(&buf);
Ok((readed, decode_instant_from_duration(instant)))
}

#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
async fn decode_from_async_reader<R: futures_util::io::AsyncRead + Send + Unpin>(
reader: &mut R,
) -> std::io::Result<(usize, Self)>
where
Self: Sized,
Self::Error: Send + Sync + 'static,
{
use futures_util::AsyncReadExt;

let mut buf = [0; ENCODED_LEN];
reader.read_exact(&mut buf).await?;
let (readed, instant) = decode_duration_unchecked(&buf);
Ok((readed, decode_instant_from_duration(instant)))
}
}

fn init(now: Instant) -> (SystemTime, Instant) {
static ONCE: OnceLock<(SystemTime, Instant)> = OnceLock::new();
*ONCE.get_or_init(|| {
let system_now = SystemTime::now();
(system_now, now)
})
}

#[inline]
fn encode_instant_to_duration(instant: Instant) -> Duration {
let (system_now, instant_now) = init(instant);
if instant <= instant_now {
system_now.duration_since(SystemTime::UNIX_EPOCH).unwrap() + (instant_now - instant)
} else {
system_now.duration_since(SystemTime::UNIX_EPOCH).unwrap() + (instant - instant_now)
}
}

#[inline]
fn decode_instant_from_duration(duration: Duration) -> Instant {
let (system_now, instant_now) = init(Instant::now());
let system_time = SystemTime::UNIX_EPOCH + duration;
if system_time >= system_now {
instant_now + system_time.duration_since(system_now).unwrap()
} else {
instant_now - system_now.duration_since(system_time).unwrap()
}
}

test_transformable!(Instant => test_instant_transformable({
let now = Instant::now();
std::thread::sleep(std::time::Duration::from_millis(10));
now
}));
Loading

0 comments on commit ca37dce

Please sign in to comment.