use strum::IntoStaticStr;
use crate::executor::{
executors::error::SubgraphExecutorError, headers::errors::HeaderRuleRuntimeError,
projection::error::ProjectionError, response::graphql_error::GraphQLError,
};
#[derive(thiserror::Error, Debug, IntoStaticStr)]
pub enum PlanExecutionErrorKind {
#[error("Projection failure: {0}")]
#[strum(serialize = "PROJECTION_FAILURE")]
ProjectionFailure(#[from] ProjectionError),
#[error(transparent)]
#[strum(serialize = "HEADER_PROPAGATION_FAILURE")]
HeaderPropagation(#[from] HeaderRuleRuntimeError),
#[error(transparent)]
#[strum(serialize = "SUBGRAPH_EXECUTION_FAILURE")]
SubgraphExecutor(#[from] SubgraphExecutorError),
}
#[derive(thiserror::Error, Debug)]
#[error("{kind}")]
pub struct PlanExecutionError {
#[source]
kind: PlanExecutionErrorKind,
context: PlanExecutionErrorContext,
}
#[derive(Debug, Clone)]
pub struct PlanExecutionErrorContext {
subgraph_name: Option<String>,
affected_path: Option<String>,
}
pub struct LazyPlanContext<SN, AP> {
pub subgraph_name: SN,
pub affected_path: AP,
}
impl PlanExecutionError {
pub(crate) fn new<SN, AP>(
kind: PlanExecutionErrorKind,
lazy_context: LazyPlanContext<SN, AP>,
) -> Self
where
SN: FnOnce() -> Option<String>,
AP: FnOnce() -> Option<String>,
{
Self {
kind,
context: PlanExecutionErrorContext {
subgraph_name: (lazy_context.subgraph_name)(),
affected_path: (lazy_context.affected_path)(),
},
}
}
pub fn error_code(&self) -> &'static str {
if let PlanExecutionErrorKind::SubgraphExecutor(subgraph_error) = &self.kind {
return subgraph_error.error_code();
}
(&self.kind).into()
}
pub fn subgraph_name(&self) -> &Option<String> {
&self.context.subgraph_name
}
pub fn affected_path(&self) -> &Option<String> {
&self.context.affected_path
}
pub fn subgraph_response_headers(&self) -> Option<&http::HeaderMap> {
match &self.kind {
PlanExecutionErrorKind::SubgraphExecutor(subgraph_error) => {
subgraph_error.response_headers()
}
_ => None,
}
}
}
impl From<&PlanExecutionError> for GraphQLError {
fn from(val: &PlanExecutionError) -> Self {
let mut error = GraphQLError::from_message_and_code(val.to_string(), val.error_code());
if let Some(subgraph_name) = &val.context.subgraph_name {
error = error.add_subgraph_name(subgraph_name);
}
if let Some(affected_path) = &val.context.affected_path {
error = error.add_affected_path(affected_path);
}
error
}
}
pub trait IntoPlanExecutionError<T> {
fn with_plan_context<SN, AP>(
self,
context: LazyPlanContext<SN, AP>,
) -> Result<T, PlanExecutionError>
where
SN: FnOnce() -> Option<String>,
AP: FnOnce() -> Option<String>;
}
impl<T> IntoPlanExecutionError<T> for Result<T, ProjectionError> {
fn with_plan_context<SN, AP>(
self,
context: LazyPlanContext<SN, AP>,
) -> Result<T, PlanExecutionError>
where
SN: FnOnce() -> Option<String>,
AP: FnOnce() -> Option<String>,
{
self.map_err(|source| {
let kind = PlanExecutionErrorKind::ProjectionFailure(source);
PlanExecutionError::new(kind, context)
})
}
}
impl<T> IntoPlanExecutionError<T> for Result<T, HeaderRuleRuntimeError> {
fn with_plan_context<SN, AP>(
self,
context: LazyPlanContext<SN, AP>,
) -> Result<T, PlanExecutionError>
where
SN: FnOnce() -> Option<String>,
AP: FnOnce() -> Option<String>,
{
self.map_err(|source| {
let kind = PlanExecutionErrorKind::HeaderPropagation(source);
PlanExecutionError::new(kind, context)
})
}
}
impl<T> IntoPlanExecutionError<T> for Result<T, SubgraphExecutorError> {
fn with_plan_context<SN, AP>(
self,
context: LazyPlanContext<SN, AP>,
) -> Result<T, PlanExecutionError>
where
SN: FnOnce() -> Option<String>,
AP: FnOnce() -> Option<String>,
{
self.map_err(|source| {
let kind = PlanExecutionErrorKind::SubgraphExecutor(source);
PlanExecutionError::new(kind, context)
})
}
}