graphrecords-query 0.5.0

High-performance graph-based data records
Documentation
pub mod aggregation;
pub mod argument;
pub mod arithmetic;
pub mod comparison;
pub mod conversion;
#[cfg(feature = "dynamic")]
pub mod dispatch;
pub mod execution;
pub mod grouping;
pub mod groups;
pub mod index;
pub mod numeric;
pub mod ordering;
pub mod string;
pub mod structure;

use crate::{IndexDomain, OwnedIndex};
use graphrecords_core::errors::GraphRecordError;
use std::{
    any::{Any, TypeId},
    error::Error,
    fmt::{self, Display, Formatter},
    hash::{Hash, Hasher},
    sync::Arc,
};

pub type QueryResult<T> = Result<T, Box<Failure>>;

pub trait Diagnostic: Error + Send + Sync + 'static {
    fn name() -> &'static str
    where
        Self: Sized;

    fn help(&self) -> Option<String> {
        None
    }
}

#[derive(Clone, Copy, Debug)]
pub struct FailureKind {
    identifier: TypeId,
    name: &'static str,
}

impl FailureKind {
    #[must_use]
    pub fn of<D: Diagnostic>() -> Self {
        Self {
            identifier: TypeId::of::<D>(),
            name: D::name(),
        }
    }

    #[must_use]
    pub fn is<D: Diagnostic>(&self) -> bool {
        self.identifier == TypeId::of::<D>()
    }

    #[must_use]
    pub const fn name(&self) -> &'static str {
        self.name
    }
}

impl Display for FailureKind {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.name)
    }
}

impl PartialEq for FailureKind {
    fn eq(&self, other: &Self) -> bool {
        self.identifier == other.identifier
    }
}

impl Eq for FailureKind {}

impl Hash for FailureKind {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.identifier.hash(state);
    }
}

pub trait ErrorGroup: 'static {
    fn name() -> &'static str
    where
        Self: Sized;

    fn contains(kind: &FailureKind) -> bool;
}

#[derive(Clone, Debug)]
pub struct Failure {
    operation: &'static str,
    element: Option<Arc<dyn OwnedIndex>>,
    kind: FailureKind,
    cause: Arc<dyn Diagnostic>,
}

impl Failure {
    pub fn new<D: Diagnostic>(operation: &'static str, cause: D) -> Box<Self> {
        Box::new(Self {
            operation,
            element: None,
            kind: FailureKind::of::<D>(),
            cause: Arc::new(cause),
        })
    }

    pub fn new_at<I: IndexDomain, D: Diagnostic>(
        operation: &'static str,
        cause: D,
        index: &I::Index<'_>,
    ) -> Box<Self> {
        Box::new(Self {
            operation,
            element: Some(Arc::new(I::to_owned(index))),
            kind: FailureKind::of::<D>(),
            cause: Arc::new(cause),
        })
    }

    #[must_use]
    pub fn at<I: IndexDomain>(mut self: Box<Self>, index: &I::Index<'_>) -> Box<Self> {
        self.element = Some(Arc::new(I::to_owned(index)));
        self
    }

    #[must_use]
    pub const fn operation(&self) -> &'static str {
        self.operation
    }

    #[must_use]
    pub fn element(&self) -> Option<&dyn OwnedIndex> {
        self.element.as_deref()
    }

    #[must_use]
    pub fn downcast_element<T: OwnedIndex>(&self) -> Option<&T> {
        let element: &dyn Any = self.element.as_deref()?;

        element.downcast_ref()
    }

    #[must_use]
    pub fn cause(&self) -> &dyn Diagnostic {
        self.cause.as_ref()
    }

    #[must_use]
    pub const fn kind(&self) -> FailureKind {
        self.kind
    }

    #[must_use]
    pub fn is_kind<D: Diagnostic>(&self) -> bool {
        self.kind.is::<D>()
    }

    #[must_use]
    pub fn help(&self) -> Option<String> {
        self.cause.help()
    }

    #[must_use]
    pub fn downcast_cause<T: Error + 'static>(&self) -> Option<&T> {
        let mut current: &(dyn Error + 'static) = self.cause.as_ref();

        loop {
            if let Some(cause) = current.downcast_ref() {
                return Some(cause);
            }

            current = current.source()?;
        }
    }

    #[must_use]
    pub fn has_cause<T: Error + 'static>(&self) -> bool {
        self.downcast_cause::<T>().is_some()
    }
}

impl Display for Failure {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self.element() {
            Some(element) => write!(
                formatter,
                "operation `{}` failed at element `{element}`: {}",
                self.operation, self.cause,
            )?,
            None => write!(
                formatter,
                "operation `{}` failed: {}",
                self.operation, self.cause,
            )?,
        }

        if let Some(help) = self.help() {
            write!(formatter, "\nhelp: {help}")?;
        }

        Ok(())
    }
}

impl Error for Failure {}

#[derive(Debug)]
pub struct External<E: Error + Send + Sync + 'static>(E);

impl<E: Error + Send + Sync + 'static> External<E> {
    #[must_use]
    pub const fn new(error: E) -> Self {
        Self(error)
    }

    #[must_use]
    pub const fn error(&self) -> &E {
        &self.0
    }
}

impl<E: Error + Send + Sync + 'static> Display for External<E> {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, formatter)
    }
}

impl<E: Error + Send + Sync + 'static> Error for External<E> {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.0)
    }
}

impl<E: Error + Send + Sync + 'static> Diagnostic for External<E> {
    fn name() -> &'static str {
        "External"
    }
}

impl Diagnostic for GraphRecordError {
    fn name() -> &'static str {
        "GraphRecordError"
    }

    fn help(&self) -> Option<String> {
        match self {
            Self::IncompatibleValueOperands { .. } | Self::IncompatibleAttributeOperands { .. } => {
                Some(
                    "narrow the values down first using is_string(), is_int(), is_float(), is_bool(), is_datetime() or is_duration()"
                        .to_string(),
                )
            }
            Self::GroupNotFound { .. } => {
                Some("add the group first or check `groups()` before querying".to_string())
            }
            _ => None,
        }
    }
}