1use crate::{
2 DeriveError, NodeId, OutputKey, ResourceCommandKind, ResourceKey, ScopeId, TransactionId,
3};
4use core::fmt;
5
6pub type GraphResult<T> = Result<T, GraphError>;
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11pub enum ErrorCategory {
12 ProgrammerError,
14 DeriveError,
16 PlanError,
18 OutputError,
20 HostResourceStatus,
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct ErrorAuditEvent {
27 pub category: ErrorCategory,
29 pub target: ErrorTarget,
31}
32
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
35pub enum ErrorTarget {
36 Graph,
38 Node(NodeId),
40 Scope(ScopeId),
42 Transaction(TransactionId),
44 Output(OutputKey),
46}
47
48#[derive(Clone, Debug, Eq, PartialEq)]
50pub enum PlanError {
51 Message(String),
53}
54
55impl PlanError {
56 pub fn message(message: impl Into<String>) -> Self {
58 Self::Message(message.into())
59 }
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
64pub enum OutputError {
65 Read(DeriveError),
67 Message(String),
69}
70
71impl OutputError {
72 pub fn message(message: impl Into<String>) -> Self {
74 Self::Message(message.into())
75 }
76}
77
78impl From<DeriveError> for OutputError {
79 fn from(error: DeriveError) -> Self {
80 Self::Read(error)
81 }
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
86pub struct FullRecomputeResourceMismatch {
87 pub key: ResourceKey,
89 pub incremental_owners: Vec<ScopeId>,
91 pub recomputed_owners: Vec<ScopeId>,
93}
94
95#[derive(Copy, Clone, Debug, Eq, PartialEq)]
97pub struct FullRecomputeOutputMismatch {
98 pub key: OutputKey,
100 pub incremental_present: bool,
102 pub recomputed_present: bool,
104}
105
106#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct ResourcePayloadConflict {
109 pub key: ResourceKey,
111 pub joining_scope: ScopeId,
113 pub existing_owners: Vec<ScopeId>,
115}
116
117#[derive(Clone, Debug, Eq, PartialEq)]
119pub enum GraphError {
120 UnknownNode(NodeId),
122 UnknownScope(ScopeId),
124 DuplicateDependency(NodeId),
126 SelfDependency(NodeId),
128 NodeAlreadyAttached(NodeId),
130 ScopeAlreadyClosed(ScopeId),
132 ScopeClosed(ScopeId),
134 NestedTransaction,
136 TransactionClosed(TransactionId),
138 NotInputNode(NodeId),
140 NotDerivedNode(NodeId),
142 NotCollectionNode(NodeId),
144 WrongInputType(NodeId),
146 WrongDerivedType(NodeId),
148 WrongCollectionType(NodeId),
150 UnknownOutput(OutputKey),
152 OutputFailed(OutputKey, OutputError),
154 PlanFailed(ScopeId, PlanError),
156 ResourceScopeMismatch(ScopeId),
158 ResourceNotOwned {
160 key: ResourceKey,
162 scope: ScopeId,
164 command_kind: ResourceCommandKind,
166 },
167 ResourcePayloadConflict(ResourcePayloadConflict),
169 CycleDetected(NodeId),
171 CollectionDependencyNotAllowed(NodeId),
173 DeriveFailed(NodeId, DeriveError),
175 CollectionFailed(NodeId, DeriveError),
177 FullRecomputeMismatch(NodeId),
179 FullRecomputeResourceMismatch(FullRecomputeResourceMismatch),
181 FullRecomputeOutputMismatch(FullRecomputeOutputMismatch),
183}
184
185impl fmt::Display for GraphError {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self {
188 Self::UnknownNode(id) => write!(f, "unknown node: {id:?}"),
189 Self::UnknownScope(id) => write!(f, "unknown scope: {id:?}"),
190 Self::DuplicateDependency(id) => write!(f, "duplicate dependency: {id:?}"),
191 Self::SelfDependency(id) => write!(f, "self dependency: {id:?}"),
192 Self::NodeAlreadyAttached(id) => write!(f, "node already attached: {id:?}"),
193 Self::ScopeAlreadyClosed(id) => write!(f, "scope already closed: {id:?}"),
194 Self::ScopeClosed(id) => write!(f, "scope already closed: {id:?}"),
195 Self::NestedTransaction => write!(f, "a transaction is already open"),
196 Self::TransactionClosed(id) => write!(f, "transaction already closed: {id:?}"),
197 Self::NotInputNode(id) => write!(f, "node is not an input: {id:?}"),
198 Self::NotDerivedNode(id) => write!(f, "node is not derived: {id:?}"),
199 Self::NotCollectionNode(id) => write!(f, "node is not a collection: {id:?}"),
200 Self::WrongInputType(id) => write!(f, "wrong input value type for node: {id:?}"),
201 Self::WrongDerivedType(id) => write!(f, "wrong derived value type for node: {id:?}"),
202 Self::WrongCollectionType(id) => {
203 write!(f, "wrong collection value type for node: {id:?}")
204 }
205 Self::UnknownOutput(key) => write!(f, "unknown output: {key:?}"),
206 Self::OutputFailed(key, error) => write!(f, "output failed for {key:?}: {error:?}"),
207 Self::PlanFailed(scope, error) => {
208 write!(f, "resource planner failed for {scope:?}: {error:?}")
209 }
210 Self::ResourceScopeMismatch(id) => write!(f, "resource scope mismatch: {id:?}"),
211 Self::ResourceNotOwned {
212 key,
213 scope,
214 command_kind,
215 } => write!(
216 f,
217 "resource is not owned: key {key:?}, scope {scope:?}, command {command_kind:?}"
218 ),
219 Self::ResourcePayloadConflict(conflict) => write!(
220 f,
221 "resource payload conflict: key {:?}, joining scope {:?}, existing owners {:?}",
222 conflict.key, conflict.joining_scope, conflict.existing_owners
223 ),
224 Self::CycleDetected(id) => write!(f, "dependency cycle detected at node: {id:?}"),
225 Self::CollectionDependencyNotAllowed(id) => {
226 write!(
227 f,
228 "collection dependency is not allowed for derived node: {id:?}"
229 )
230 }
231 Self::DeriveFailed(id, error) => write!(f, "derive failed for {id:?}: {error:?}"),
232 Self::CollectionFailed(id, error) => {
233 write!(f, "collection failed for {id:?}: {error:?}")
234 }
235 Self::FullRecomputeMismatch(id) => {
236 write!(f, "full recompute mismatch for node: {id:?}")
237 }
238 Self::FullRecomputeResourceMismatch(mismatch) => write!(
239 f,
240 "full recompute resource mismatch for key: {:?}",
241 mismatch.key
242 ),
243 Self::FullRecomputeOutputMismatch(mismatch) => write!(
244 f,
245 "full recompute output mismatch for key: {:?}",
246 mismatch.key
247 ),
248 }
249 }
250}
251
252impl GraphError {
253 pub const fn category(&self) -> ErrorCategory {
255 match self {
256 Self::DeriveFailed(_, _) | Self::CollectionFailed(_, _) => ErrorCategory::DeriveError,
257 Self::PlanFailed(_, _) => ErrorCategory::PlanError,
258 Self::OutputFailed(_, _) => ErrorCategory::OutputError,
259 _ => ErrorCategory::ProgrammerError,
260 }
261 }
262
263 pub const fn audit_event(&self) -> ErrorAuditEvent {
265 ErrorAuditEvent {
266 category: self.category(),
267 target: match self {
268 Self::UnknownNode(node)
269 | Self::DuplicateDependency(node)
270 | Self::SelfDependency(node)
271 | Self::NodeAlreadyAttached(node)
272 | Self::NotInputNode(node)
273 | Self::NotDerivedNode(node)
274 | Self::NotCollectionNode(node)
275 | Self::WrongInputType(node)
276 | Self::WrongDerivedType(node)
277 | Self::WrongCollectionType(node)
278 | Self::CycleDetected(node)
279 | Self::CollectionDependencyNotAllowed(node)
280 | Self::DeriveFailed(node, _)
281 | Self::CollectionFailed(node, _)
282 | Self::FullRecomputeMismatch(node) => ErrorTarget::Node(*node),
283 Self::UnknownScope(scope)
284 | Self::ScopeAlreadyClosed(scope)
285 | Self::ScopeClosed(scope)
286 | Self::ResourceScopeMismatch(scope)
287 | Self::PlanFailed(scope, _) => ErrorTarget::Scope(*scope),
288 Self::ResourceNotOwned { scope, .. } => ErrorTarget::Scope(*scope),
289 Self::ResourcePayloadConflict(conflict) => {
290 ErrorTarget::Scope(conflict.joining_scope)
291 }
292 Self::TransactionClosed(transaction) => ErrorTarget::Transaction(*transaction),
293 Self::UnknownOutput(output) | Self::OutputFailed(output, _) => {
294 ErrorTarget::Output(*output)
295 }
296 Self::FullRecomputeOutputMismatch(mismatch) => ErrorTarget::Output(mismatch.key),
297 Self::NestedTransaction | Self::FullRecomputeResourceMismatch(_) => {
298 ErrorTarget::Graph
299 }
300 },
301 }
302 }
303}
304
305impl std::error::Error for GraphError {}