cido 0.2.0

Core traits and implementations for indexing with cido
Documentation
use super::DecodeHexError;
use crate::stable_hash;
use core::fmt::{Debug, Display, Formatter};
use core::ops::{Deref, DerefMut};
use core::str::FromStr;
use serde::Deserialize;
use smallvec::SmallVec;

/// Analogous to Vec<u8> with better support for some operations
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bytes(SmallVec<[u8; 32]>);

impl Bytes {
  pub fn is_empty(&self) -> bool {
    self.0.is_empty()
  }

  pub fn bytes(&self) -> &[u8] {
    &self.0
  }
}

impl<'a> From<&'a [u8]> for Bytes {
  fn from(b: &'a [u8]) -> Self {
    Self(SmallVec::from_slice(b))
  }
}

impl From<[u8; 32]> for Bytes {
  fn from(value: [u8; 32]) -> Self {
    Self(SmallVec::<[u8; 32]>::from(value))
  }
}

impl From<Vec<u8>> for Bytes {
  fn from(value: Vec<u8>) -> Self {
    Self(SmallVec::from_vec(value))
  }
}

impl From<Bytes> for Vec<u8> {
  fn from(val: Bytes) -> Self {
    val.0.to_vec()
  }
}

impl Display for Bytes {
  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
    write!(f, "0x")?;
    if self.0.len() > 8 {
      for v in &self.0[..4] {
        write!(f, "{:02x}", v)?;
      }
      write!(f, "..")?;
      for v in &self.0[self.0.len() - 4..] {
        write!(f, "{:02x}", v)?;
      }
    } else {
      <Self as ::core::fmt::LowerHex>::fmt(self, f)?;
    }
    Ok(())
  }
}

impl Debug for Bytes {
  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
    write!(f, "0x")?;
    <Self as ::core::fmt::LowerHex>::fmt(self, f)
  }
}

impl ::core::fmt::Binary for Bytes {
  fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
    for byte in self.iter() {
      write!(f, "{:08b}", byte)?;
    }
    Ok(())
  }
}

impl ::core::fmt::LowerHex for Bytes {
  fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
    for byte in self.iter() {
      write!(f, "{:02x}", byte)?;
    }
    Ok(())
  }
}

impl ::core::fmt::UpperHex for Bytes {
  fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
    for byte in self.iter() {
      write!(f, "{:02X}", byte)?;
    }
    Ok(())
  }
}

impl FromStr for Bytes {
  type Err = DecodeHexError;

  fn from_str(mut s: &str) -> Result<Self, Self::Err> {
    let extra_len = if s.starts_with("0x") || s.starts_with("0X") {
      s = &s[2..];
      2
    } else {
      0
    };

    if s.len() & 1 != 0 {
      return Err(DecodeHexError::WrongSize {
        expected: s.len() + extra_len + 1,
        actual: s.len() + extra_len,
      });
    }
    let mut output = Self(SmallVec::with_capacity(s.len() / 2));

    for chars in s.as_bytes().chunks_exact(2) {
      let (l, r) = (chars[0] as char, chars[1] as char);
      match (l.to_digit(16), r.to_digit(16)) {
        (Some(l), Some(r)) => output.0.push(((l as u8) << 4) | r as u8),
        (_, _) => return Err(DecodeHexError::WrongCharacter(l, r)),
      };
    }
    Ok(output)
  }
}

impl AsRef<[u8]> for Bytes {
  fn as_ref(&self) -> &[u8] {
    &self.0
  }
}
impl AsMut<[u8]> for Bytes {
  fn as_mut(&mut self) -> &mut [u8] {
    &mut self.0
  }
}
impl Deref for Bytes {
  type Target = [u8];

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}
impl DerefMut for Bytes {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.0
  }
}

impl serde::Serialize for Bytes {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::Serializer,
  {
    use std::fmt::Write;
    if serializer.is_human_readable() {
      super::TRANSIENT_STRING_SERIALIZER.with(|s| {
        let mut s = s.borrow_mut();
        s.clear();
        s.reserve(self.0.len() * 2 + 2);
        // use debug because it should always have the full representation
        write!(s, "{:?}", self).unwrap();
        s.serialize(serializer)
      })
    } else {
      self.0.serialize(serializer)
    }
  }
}

impl<'de> Deserialize<'de> for Bytes {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: serde::Deserializer<'de>,
  {
    use serde::de::Error;
    if deserializer.is_human_readable() {
      let s = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
      s.parse::<Self>().map_err(D::Error::custom)
    } else {
      let x = SmallVec::<[u8; 32]>::deserialize(deserializer)?;
      Ok(Self(x))
    }
  }
}

mod sql {
  use super::*;
  use sqlx::{
    Decode, Encode, Postgres, Type,
    postgres::{PgHasArrayType, PgTypeInfo},
  };

  impl Type<Postgres> for Bytes {
    fn type_info() -> PgTypeInfo {
      <[u8] as Type<Postgres>>::type_info()
    }

    fn compatible(ty: &PgTypeInfo) -> bool {
      <[u8] as Type<Postgres>>::compatible(ty)
    }
  }
  impl<'a> Encode<'a, Postgres> for Bytes {
    fn encode_by_ref(
      &self,
      buf: &mut <Postgres as sqlx::Database>::ArgumentBuffer<'a>,
    ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync + 'static>> {
      <&[u8] as Encode<Postgres>>::encode_by_ref(&self.0.as_slice(), buf)
    }
  }

  impl<'a> Decode<'a, Postgres> for Bytes {
    fn decode(
      value: <Postgres as sqlx::Database>::ValueRef<'a>,
    ) -> Result<Self, sqlx::error::BoxDynError> {
      let decoded = <&'a [u8] as Decode<Postgres>>::decode(value)?;
      Ok(Self(SmallVec::from_slice(decoded)))
    }
  }

  impl PgHasArrayType for Bytes {
    fn array_type_info() -> PgTypeInfo {
      <Vec<u8> as PgHasArrayType>::array_type_info()
    }
  }
}

mod graphql {
  use super::*;

  impl async_graphql::OutputType for Bytes {
    fn type_name() -> std::borrow::Cow<'static, str> {
      ::std::borrow::Cow::Borrowed("Bytes")
    }

    fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
      registry.create_output_type::<Self, _>(async_graphql::registry::MetaTypeId::Scalar, |_| {
        async_graphql::registry::MetaType::Scalar {
          name: Self::type_name().to_string(),
          description: None,
          is_valid: None,
          visible: None,
          inaccessible: false,
          tags: vec![],
          directive_invocations: vec![],
          specified_by_url: None,
        }
      })
    }

    /// Resolve an output value to `async_graphql::Value`.
    async fn resolve(
      &self,
      _ctx: &async_graphql::ContextSelectionSet<'_>,
      _field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
    ) -> async_graphql::ServerResult<async_graphql::Value> {
      Ok(async_graphql::Value::String(format!("{self:?}")))
    }
  }

  impl async_graphql::InputType for Bytes {
    type RawValueType = Self;

    fn type_name() -> std::borrow::Cow<'static, str> {
      "Bytes".into()
    }

    fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
      registry.create_input_type::<Self, _>(async_graphql::registry::MetaTypeId::Scalar, |_| {
        async_graphql::registry::MetaType::Scalar {
          name: Self::type_name().to_string(),
          description: None,
          visible: None,
          is_valid: None,
          specified_by_url: None,
          inaccessible: false,
          tags: vec![],
          directive_invocations: vec![],
        }
      })
    }

    fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
      match value.unwrap_or_default() {
        async_graphql::Value::String(v) => v
          .parse::<Self>()
          .map_err(async_graphql::InputValueError::custom),
        async_graphql::Value::Binary(bytes) => Ok(Self::from(&*bytes)),
        _ => Err(async_graphql::InputValueError::custom(
          "Only supports hex strings or byte arrays",
        )),
      }
    }

    fn to_value(&self) -> async_graphql::Value {
      async_graphql::Value::String(format!("{self:?}"))
    }

    fn as_raw_value(&self) -> Option<&Self::RawValueType> {
      Some(self)
    }
  }
}

impl stable_hash::StableHash for Bytes {
  fn stable_hash<H: stable_hash::StableHasher>(&self, field_address: H::Addr, state: &mut H) {
    stable_hash::utils::AsBytes(self.0.as_ref()).stable_hash(field_address, state);
  }
}