cido 0.2.0

Core traits and implementations for indexing with cido
Documentation
use std::borrow::Cow;

use crate::stable_hash::{StableHash, StableHasher};
use async_graphql::{InputType, OutputType};
use sqlx::postgres::PgHasArrayType;

use crate::poi::Hashable;

use super::*;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id<I>(pub(crate) I);

impl<I> Id<I> {
  pub fn into_inner(self) -> I {
    self.0
  }

  pub fn from(inner: I) -> Self {
    Self(inner)
  }

  pub fn as_ref(&self) -> Id<&I> {
    Id(&self.0)
  }

  pub fn map<U>(&self, f: impl FnOnce(&I) -> U) -> Id<U> {
    Id(f(&self.0))
  }
}

impl<I> From<I> for Id<I> {
  fn from(value: I) -> Self {
    Self(value)
  }
}

impl<I: std::fmt::Display> std::fmt::Display for Id<I> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    self.0.fmt(f)
  }
}

impl<I: std::str::FromStr> std::str::FromStr for Id<I> {
  type Err = I::Err;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    I::from_str(s).map(Self)
  }
}

impl<I> core::ops::Deref for Id<I> {
  type Target = I;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl<I> core::ops::DerefMut for Id<I> {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.0
  }
}

impl<I: InputType> InputType for Id<I> {
  type RawValueType = Self;

  fn type_name() -> std::borrow::Cow<'static, str> {
    (*I::type_name())
      .replace("String", "ID")
      .replace("Bytes", "ID")
      .into()
    // std::borrow::Cow::Borrowed("ID")
  }

  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,
        is_valid: None,
        visible: None,
        inaccessible: false,
        tags: vec![],
        directive_invocations: vec![],
        specified_by_url: None,
      }
    })
  }

  fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
    I::parse(value).map(Self).map_err(|e| e.propagate())
  }

  fn to_value(&self) -> async_graphql::Value {
    self.0.to_value()
  }

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

impl<I: OutputType> OutputType for Id<I> {
  fn type_name() -> Cow<'static, str> {
    (*I::type_name())
      .replace("String", "ID")
      .replace("Bytes", "ID")
      .into()
  }

  fn qualified_type_name() -> String {
    format!("{}!", Self::type_name())
  }

  fn introspection_type_name(&self) -> Cow<'static, str> {
    Self::type_name()
  }

  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![],
        specified_by_url: None,
        directive_invocations: vec![],
      }
    })
  }

  async fn resolve(
    &self,
    ctx: &async_graphql::ContextSelectionSet<'_>,
    field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
  ) -> async_graphql::ServerResult<async_graphql::Value> {
    I::resolve(&self.0, ctx, field).await
  }
}

impl<I: PgHasArrayType> PgHasArrayType for Id<I> {
  fn array_type_info() -> sqlx::postgres::PgTypeInfo {
    I::array_type_info()
  }
}

impl<'q, I: sqlx::Encode<'q, D>, D: sqlx::Database> sqlx::Encode<'q, D> for Id<I> {
  fn encode_by_ref(
    &self,
    buf: &mut <D as sqlx::database::Database>::ArgumentBuffer<'q>,
  ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync + 'static>> {
    self.0.encode_by_ref(buf)
  }
}

impl<I: sqlx::Type<sqlx::Postgres>> sqlx::Type<sqlx::Postgres> for Id<I> {
  fn type_info() -> sqlx::postgres::PgTypeInfo {
    I::type_info()
  }

  fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
    I::compatible(ty)
  }
}

impl<'r, I: sqlx::Decode<'r, D>, D: sqlx::Database> sqlx::Decode<'r, D> for Id<I> {
  fn decode(
    value: <D as sqlx::database::Database>::ValueRef<'r>,
  ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
    I::decode(value).map(Self)
  }
}

impl<I: StableHash> StableHash for Id<I> {
  fn stable_hash<H: StableHasher>(&self, sequence_number: H::Addr, state: &mut H) {
    self.0.stable_hash(sequence_number, state)
  }
}

impl<I: Hashable> Hashable for Id<I> {
  fn variant() -> crate::poi::ValueVariant {
    I::variant()
  }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IdFilter<F>(F);

impl<F> IdFilter<F> {
  pub fn into_inner(self) -> F {
    self.0
  }
}

static ALLOWED_OPS: &[&str] = &["", "not", "lt", "lte", "gt", "gte", "in", "not_in"];

impl<F: Filter> Filter for IdFilter<F> {
  type Primitive = F::Primitive;

  type Primitives = F::Primitives;

  fn merge(&mut self, other: Self) {
    self.0.merge(other.0)
  }

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

  fn match_operator(
    name: &'static str,
    op: &'static str,
    registry: &mut async_graphql::registry::Registry,
  ) -> Option<async_graphql::registry::MetaInputValue>
  where
    Self::Primitive: async_graphql::InputType,
    Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
  {
    if ALLOWED_OPS.contains(&op) {
      F::match_id_operator(name, op, registry)
    } else {
      None
    }
  }

  fn match_id_operator(
    name: &'static str,
    op: &'static str,
    registry: &mut async_graphql::registry::Registry,
  ) -> Option<async_graphql::registry::MetaInputValue>
  where
    Self::Primitive: async_graphql::InputType,
    Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
  {
    Self::match_operator(name, op, registry)
  }

  fn parse<T: async_graphql::InputType>(
    field_name: &'static str,
    obj: &async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
  ) -> Result<Option<Self>, async_graphql::InputValueError<T>>
  where
    Self::Primitive: async_graphql::InputType,
    Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
  {
    F::parse(field_name, obj).map(|val| val.map(Self))
  }

  fn to_value(
    &self,
    field_name: &'static str,
    map: &mut async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
  ) where
    Self::Primitive: async_graphql::InputType,
    Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
  {
    F::to_value(&self.0, field_name, map)
  }

  fn type_name() -> &'static str {
    F::type_name()
  }

  fn pg_type() -> Option<sqlx::postgres::PgTypeInfo>
  where
    Self::Primitive: sqlx::Type<sqlx::Postgres>,
  {
    F::pg_type()
  }

  fn is_collection() -> bool {
    F::is_collection()
  }

  fn is_pg_array() -> bool
  where
    Self::Primitive: sqlx::Type<sqlx::Postgres>,
  {
    F::is_pg_array()
  }

  fn apply_filter<'q, QB: core::borrow::BorrowMut<sqlx::QueryBuilder<'q, sqlx::Postgres>>>(
    &'q self,
    field: &str,
    builder: QB,
  ) -> Result<(), async_graphql::Error>
  where
    Self::Primitive:
      for<'a> sqlx::Encode<'a, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send + Sync,
  {
    F::apply_filter(&self.0, field, builder)
  }
}

impl<I: ToGraphqlFilter> ToGraphqlFilter for Id<I> {
  type Filter = IdFilter<I::Filter>;

  fn to_graphql_filter(self) -> Self::Filter {
    IdFilter(self.0.to_graphql_filter())
  }
}

impl<I: ToDbFilter> ToDbFilter for Id<I> {
  type Filter = IdFilter<I::Filter>;

  fn to_db_filter(self) -> Self::Filter {
    IdFilter(self.0.to_db_filter())
  }
}