Skip to content

Commit

Permalink
Add methods for inspecting Saga DAG (#67)
Browse files Browse the repository at this point in the history
* Add methods for inspecting Saga DAG

* Add basic test

* fmt

* Leave the lockfile alone

* Use iterator

* Make clippy happy

* Do not expose InternalNode
  • Loading branch information
smklein authored Oct 30, 2022
1 parent 53ee324 commit 6661416
Showing 1 changed file with 62 additions and 1 deletion.
63 changes: 62 additions & 1 deletion src/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl Node {
/// the execution state of the saga here. That continues to reside in saga log
/// consisting of `SagaNodeEvent`s.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum InternalNode {
pub(crate) enum InternalNode {
Start { params: Arc<serde_json::Value> },
End,
Action { name: NodeName, label: String, action_name: ActionName },
Expand Down Expand Up @@ -330,6 +330,19 @@ impl InternalNode {
}
}

/// A named, user-visible node in a saga graph.
pub struct NodeEntry<'a>(&'a InternalNode);

impl<'a> NodeEntry<'a> {
pub fn name(&self) -> &NodeName {
self.0.node_name().unwrap()
}

pub fn label(&self) -> String {
self.0.label()
}
}

/// A [`Dag`] plus saga input parameters that together can be used to execute a
/// saga
///
Expand All @@ -352,6 +365,31 @@ pub struct SagaDag {
pub(crate) end_node: NodeIndex,
}

/// An [`Iterator`] over all named nodes in the DAG.
pub struct SagaDagIterator<'a> {
dag: &'a SagaDag,
index: NodeIndex,
}

impl<'a> Iterator for SagaDagIterator<'a> {
type Item = NodeEntry<'a>;

fn next(&mut self) -> Option<Self::Item> {
while let Some(node) = self.dag.get(self.index) {
self.index = NodeIndex::new(self.index.index() + 1);
match node {
InternalNode::Action { .. } => return Some(NodeEntry(node)),
InternalNode::Constant { .. } => return Some(NodeEntry(node)),
InternalNode::SubsagaEnd { .. } => {
return Some(NodeEntry(node))
}
_ => (),
}
}
None
}
}

impl SagaDag {
/// Make a [`SagaDag`] from the given DAG and input parameters
pub fn new(dagfrag: Dag, params: serde_json::Value) -> SagaDag {
Expand Down Expand Up @@ -402,6 +440,11 @@ impl SagaDag {
.ok_or_else(|| anyhow!("saga has no node named \"{}\"", name))
}

/// Returns an iterator over all named nodes in the saga DAG.
pub fn get_nodes(&self) -> SagaDagIterator<'_> {
SagaDagIterator { dag: self, index: NodeIndex::new(0) }
}

/// Returns an object that can be used to print a graphviz-format
/// representation of the underlying DAG
pub fn dot(&self) -> DagDot<'_> {
Expand Down Expand Up @@ -828,6 +871,24 @@ mod test {
use super::NodeName;
use super::SagaName;

#[test]
fn test_saga_names_and_label() {
let mut builder = DagBuilder::new(SagaName::new("test-saga"));
builder.append(Node::constant("a", serde_json::Value::Null));
let dag = crate::SagaDag::new(
builder.build().expect("Should have built DAG"),
serde_json::Value::Null,
);

let mut nodes = dag.get_nodes();

let node = nodes.next().unwrap();
assert_eq!("a", node.name().as_ref());
assert_eq!("(constant = null)", node.label());

assert!(nodes.next().is_none());
}

#[test]
fn test_builder_bad_output_nodes() {
// error case: totally empty DAG
Expand Down

0 comments on commit 6661416

Please sign in to comment.