hive_router_plan_executor/execution/
error.rs

1use strum::IntoStaticStr;
2
3use crate::{
4    headers::errors::HeaderRuleRuntimeError,
5    projection::error::ProjectionError,
6    response::graphql_error::{GraphQLError, GraphQLErrorExtensions},
7};
8
9#[derive(thiserror::Error, Debug, Clone, IntoStaticStr)]
10pub enum PlanExecutionErrorKind {
11    #[error("Projection faiure: {0}")]
12    #[strum(serialize = "PROJECTION_FAILURE")]
13    ProjectionFailure(#[from] ProjectionError),
14
15    #[error(transparent)]
16    #[strum(serialize = "HEADER_PROPAGATION_FAILURE")]
17    HeaderPropagation(#[from] HeaderRuleRuntimeError),
18}
19
20/// The central error type for all query plan execution failures.
21///
22/// This struct combines a specific `PlanExecutionErrorKind` with a
23/// `PlanExecutionErrorContext` that holds shared, dynamic information
24/// like the subgraph name or affected GraphQL path.
25#[derive(thiserror::Error, Debug, Clone)]
26#[error("{kind}")]
27pub struct PlanExecutionError {
28    #[source]
29    kind: PlanExecutionErrorKind,
30    context: PlanExecutionErrorContext,
31}
32
33#[derive(Debug, Clone)]
34pub struct PlanExecutionErrorContext {
35    subgraph_name: Option<String>,
36    affected_path: Option<String>,
37}
38
39pub struct LazyPlanContext<SN, AP> {
40    pub subgraph_name: SN,
41    pub affected_path: AP,
42}
43
44impl PlanExecutionError {
45    pub(crate) fn new<SN, AP>(
46        kind: PlanExecutionErrorKind,
47        lazy_context: LazyPlanContext<SN, AP>,
48    ) -> Self
49    where
50        SN: FnOnce() -> Option<String>,
51        AP: FnOnce() -> Option<String>,
52    {
53        Self {
54            kind,
55            context: PlanExecutionErrorContext {
56                subgraph_name: (lazy_context.subgraph_name)(),
57                affected_path: (lazy_context.affected_path)(),
58            },
59        }
60    }
61
62    pub fn error_code(&self) -> &'static str {
63        (&self.kind).into()
64    }
65
66    pub fn subgraph_name(&self) -> &Option<String> {
67        &self.context.subgraph_name
68    }
69
70    pub fn affected_path(&self) -> &Option<String> {
71        &self.context.affected_path
72    }
73}
74
75impl From<PlanExecutionError> for GraphQLError {
76    fn from(val: PlanExecutionError) -> Self {
77        let message = val.to_string();
78        GraphQLError {
79            extensions: GraphQLErrorExtensions {
80                code: Some(val.error_code().into()),
81                service_name: val.context.subgraph_name,
82                affected_path: val.context.affected_path,
83                extensions: Default::default(),
84            },
85            message,
86            locations: None,
87            path: None,
88        }
89    }
90}
91
92/// An extension trait for `Result` types that can be converted into a `PlanExecutionError`.
93///
94/// This trait provides a lazy, performant way to add contextual information to
95/// an error, only performing work (like cloning strings) if the `Result` is an `Err`.
96pub trait IntoPlanExecutionError<T> {
97    fn with_plan_context<SN, AP>(
98        self,
99        context: LazyPlanContext<SN, AP>,
100    ) -> Result<T, PlanExecutionError>
101    where
102        SN: FnOnce() -> Option<String>,
103        AP: FnOnce() -> Option<String>;
104}
105
106impl<T> IntoPlanExecutionError<T> for Result<T, ProjectionError> {
107    fn with_plan_context<SN, AP>(
108        self,
109        context: LazyPlanContext<SN, AP>,
110    ) -> Result<T, PlanExecutionError>
111    where
112        SN: FnOnce() -> Option<String>,
113        AP: FnOnce() -> Option<String>,
114    {
115        self.map_err(|source| {
116            let kind = PlanExecutionErrorKind::ProjectionFailure(source);
117            PlanExecutionError::new(kind, context)
118        })
119    }
120}
121
122impl<T> IntoPlanExecutionError<T> for Result<T, HeaderRuleRuntimeError> {
123    fn with_plan_context<SN, AP>(
124        self,
125        context: LazyPlanContext<SN, AP>,
126    ) -> Result<T, PlanExecutionError>
127    where
128        SN: FnOnce() -> Option<String>,
129        AP: FnOnce() -> Option<String>,
130    {
131        self.map_err(|source| {
132            let kind = PlanExecutionErrorKind::HeaderPropagation(source);
133            PlanExecutionError::new(kind, context)
134        })
135    }
136}