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()
}
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())
}
}