orphanage 0.5.6

Random collection of stuff that is still searching for a home.
Documentation
use std::ops::Deref;

use postgres_types::{FromSql, IsNull, ToSql, Type};

use bytes::{BufMut, BytesMut};

use byteorder::{BigEndian, ReadBytesExt};

use jiff::{SignedDuration, Span, SpanRelativeTo};

use std::error::Error;

/// A wrapper to implement `FromSql` for `jiff::Span`
#[derive(Debug, Clone)]
pub struct JiffSpan(pub Span);

impl JiffSpan {
  #[must_use]
  pub const fn into_inner(self) -> Span {
    self.0
  }

  /// Turn `JiffSpan` into [`SignedDuration`].
  ///
  /// Assumes days are exactly 24h.
  ///
  /// # Errors
  /// Returns `jiff::Error`.
  pub fn to_duration(self) -> Result<SignedDuration, jiff::Error> {
    let span = self.into_inner();
    let rel = SpanRelativeTo::days_are_24_hours();
    span.to_duration(rel)
  }
}

impl Deref for JiffSpan {
  type Target = Span;
  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl<'a> FromSql<'a> for JiffSpan {
  fn from_sql(
    ty: &Type,
    raw: &'a [u8]
  ) -> Result<Self, Box<dyn Error + Sync + Send>> {
    if !matches!(*ty, Type::INTERVAL) {
      return Err("Type must be INTERVAL".into());
    }

    // postgres interval wire format:
    // - 8 bytes (i64): microseconds
    // - 4 bytes (i32): days
    // - 4 bytes (i32): months
    let mut reader = raw;
    let micros = reader.read_i64::<BigEndian>()?;
    let days = reader.read_i32::<BigEndian>()?;
    let months = reader.read_i32::<BigEndian>()?;

    let span = Span::new().months(months).days(days).microseconds(micros);

    Ok(Self(span))
  }

  fn accepts(ty: &Type) -> bool {
    matches!(*ty, Type::INTERVAL)
  }
}

impl ToSql for JiffSpan {
  fn to_sql(
    &self,
    ty: &Type,
    out: &mut BytesMut
  ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
    if !matches!(*ty, Type::INTERVAL) {
      return Err("Type must be INTERVAL".into());
    }

    let span = &self.0;

    // postgres combines years and months.
    let ymonths = i32::from(span.get_years());
    let ymonths = ymonths
      .checked_mul(12)
      .ok_or("Month overflow for Postgres Interval")?;
    let total_months = ymonths
      .checked_add(span.get_months())
      .ok_or("Month overflow for Postgres Interval")?;

    // postgres keeps days separate
    let total_days = span.get_days();

    // calculate microseconds
    let total_micros = (i64::from(span.get_hours()) * 3_600_000_000)
      + (span.get_minutes() * 60_000_000)
      + (span.get_seconds() * 1_000_000)
      + (span.get_milliseconds() * 1_000)
      + (span.get_microseconds());

    out.put_i64(total_micros);
    out.put_i32(total_days);
    out.put_i32(total_months);

    Ok(IsNull::No)
  }

  fn accepts(ty: &Type) -> bool {
    matches!(*ty, Type::INTERVAL)
  }

  fn to_sql_checked(
    &self,
    ty: &Type,
    out: &mut BytesMut
  ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
    self.to_sql(ty, out)
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :