use crate::prelude::*;
use crate::op::Op;
use crate::wrappers::logging::LoggingWrapper;
use crate::{DryContext, OpError, WetContext};
use std::panic::Location;
pub async fn perform<T>(
op: Box<dyn Op<T>>,
dry: &mut DryContext,
wet: &mut WetContext,
) -> OpResult<T>
where
T: Send + 'static,
{
let trigger_name = get_caller_trigger_name();
let logged_op = LoggingWrapper::new(op, trigger_name);
logged_op.perform(dry, wet).await
}
#[track_caller]
pub fn get_caller_trigger_name() -> String {
let location = Location::caller();
format!(
"{}::{}",
location
.file()
.split('/')
.last()
.unwrap_or("unknown")
.replace(".rs", ""),
location.line()
)
}
pub fn wrap_nested_op_exception(trigger_name: &str, error: OpError) -> OpError {
match error {
OpError::ExecutionFailed(msg) => {
OpError::ExecutionFailed(format!("Op '{}' failed: {}", trigger_name, msg))
}
OpError::Timeout { timeout_ms } => OpError::ExecutionFailed(format!(
"Op '{}' timed out after {}ms",
trigger_name, timeout_ms
)),
OpError::Context(msg) => {
OpError::Context(format!("Op '{}' context error: {}", trigger_name, msg))
}
OpError::BatchFailed(msg) => {
OpError::BatchFailed(format!("Batch op '{}' failed: {}", trigger_name, msg))
}
OpError::WrappedClassified {
chain,
code,
class,
reason,
arg_urn,
} => OpError::WrappedClassified {
chain: format!("Batch op '{}' failed: {}", trigger_name, chain),
code,
class,
reason,
arg_urn,
},
OpError::Classified {
code,
class,
message,
arg_urn,
} => OpError::WrappedClassified {
chain: format!("Op '{}' failed: {}: {}", trigger_name, code, message),
code,
class,
reason: message,
arg_urn,
},
OpError::Aborted(reason) => {
OpError::Aborted(format!("Op '{}' aborted: {}", trigger_name, reason))
}
OpError::Trigger(msg) => {
OpError::Trigger(format!("Op '{}' internal error: {}", trigger_name, msg))
}
OpError::Other(boxed_error) => {
OpError::ExecutionFailed(format!("Op '{}' failed: {}", trigger_name, boxed_error))
}
}
}
pub fn wrap_nested_exception(error: Box<dyn std::error::Error + Send + Sync>) -> OpError {
OpError::Other(error)
}
pub fn wrap_runtime_exception(error: Box<dyn std::error::Error + Send + Sync>) -> OpError {
OpError::ExecutionFailed(format!("Runtime error: {}", error))
}
#[cfg(test)]
mod tests {
use super::*;
struct TestOp;
#[async_trait]
impl Op<i32> for TestOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<i32> {
Ok(42)
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("TestOp").build()
}
}
#[tokio::test]
async fn test0005_perform_with_auto_logging() {
let mut dry = DryContext::new();
let mut wet = WetContext::new();
let op = Box::new(TestOp);
let result = perform(op, &mut dry, &mut wet).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test0006_caller_trigger_name() {
let name = get_caller_trigger_name();
assert!(name.contains("ops"));
assert!(name.contains("::"));
}
#[test]
fn test0007_wrap_nested_op_exception() {
let original_error = OpError::ExecutionFailed("original error".to_string());
let wrapped = wrap_nested_op_exception("TestOp", original_error);
match wrapped {
OpError::ExecutionFailed(msg) => {
assert!(msg.contains("TestOp"));
assert!(msg.contains("original error"));
}
_ => panic!("Expected ExecutionFailed error"),
}
}
#[test]
fn test1903_wrap_preserves_classification() {
use crate::failure::AttributionClass;
let classified = OpError::Classified {
code: "CONTEXT_OVERFLOW".to_string(),
class: AttributionClass::Input,
message: "prompt too large".to_string(),
arg_urn: Some("media:prompt;textable".to_string()),
};
let wrapped = wrap_nested_op_exception("GenerateOp", classified);
match wrapped {
OpError::WrappedClassified {
chain,
code,
class,
reason,
arg_urn,
} => {
assert!(chain.contains("GenerateOp"), "the wrap names the op");
assert!(chain.contains("prompt too large"));
assert_eq!(code, "CONTEXT_OVERFLOW");
assert_eq!(class, AttributionClass::Input);
assert_eq!(reason, "prompt too large");
assert_eq!(
arg_urn.as_deref(),
Some("media:prompt;textable"),
"the origin's argument attribution survives wrapping verbatim"
);
}
other => panic!("expected WrappedClassified, got {:?}", other),
}
let rewrapped = wrap_nested_op_exception(
"OuterBatch",
OpError::WrappedClassified {
chain: "Op 'GenerateOp' failed: CONTEXT_OVERFLOW: prompt too large".to_string(),
code: "CONTEXT_OVERFLOW".to_string(),
class: AttributionClass::Input,
reason: "prompt too large".to_string(),
arg_urn: Some("media:prompt;textable".to_string()),
},
);
match rewrapped {
OpError::WrappedClassified {
chain,
code,
class,
reason,
arg_urn,
} => {
assert!(chain.contains("OuterBatch"));
assert!(chain.contains("GenerateOp"));
assert_eq!(code, "CONTEXT_OVERFLOW");
assert_eq!(class, AttributionClass::Input);
assert_eq!(reason, "prompt too large");
assert_eq!(arg_urn.as_deref(), Some("media:prompt;textable"));
}
other => panic!("expected WrappedClassified, got {:?}", other),
}
}
#[test]
fn test0008_wrap_runtime_exception() {
let error = Box::new(std::io::Error::new(std::io::ErrorKind::Other, "test error"));
let wrapped = wrap_runtime_exception(error);
match wrapped {
OpError::ExecutionFailed(msg) => {
assert!(msg.contains("Runtime error"));
assert!(msg.contains("test error"));
}
_ => panic!("Expected ExecutionFailed error"),
}
}
}