use orka::{ContextData, OrkaError, Pipeline, PipelineControl, PipelineResult};
use std::sync::Arc;
use tracing::info;
#[derive(Clone, Debug, Default)]
struct MainDynContext {
trigger_value: i32,
log: Vec<String>,
shared_input_for_scoped: String,
}
#[derive(Clone, Debug, Default)]
struct DynScopedCtxAlpha {
input: String,
message_alpha: String,
}
#[derive(Clone, Debug, Default)]
struct DynScopedCtxBeta {
input: String,
message_beta: String,
is_special_beta: bool,
}
type AppError = OrkaError;
async fn factory_for_alpha(
main_ctx: ContextData<MainDynContext>,
) -> Result<Arc<Pipeline<DynScopedCtxAlpha, AppError>>, OrkaError> {
let main_trigger_val = main_ctx.read().trigger_value;
info!(
"Dynamic Factory Alpha: Creating pipeline. Main trigger value was: {}",
main_trigger_val
);
if main_trigger_val == 42 {
return Err(OrkaError::PipelineProviderFailure {
step_name: "factory_for_alpha_init_fail".to_string(),
source: anyhow::anyhow!("Factory Alpha cannot proceed with trigger_value 42"),
});
}
let mut p_alpha = Pipeline::<DynScopedCtxAlpha, AppError>::new(["process_alpha_dyn"]);
p_alpha.on_root("process_alpha_dyn", |s_ctx| async move {
let mut data = s_ctx.write();
data.message_alpha = format!("Alpha dynamically processed: '{}'", data.input);
info!("Scoped: {}", data.message_alpha);
if data.input == "FAIL_ALPHA_HANDLER" {
return Err(OrkaError::Internal("Alpha scoped handler failed".to_string()));
}
Ok(PipelineControl::Continue)
});
Ok(Arc::new(p_alpha))
}
async fn factory_for_beta(
main_ctx: ContextData<MainDynContext>,
) -> Result<Arc<Pipeline<DynScopedCtxBeta, AppError>>, OrkaError> {
let main_trigger_val = main_ctx.read().trigger_value;
info!(
"Dynamic Factory Beta: Creating pipeline. Main trigger value was: {}",
main_trigger_val
);
let mut p_beta = Pipeline::<DynScopedCtxBeta, AppError>::new(["process_beta_dyn"]);
p_beta.on_root("process_beta_dyn", move |s_ctx| {
let is_special_from_factory = main_trigger_val > 100;
async move {
let mut data = s_ctx.write();
data.message_beta = format!("Beta dynamically processed: '{}'", data.input);
data.is_special_beta = is_special_from_factory;
info!("Scoped: {}, Special: {}", data.message_beta, data.is_special_beta);
Ok(PipelineControl::Continue)
}
});
Ok(Arc::new(p_beta))
}
async fn always_failing_factory(
_main_ctx: ContextData<MainDynContext>,
) -> Result<Arc<Pipeline<DynScopedCtxAlpha, AppError>>, OrkaError> {
info!("Always Failing Factory: Intentionally returning error.");
Err(OrkaError::PipelineProviderFailure {
step_name: "always_failing_factory".to_string(),
source: anyhow::anyhow!("Provider error from always_failing_factory"),
})
}
#[tokio::main]
async fn main() -> Result<(), AppError> {
tracing_subscriber::fmt().with_max_level(tracing::Level::INFO).init();
info!("--- Dynamic Conditional Logic Example ---");
let mut main_pipeline =
Pipeline::<MainDynContext, AppError>::new(["set_trigger", "dynamic_conditional_step", "final_check"]);
main_pipeline.on_root("set_trigger", |ctx| async move {
let mut data = ctx.write();
let log_msg = format!("Main: Trigger value set to: {}", data.trigger_value);
info!("{}", log_msg);
data.log.push(log_msg);
Ok(PipelineControl::Continue)
});
main_pipeline
.conditional_scopes_for_step("dynamic_conditional_step")
.add_dynamic_scope(factory_for_alpha, |main_ctx: ContextData<MainDynContext>| {
let input = main_ctx.read().shared_input_for_scoped.clone();
info!("Extractor for Alpha: Input will be '{}'", input);
Ok(ContextData::new(DynScopedCtxAlpha {
input,
..Default::default()
}))
})
.on_condition(|main_ctx: ContextData<MainDynContext>| {
let val = main_ctx.read().trigger_value;
val > 0 && val <= 50
})
.add_dynamic_scope(factory_for_beta, |main_ctx: ContextData<MainDynContext>| {
let input = main_ctx.read().shared_input_for_scoped.clone();
info!("Extractor for Beta: Input will be '{}'", input);
Ok(ContextData::new(DynScopedCtxBeta {
input,
..Default::default()
}))
})
.on_condition(|main_ctx: ContextData<MainDynContext>| main_ctx.read().trigger_value > 50)
.if_no_scope_matches(PipelineControl::Continue)
.finalize_conditional_step(false);
main_pipeline.on_root("final_check", |ctx| async move {
let mut data = ctx.write();
let log_msg = format!("Main: Final check. Current log: {:?}", data.log);
info!("{}", log_msg);
data.log.push(log_msg);
Ok(PipelineControl::Continue)
});
info!("\n--- Running Scenario for Dynamic Alpha ---");
let ctx_alpha = ContextData::new(MainDynContext {
trigger_value: 25,
shared_input_for_scoped: "Hello Alpha".to_string(),
..Default::default()
});
let result_alpha = main_pipeline.run(ctx_alpha.clone()).await?;
assert_eq!(result_alpha, PipelineResult::Completed);
let final_alpha_log = ctx_alpha.read().log.clone();
assert!(final_alpha_log.iter().any(|s| s.contains("Trigger value set to: 25")));
info!("Alpha scenario log: {:?}", final_alpha_log);
info!("\n--- Running Scenario for Dynamic Beta (special) ---");
let ctx_beta = ContextData::new(MainDynContext {
trigger_value: 150,
shared_input_for_scoped: "Hello Beta".to_string(),
..Default::default()
});
let result_beta = main_pipeline.run(ctx_beta.clone()).await?;
assert_eq!(result_beta, PipelineResult::Completed);
let final_beta_log = ctx_beta.read().log.clone();
assert!(final_beta_log.iter().any(|s| s.contains("Trigger value set to: 150")));
info!("Beta scenario log: {:?}", final_beta_log);
info!("\n--- Running Scenario: Alpha Factory has internal failure ---");
let ctx_alpha_factory_fail = ContextData::new(MainDynContext {
trigger_value: 42,
shared_input_for_scoped: "Input for Alpha factory failure".to_string(),
..Default::default()
});
let result_alpha_factory_fail = main_pipeline.run(ctx_alpha_factory_fail.clone()).await;
assert!(
result_alpha_factory_fail.is_err(),
"Expected pipeline to fail due to Alpha factory's internal error"
);
if let Err(e) = &result_alpha_factory_fail {
info!("Pipeline failed as expected due to Alpha factory internal error: {}", e);
assert!(format!("{:?}", e).contains("Factory Alpha cannot proceed with trigger_value 42"));
}
info!("\n--- Running Scenario: Scoped Alpha Handler Fails ---");
let ctx_alpha_handler_fail = ContextData::new(MainDynContext {
trigger_value: 10,
shared_input_for_scoped: "FAIL_ALPHA_HANDLER".to_string(),
..Default::default()
});
let result_alpha_handler_fail = main_pipeline.run(ctx_alpha_handler_fail.clone()).await;
assert!(
result_alpha_handler_fail.is_err(),
"Expected pipeline to fail due to Alpha scoped handler error"
);
if let Err(e) = &result_alpha_handler_fail {
info!("Pipeline failed as expected due to Alpha scoped handler error: {}", e);
assert!(format!("{:?}", e).contains("Alpha scoped handler failed"));
}
let mut fail_test_pipeline = Pipeline::<MainDynContext, AppError>::new([
"set_trigger_fail_test",
"dynamic_cond_step_prov_fail_test",
]);
fail_test_pipeline.on_root("set_trigger_fail_test", |ctx| async move {
let mut data = ctx.write();
let log_msg = format!("FailTest Main: Trigger for provider fail: {}", data.trigger_value);
info!("{}", log_msg);
data.log.push(log_msg);
Ok(PipelineControl::Continue)
});
fail_test_pipeline
.conditional_scopes_for_step("dynamic_cond_step_prov_fail_test")
.add_dynamic_scope(always_failing_factory, |main_ctx: ContextData<MainDynContext>| {
let input = main_ctx.read().shared_input_for_scoped.clone();
Ok(ContextData::new(DynScopedCtxAlpha {
input,
..Default::default()
}))
})
.on_condition(|main_ctx: ContextData<MainDynContext>| main_ctx.read().trigger_value == 777)
.if_no_scope_matches(PipelineControl::Stop)
.finalize_conditional_step(false);
info!("\n--- Running Scenario: Provider (Factory) Fails Externally ---");
let ctx_provider_fail = ContextData::new(MainDynContext {
trigger_value: 777,
shared_input_for_scoped: "Input for always_failing_provider".to_string(),
..Default::default()
});
let result_provider_fail = fail_test_pipeline.run(ctx_provider_fail.clone()).await;
assert!(
result_provider_fail.is_err(),
"Expected pipeline to fail due to provider error. Got: {:?}",
result_provider_fail
);
if let Err(e) = result_provider_fail {
info!("Pipeline failed as expected due to provider error: {}", e);
match e {
OrkaError::PipelineProviderFailure { ref source, .. } => {
assert!(source.to_string().contains("Provider error from always_failing_factory"));
}
OrkaError::Internal(ref s)
if s.contains("conditional_scope_provider") && s.contains("Provider error from always_failing_factory") => {}
other_err => panic!(
"Unexpected error type for provider failure: {:?}, expected PipelineProviderFailure containing 'always_failing_factory'",
other_err
),
}
}
Ok(())
}