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 enum GraphError {
109 UnknownNode(NodeId),
111 UnknownScope(ScopeId),
113 DuplicateDependency(NodeId),
115 SelfDependency(NodeId),
117 NodeAlreadyAttached(NodeId),
119 ScopeAlreadyClosed(ScopeId),
121 ScopeClosed(ScopeId),
123 NestedTransaction,
125 TransactionClosed(TransactionId),
127 NotInputNode(NodeId),
129 NotDerivedNode(NodeId),
131 NotCollectionNode(NodeId),
133 WrongInputType(NodeId),
135 WrongDerivedType(NodeId),
137 WrongCollectionType(NodeId),
139 UnknownOutput(OutputKey),
141 OutputFailed(OutputKey, OutputError),
143 PlanFailed(ScopeId, PlanError),
145 ResourceScopeMismatch(ScopeId),
147 ResourceNotOwned {
149 key: ResourceKey,
151 scope: ScopeId,
153 command_kind: ResourceCommandKind,
155 },
156 CycleDetected(NodeId),
158 CollectionDependencyNotAllowed(NodeId),
160 DeriveFailed(NodeId, DeriveError),
162 CollectionFailed(NodeId, DeriveError),
164 FullRecomputeMismatch(NodeId),
166 FullRecomputeResourceMismatch(FullRecomputeResourceMismatch),
168 FullRecomputeOutputMismatch(FullRecomputeOutputMismatch),
170}
171
172impl fmt::Display for GraphError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 match self {
175 Self::UnknownNode(id) => write!(f, "unknown node: {id:?}"),
176 Self::UnknownScope(id) => write!(f, "unknown scope: {id:?}"),
177 Self::DuplicateDependency(id) => write!(f, "duplicate dependency: {id:?}"),
178 Self::SelfDependency(id) => write!(f, "self dependency: {id:?}"),
179 Self::NodeAlreadyAttached(id) => write!(f, "node already attached: {id:?}"),
180 Self::ScopeAlreadyClosed(id) => write!(f, "scope already closed: {id:?}"),
181 Self::ScopeClosed(id) => write!(f, "scope already closed: {id:?}"),
182 Self::NestedTransaction => write!(f, "a transaction is already open"),
183 Self::TransactionClosed(id) => write!(f, "transaction already closed: {id:?}"),
184 Self::NotInputNode(id) => write!(f, "node is not an input: {id:?}"),
185 Self::NotDerivedNode(id) => write!(f, "node is not derived: {id:?}"),
186 Self::NotCollectionNode(id) => write!(f, "node is not a collection: {id:?}"),
187 Self::WrongInputType(id) => write!(f, "wrong input value type for node: {id:?}"),
188 Self::WrongDerivedType(id) => write!(f, "wrong derived value type for node: {id:?}"),
189 Self::WrongCollectionType(id) => {
190 write!(f, "wrong collection value type for node: {id:?}")
191 }
192 Self::UnknownOutput(key) => write!(f, "unknown output: {key:?}"),
193 Self::OutputFailed(key, error) => write!(f, "output failed for {key:?}: {error:?}"),
194 Self::PlanFailed(scope, error) => {
195 write!(f, "resource planner failed for {scope:?}: {error:?}")
196 }
197 Self::ResourceScopeMismatch(id) => write!(f, "resource scope mismatch: {id:?}"),
198 Self::ResourceNotOwned {
199 key,
200 scope,
201 command_kind,
202 } => write!(
203 f,
204 "resource is not owned: key {key:?}, scope {scope:?}, command {command_kind:?}"
205 ),
206 Self::CycleDetected(id) => write!(f, "dependency cycle detected at node: {id:?}"),
207 Self::CollectionDependencyNotAllowed(id) => {
208 write!(
209 f,
210 "collection dependency is not allowed for derived node: {id:?}"
211 )
212 }
213 Self::DeriveFailed(id, error) => write!(f, "derive failed for {id:?}: {error:?}"),
214 Self::CollectionFailed(id, error) => {
215 write!(f, "collection failed for {id:?}: {error:?}")
216 }
217 Self::FullRecomputeMismatch(id) => {
218 write!(f, "full recompute mismatch for node: {id:?}")
219 }
220 Self::FullRecomputeResourceMismatch(mismatch) => write!(
221 f,
222 "full recompute resource mismatch for key: {:?}",
223 mismatch.key
224 ),
225 Self::FullRecomputeOutputMismatch(mismatch) => write!(
226 f,
227 "full recompute output mismatch for key: {:?}",
228 mismatch.key
229 ),
230 }
231 }
232}
233
234impl GraphError {
235 pub const fn category(&self) -> ErrorCategory {
237 match self {
238 Self::DeriveFailed(_, _) | Self::CollectionFailed(_, _) => ErrorCategory::DeriveError,
239 Self::PlanFailed(_, _) => ErrorCategory::PlanError,
240 Self::OutputFailed(_, _) => ErrorCategory::OutputError,
241 _ => ErrorCategory::ProgrammerError,
242 }
243 }
244
245 pub const fn audit_event(&self) -> ErrorAuditEvent {
247 ErrorAuditEvent {
248 category: self.category(),
249 target: match self {
250 Self::UnknownNode(node)
251 | Self::DuplicateDependency(node)
252 | Self::SelfDependency(node)
253 | Self::NodeAlreadyAttached(node)
254 | Self::NotInputNode(node)
255 | Self::NotDerivedNode(node)
256 | Self::NotCollectionNode(node)
257 | Self::WrongInputType(node)
258 | Self::WrongDerivedType(node)
259 | Self::WrongCollectionType(node)
260 | Self::CycleDetected(node)
261 | Self::CollectionDependencyNotAllowed(node)
262 | Self::DeriveFailed(node, _)
263 | Self::CollectionFailed(node, _)
264 | Self::FullRecomputeMismatch(node) => ErrorTarget::Node(*node),
265 Self::UnknownScope(scope)
266 | Self::ScopeAlreadyClosed(scope)
267 | Self::ScopeClosed(scope)
268 | Self::ResourceScopeMismatch(scope)
269 | Self::PlanFailed(scope, _) => ErrorTarget::Scope(*scope),
270 Self::ResourceNotOwned { scope, .. } => ErrorTarget::Scope(*scope),
271 Self::TransactionClosed(transaction) => ErrorTarget::Transaction(*transaction),
272 Self::UnknownOutput(output) | Self::OutputFailed(output, _) => {
273 ErrorTarget::Output(*output)
274 }
275 Self::FullRecomputeOutputMismatch(mismatch) => ErrorTarget::Output(mismatch.key),
276 Self::NestedTransaction | Self::FullRecomputeResourceMismatch(_) => {
277 ErrorTarget::Graph
278 }
279 },
280 }
281 }
282}
283
284impl std::error::Error for GraphError {}