serde_v8vs 0.1.0

serde support for V8's ValueSerializer format
Documentation
#[cfg(any(test, feature = "std"))]
use std::time::{Duration, SystemTime};

use serde_core::{
	Deserializer,
	de::{self},
	ser::Serializer
};

pub(crate) const DATE_MAGIC: &str = "{312dedf6-9f6a-436f-aeb9-d3defff0494c}";

/// Serialize a "date-ish" type as a JS [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date).
/// The default behavior might serialize them as objects.
///
/// Supported "date-ish" types are:
/// - [`SystemTime`]
/// - [`jiff::Timestamp`](https://docs.rs/jiff/0.2/jiff/struct.Timestamp.html) (with the `jiff` Cargo feature)
/// - [`chrono::DateTime<Utc>`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) (with the `chrono` Cargo
///   feature)
pub mod as_date {
	use serde_core::{Deserializer, Serializer};

	use super::AsDate;

	pub fn serialize<S: Serializer, T: AsDate>(date: &T, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer
	{
		date.serialize_as_date(serializer)
	}

	pub fn deserialize<'de, D: Deserializer<'de>, T: AsDate>(deserializer: D) -> Result<T, D::Error> {
		T::deserialize_from_date(deserializer)
	}
}

#[diagnostic::on_unimplemented(
	message = "{Self} is not a date-ish type",
	note = "valid date-ish types are SystemTime, chrono::DateTime (with `chrono` feature), or jiff::Timestamp (with `jiff` feature)"
)]
pub trait AsDate {
	fn serialize_as_date<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer;

	fn deserialize_from_date<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
	where
		Self: Sized;
}

#[cfg(any(test, feature = "std"))]
impl AsDate for SystemTime {
	fn serialize_as_date<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer
	{
		let dur = self
			.duration_since(SystemTime::UNIX_EPOCH)
			.map_err(|e| <S::Error as serde_core::ser::Error>::custom(e.to_string()))?;
		serializer.serialize_newtype_struct(DATE_MAGIC, &((dur.as_secs() as f64) * 1000. + (dur.subsec_nanos() as f64) / 1_000_000.))
	}

	fn deserialize_from_date<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
	where
		Self: Sized
	{
		let t = deserializer.deserialize_f64(F64Extractor)?;
		let millis = t.trunc();
		let nanos = (t - millis) * 1_000_000.;
		Ok(SystemTime::UNIX_EPOCH
			.checked_add(Duration::from_nanos_u128((millis as u128 * 1_000_000) + nanos as u128))
			.ok_or_else(|| <D::Error as de::Error>::custom("date cannot be represented as SystemTime"))?)
	}
}

#[cfg(feature = "jiff-0_2")]
impl AsDate for jiff_0_2::Timestamp {
	fn serialize_as_date<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer
	{
		serializer.serialize_newtype_struct(DATE_MAGIC, &((self.as_nanosecond() as f64) / 1_000_000.))
	}

	fn deserialize_from_date<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
	where
		Self: Sized
	{
		let t = deserializer.deserialize_f64(F64Extractor)?;
		let millis = t.trunc();
		let nanos = (t - millis) * 1_000_000.;
		Ok(jiff_0_2::Timestamp::from_nanosecond((millis as i128 * 1_000_000) + nanos as i128)
			.map_err(|e| <D::Error as de::Error>::custom(format!("date cannot be represented as jiff::Timestamp: {e}")))?)
	}
}

#[cfg(feature = "chrono-0_4")]
impl AsDate for chrono_0_4::DateTime<chrono_0_4::Utc> {
	fn serialize_as_date<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer
	{
		serializer.serialize_newtype_struct(DATE_MAGIC, &((self.timestamp_millis() as f64) + (self.timestamp_subsec_nanos() as f64) / 1_000_000.))
	}

	fn deserialize_from_date<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
	where
		Self: Sized
	{
		let t_sec = deserializer.deserialize_f64(F64Extractor)? / 1000.;
		let secs = t_sec.trunc();
		let nanos = (t_sec - secs) * 1_000_000_000.;
		Ok(chrono_0_4::DateTime::from_timestamp(secs as i64, nanos as u32)
			.ok_or_else(|| <D::Error as de::Error>::custom(format!("date cannot be represented as chrono::DateTime")))?)
	}
}

struct F64Extractor;

impl de::Visitor<'_> for F64Extractor {
	type Value = f64;

	fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
		write!(formatter, "date")
	}

	fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
	where
		E: de::Error
	{
		Ok(v)
	}
}