use crate::conditional::provider::PipelineProvider;
use crate::core::context::{ConditionFn, ExtractorFn, MergeFn};
use crate::core::context_data::ContextData;
use crate::core::control::PipelineControl;
use crate::error::OrkaError;
use async_trait::async_trait;
use std::marker::PhantomData;
use std::sync::Arc;
use tracing::{event, instrument, Level};
pub(crate) struct ConditionalScope<TData, SData, MainErr>
where
TData: 'static + Send + Sync,
SData: 'static + Send + Sync,
MainErr: std::error::Error + From<OrkaError> + Send + Sync + 'static,
{
pub(crate) pipeline_provider: Arc<dyn PipelineProvider<TData, SData, MainErr>>,
pub(crate) extractor: ExtractorFn<TData, SData>,
pub(crate) condition: ConditionFn<TData>,
pub(crate) merge: Option<MergeFn<TData, SData>>,
pub(crate) _phantom_main_err: PhantomData<MainErr>,
}
#[async_trait]
pub(crate) trait AnyConditionalScope<TData, MainErr>: Send + Sync
where
TData: 'static + Send + Sync,
MainErr: std::error::Error + From<OrkaError> + Send + Sync + 'static,
{
fn is_condition_met(&self, main_ctx_data: ContextData<TData>) -> bool;
async fn execute_scoped_pipeline(&self, main_ctx_data: ContextData<TData>) -> Result<PipelineControl, MainErr>;
}
#[async_trait]
impl<TData, SData, MainErr> AnyConditionalScope<TData, MainErr> for ConditionalScope<TData, SData, MainErr>
where
TData: 'static + Send + Sync,
SData: 'static + Send + Sync,
MainErr: std::error::Error + From<OrkaError> + Send + Sync + 'static,
{
fn is_condition_met(&self, main_ctx_data: ContextData<TData>) -> bool {
(self.condition)(main_ctx_data)
}
#[instrument(
name = "AnyConditionalScope::execute_scoped_pipeline",
skip(self, main_ctx_data),
fields(
main_context_data_type = %std::any::type_name::<TData>(),
scoped_context_data_type = %std::any::type_name::<SData>(),
main_error_type = %std::any::type_name::<MainErr>(),
),
err(Display)
)]
async fn execute_scoped_pipeline(&self, main_ctx_data: ContextData<TData>) -> Result<PipelineControl, MainErr> {
event!(Level::DEBUG, "Attempting to execute conditional scope.");
let scoped_pipeline_instance = match self.pipeline_provider.get_pipeline(main_ctx_data.clone()).await {
Ok(p) => {
event!(Level::TRACE, "Scoped pipeline instance obtained.");
p
}
Err(orka_provider_err) => {
event!(Level::ERROR, error = %orka_provider_err, "Failed to get pipeline from provider.");
let enriched_err = match orka_provider_err {
OrkaError::HandlerError { source } => OrkaError::PipelineProviderFailure {
step_name: String::from("conditional_scope_provider"), source,
},
OrkaError::PipelineProviderFailure { source, .. } => OrkaError::PipelineProviderFailure {
step_name: String::from("conditional_scope_provider"),
source,
},
other_err => other_err,
};
return Err(MainErr::from(enriched_err));
}
};
let sub_sdata_ctx: ContextData<SData> = match (self.extractor)(main_ctx_data.clone()) {
Ok(s_ctx_data) => {
event!(Level::TRACE, "Sub-context data extracted successfully.");
s_ctx_data
}
Err(orka_extractor_err) => {
event!(Level::ERROR, error = %orka_extractor_err, "Sub-context data extractor failed.");
let enriched_err = match orka_extractor_err {
OrkaError::HandlerError { source } => OrkaError::ExtractorFailure {
step_name: String::from("conditional_scope_extractor"), source,
},
OrkaError::ExtractorFailure { source, .. } => OrkaError::ExtractorFailure {
step_name: String::from("conditional_scope_extractor"),
source,
},
other_err => other_err,
};
return Err(MainErr::from(enriched_err));
}
};
event!(Level::DEBUG, "Running scoped pipeline.");
let control = match scoped_pipeline_instance.run(sub_sdata_ctx.clone()).await {
Ok(crate::core::control::PipelineResult::Completed) => {
event!(Level::INFO, "Scoped pipeline completed successfully.");
PipelineControl::Continue
}
Ok(crate::core::control::PipelineResult::Stopped) => {
event!(Level::INFO, "Scoped pipeline was stopped by one of its handlers.");
PipelineControl::Stop
}
Err(main_err_from_scoped_pipeline) => {
event!(Level::ERROR, error = %main_err_from_scoped_pipeline, "Scoped pipeline execution failed.");
return Err(main_err_from_scoped_pipeline);
}
};
if let Some(merge) = self.merge.as_ref() {
event!(Level::TRACE, "Merging scoped context back into main context.");
let sub_guard = sub_sdata_ctx.read();
let mut main_guard = main_ctx_data.write();
merge(&mut *main_guard, &*sub_guard);
}
Ok(control)
}
}