use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
use crate::asset::PendingAsset;
use crate::key::FullCacheKey;
#[derive(Debug, Clone)]
pub enum QueryError {
Suspend {
asset: PendingAsset,
},
Cycle {
path: Vec<FullCacheKey>,
},
Cancelled,
DependenciesRemoved {
missing_keys: Vec<FullCacheKey>,
},
InconsistentAssetResolution,
UserError(Arc<anyhow::Error>),
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
QueryError::Suspend { asset } => {
write!(f, "query suspended: waiting for {}", asset.debug_repr())
}
QueryError::Cycle { path } => {
let path_str: Vec<String> = path.iter().map(|k| k.debug_repr()).collect();
write!(f, "dependency cycle detected: {}", path_str.join(" -> "))
}
QueryError::Cancelled => write!(f, "query cancelled"),
QueryError::DependenciesRemoved { missing_keys } => {
write!(
f,
"dependencies removed during execution: {:?}",
missing_keys
)
}
QueryError::InconsistentAssetResolution => {
write!(
f,
"asset resolution occurred during query execution, causing inconsistent snapshot"
)
}
QueryError::UserError(e) => write!(f, "user error: {}", e),
}
}
}
impl<T: Into<anyhow::Error>> From<T> for QueryError {
fn from(err: T) -> Self {
QueryError::UserError(Arc::new(err.into()))
}
}
impl QueryError {
pub fn user_error(&self) -> Option<&Arc<anyhow::Error>> {
match self {
QueryError::UserError(e) => Some(e),
_ => None,
}
}
pub fn downcast_ref<E: std::error::Error + Send + Sync + 'static>(&self) -> Option<&E> {
self.user_error().and_then(|e| e.downcast_ref::<E>())
}
pub fn is<E: std::error::Error + Send + Sync + 'static>(&self) -> bool {
self.downcast_ref::<E>().is_some()
}
}
#[derive(Clone)]
pub struct TypedErr<E> {
arc: Arc<anyhow::Error>,
_marker: PhantomData<E>,
}
impl<E: std::error::Error + Send + Sync + 'static> TypedErr<E> {
fn new(arc: Arc<anyhow::Error>) -> Option<Self> {
if arc.downcast_ref::<E>().is_some() {
Some(Self {
arc,
_marker: PhantomData,
})
} else {
None
}
}
pub fn get(&self) -> &E {
self.arc.downcast_ref::<E>().unwrap()
}
}
impl<E> From<TypedErr<E>> for QueryError {
fn from(err: TypedErr<E>) -> Self {
QueryError::UserError(err.arc)
}
}
impl<E: std::error::Error + Send + Sync + 'static> Deref for TypedErr<E> {
type Target = E;
fn deref(&self) -> &E {
self.get()
}
}
impl<E: std::error::Error + Send + Sync + 'static> fmt::Debug for TypedErr<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.get(), f)
}
}
impl<E: std::error::Error + Send + Sync + 'static> fmt::Display for TypedErr<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.get(), f)
}
}
pub trait QueryResultExt<T> {
fn downcast_err<E: std::error::Error + Send + Sync + 'static>(
self,
) -> Result<Result<Arc<T>, TypedErr<E>>, QueryError>;
}
impl<T> QueryResultExt<T> for Result<Arc<T>, QueryError> {
fn downcast_err<E: std::error::Error + Send + Sync + 'static>(
self,
) -> Result<Result<Arc<T>, TypedErr<E>>, QueryError> {
match self {
Ok(value) => Ok(Ok(value)),
Err(QueryError::UserError(arc)) => match TypedErr::new(arc.clone()) {
Some(typed) => Ok(Err(typed)),
None => Err(QueryError::UserError(arc)),
},
Err(other) => Err(other),
}
}
}