1use std::sync::Arc;
4
5use crate::executor::CompiledBranch;
6use crate::{
7 validate, CompileError, Event, NodeRegistry, RunOutcome, Spec, SpecError, State, Violation,
8 WorkflowContext,
9};
10
11pub const ROOT_BRANCH: &str = "__root__";
12
13#[derive(Debug, thiserror::Error)]
14pub enum HostError {
15 #[error("workflow spec failed to parse: {0}")]
16 Parse(#[from] SpecError),
17 #[error("workflow spec '{spec_id}' has no branches")]
18 NoBranch { spec_id: String },
19 #[error("workflow spec '{spec_id}' violates safety rules: {violations:?}")]
20 Safety {
21 spec_id: String,
22 violations: Vec<Violation>,
23 },
24 #[error("workflow spec '{spec_id}' failed to compile: {source}")]
25 Compile {
26 spec_id: String,
27 #[source]
28 source: CompileError,
29 },
30}
31
32struct BranchEntry {
33 id: String,
34 branch: CompiledBranch,
35}
36
37pub struct WorkflowHost {
40 spec_id: String,
41 branches: Vec<BranchEntry>,
42}
43
44impl WorkflowHost {
45 pub fn assemble(spec_json: &str, registry: &NodeRegistry) -> Result<Self, HostError> {
46 Self::from_spec(&Spec::from_json(spec_json)?, registry)
47 }
48
49 pub fn from_spec(spec: &Spec, registry: &NodeRegistry) -> Result<Self, HostError> {
50 if spec.branches.is_empty() {
51 return Err(HostError::NoBranch {
52 spec_id: spec.spec_id.clone(),
53 });
54 }
55 if let Err(violations) = validate(spec, registry) {
56 return Err(HostError::Safety {
57 spec_id: spec.spec_id.clone(),
58 violations,
59 });
60 }
61
62 let branches = spec
63 .branches
64 .iter()
65 .map(|branch| {
66 Ok(BranchEntry {
67 id: branch.branch_id.clone(),
68 branch: CompiledBranch::compile(branch, registry).map_err(|source| {
69 HostError::Compile {
70 spec_id: spec.spec_id.clone(),
71 source,
72 }
73 })?,
74 })
75 })
76 .collect::<Result<Vec<_>, HostError>>()?;
77 Ok(Self {
78 spec_id: spec.spec_id.clone(),
79 branches,
80 })
81 }
82
83 pub fn spec_id(&self) -> &str {
84 &self.spec_id
85 }
86
87 pub fn branch_id(&self) -> &str {
88 &self.branches[0].id
89 }
90
91 pub fn branch_ids(&self) -> impl Iterator<Item = &str> {
92 self.branches.iter().map(|branch| branch.id.as_str())
93 }
94
95 pub fn branch_count(&self) -> usize {
96 self.branches.len()
97 }
98
99 pub fn context(&self, state: Arc<dyn State>) -> WorkflowContext {
100 self.context_for(self.branch_id(), state)
101 }
102
103 pub fn context_for(&self, branch_id: &str, state: Arc<dyn State>) -> WorkflowContext {
104 WorkflowContext::new(branch_id, state)
105 }
106
107 pub async fn run_event(&self, ctx: &WorkflowContext, event: Event) -> Option<RunOutcome> {
108 self.run_event_on(&ctx.branch_id, ctx, event).await
109 }
110
111 pub async fn run_event_on(
112 &self,
113 branch_id: &str,
114 ctx: &WorkflowContext,
115 event: Event,
116 ) -> Option<RunOutcome> {
117 let branch = self.branches.iter().find(|branch| branch.id == branch_id)?;
118 Some(branch.branch.run_event(ctx, event).await)
119 }
120}