wtx 0.28.0

A collection of different transport implementations and related tools focused primarily on web technologies.
use crate::{
  database::{
    DatabaseError, Typed,
    client::postgres::{DecodeWrapper, EncodeWrapper, Postgres, Ty},
  },
  misc::{Decode, Encode},
};
use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, TimeDelta, TimeZone, Utc};

const MIN_PG_ND: Option<NaiveDate> = NaiveDate::from_ymd_opt(-4713, 1, 1);
const MAX_CHRONO_ND: Option<NaiveDate> = NaiveDate::from_ymd_opt(262142, 1, 1);

impl<E> Decode<'_, Postgres<E>> for DateTime<Utc>
where
  E: From<crate::Error>,
{
  #[inline]
  fn decode(aux: &mut (), dw: &mut DecodeWrapper<'_>) -> Result<Self, E> {
    let naive = <NaiveDateTime as Decode<Postgres<E>>>::decode(aux, dw)?;
    Ok(Utc.from_utc_datetime(&naive))
  }
}

impl<E, TZ> Encode<Postgres<E>> for DateTime<TZ>
where
  E: From<crate::Error>,
  TZ: TimeZone,
{
  #[inline]
  fn encode(&self, _: &mut (), ew: &mut EncodeWrapper<'_, '_>) -> Result<(), E> {
    Encode::<Postgres<E>>::encode(&self.naive_utc(), &mut (), ew)
  }
}

impl<E, TZ> Typed<Postgres<E>> for DateTime<TZ>
where
  E: From<crate::Error>,
  TZ: TimeZone,
{
  #[inline]
  fn runtime_ty(&self) -> Option<Ty> {
    <Self as Typed<Postgres<E>>>::static_ty()
  }

  #[inline]
  fn static_ty() -> Option<Ty> {
    Some(Ty::Timestamptz)
  }
}

impl<E> Decode<'_, Postgres<E>> for NaiveDate
where
  E: From<crate::Error>,
{
  #[inline]
  fn decode(aux: &mut (), dw: &mut DecodeWrapper<'_>) -> Result<Self, E> {
    let days: i32 = Decode::<Postgres<E>>::decode(aux, dw)?;
    pg_epoch_nd()
      .and_then(|el| el.checked_add_signed(TimeDelta::try_days(days.into())?))
      .ok_or_else(|| {
        E::from(DatabaseError::UnexpectedValueFromBytes { expected: "timestamp" }.into())
      })
  }
}

impl<E> Encode<Postgres<E>> for NaiveDate
where
  E: From<crate::Error>,
{
  #[inline]
  fn encode(&self, _: &mut (), ew: &mut EncodeWrapper<'_, '_>) -> Result<(), E> {
    Encode::<Postgres<E>>::encode(
      &match pg_epoch_nd().and_then(|epoch| {
        if self < &MIN_PG_ND? || self > &MAX_CHRONO_ND? {
          return None;
        }
        i32::try_from(self.signed_duration_since(epoch).num_days()).ok()
      }) {
        Some(time) => time,
        None => {
          return Err(E::from(DatabaseError::UnexpectedValueFromBytes { expected: "date" }.into()));
        }
      },
      &mut (),
      ew,
    )
  }
}

impl<E> Typed<Postgres<E>> for NaiveDate
where
  E: From<crate::Error>,
{
  #[inline]
  fn runtime_ty(&self) -> Option<Ty> {
    <Self as Typed<Postgres<E>>>::static_ty()
  }

  #[inline]
  fn static_ty() -> Option<Ty> {
    Some(Ty::Date)
  }
}

impl<E> Decode<'_, Postgres<E>> for NaiveDateTime
where
  E: From<crate::Error>,
{
  #[inline]
  fn decode(aux: &mut (), dw: &mut DecodeWrapper<'_>) -> Result<Self, E> {
    let timestamp = Decode::<Postgres<E>>::decode(aux, dw)?;
    pg_epoch_ndt()
      .and_then(|el| el.checked_add_signed(Duration::microseconds(timestamp)))
      .ok_or_else(|| {
        E::from(DatabaseError::UnexpectedValueFromBytes { expected: "timestamp" }.into())
      })
  }
}

impl<E> Encode<Postgres<E>> for NaiveDateTime
where
  E: From<crate::Error>,
{
  #[inline]
  fn encode(&self, _: &mut (), ew: &mut EncodeWrapper<'_, '_>) -> Result<(), E> {
    Encode::<Postgres<E>>::encode(
      &match pg_epoch_ndt().and_then(|epoch| {
        if self < &MIN_PG_ND?.and_hms_opt(0, 0, 0)?
          || self > &MAX_CHRONO_ND?.and_hms_opt(0, 0, 0)?
        {
          return None;
        }
        self.signed_duration_since(epoch).num_microseconds()
      }) {
        Some(time) => time,
        None => {
          return Err(E::from(
            DatabaseError::UnexpectedValueFromBytes { expected: "timestamp" }.into(),
          ));
        }
      },
      &mut (),
      ew,
    )
  }
}

impl<E> Typed<Postgres<E>> for NaiveDateTime
where
  E: From<crate::Error>,
{
  #[inline]
  fn runtime_ty(&self) -> Option<Ty> {
    <Self as Typed<Postgres<E>>>::static_ty()
  }

  #[inline]
  fn static_ty() -> Option<Ty> {
    Some(Ty::Timestamp)
  }
}

fn pg_epoch_nd() -> Option<NaiveDate> {
  NaiveDate::from_ymd_opt(2000, 1, 1)
}

fn pg_epoch_ndt() -> Option<NaiveDateTime> {
  pg_epoch_nd()?.and_hms_opt(0, 0, 0)
}

test!(datetime_utc, DateTime<Utc>, Utc.from_utc_datetime(&pg_epoch_ndt().unwrap()));