///|
/// Why a graph definition was refused when it was compiled. Every check here is
/// structural: the nodes and edges are known then, the runs are not.
pub(all) suberror Refused {
NoNodes
NoEntry
UnknownEntry(node~ : String)
DuplicateNode(node~ : String)
DanglingEdge(from~ : String, to~ : String)
Unreachable(node~ : String)
NoExit
}
///|
/// Why a run could not finish.
pub(all) suberror GraphError {
///
/// The run took `limit` supersteps without reaching an end.
StepLimit(limit~ : Int32)
///
/// A node raised, after its retries if it had any; `message` is the error text.
NodeFailed(node~ : String, message~ : String)
///
/// A node asked to stop and wait for an answer. `resume` continues the thread.
Interrupted(thread~ : String, value~ : Json)
///
/// `resume` was called on a thread with nothing to continue.
NotInterrupted(thread~ : String)
///
/// The thread has no checkpoint the saver knows about.
MissingThread(thread~ : String)
///
/// A node routed the run to a name the graph does not define. The compile-time
/// check cannot see the names a `Goto`, a `Send` or a branch picks, so the run
/// reports them the first time one is taken.
MissingNode(node~ : String)
///
/// A checkpoint could not be decoded back into the graph's state type.
Unreadable(reason~ : String)
///
/// A node cancelled the run, or the caller did before the next superstep.
Cancelled(thread~ : String)
///
/// A saver, a store, an executor or a stream callback raised. The engine keeps
/// going only where it can tell what went wrong, so this is where an outside
/// failure ends up.
Backend(reason~ : String)
}
///|
/// The text of an error. The default rendering of a sub-error drops its payload,
/// and the payload is the whole point of a failure report that crosses a process
/// boundary, so the fields are spelled out here.
fn error_text(error : Error) -> String {
match error {
Refused::NoNodes => "the graph has no nodes"
Refused::NoEntry => "the graph has no entry node"
Refused::UnknownEntry(node~) => "the entry names no node: " + node
Refused::DuplicateNode(node~) => "the node is defined twice: " + node
Refused::DanglingEdge(from~, to~) =>
"the edge " + from + " -> " + to + " names a node that is not defined"
Refused::Unreachable(node~) => "no path reaches the node: " + node
Refused::NoExit => "no node ends the graph"
GraphError::StepLimit(limit~) =>
"the run took " + limit.to_string() + " supersteps without ending"
GraphError::NodeFailed(node~, message~) =>
"node '" + node + "' failed: " + message
GraphError::Interrupted(thread~, value~) =>
"thread '" + thread + "' waits for an answer: " + value.stringify()
GraphError::NotInterrupted(thread~) =>
"thread '" + thread + "' has nothing to continue"
GraphError::MissingThread(thread~) => "no thread named '" + thread + "'"
GraphError::MissingNode(node~) => "no node named '" + node + "'"
GraphError::Unreadable(reason~) =>
"a checkpoint could not be read: " + reason
GraphError::Cancelled(thread~) => "thread '" + thread + "' was cancelled"
GraphError::Backend(reason~) => reason
Failure::Failure(message) => message
_ => error.to_string()
}
}