use std::sync::Arc;
use crate::{
effect::{Answered, EffectId, EffectKind, HandlerDescriptor, HandlerKey},
error::ErrorReport,
streaming::{Item, StreamEvent},
wasm_compat::{WasmCompatSend, WasmCompatSync},
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DispatchContext {
pub parent: Option<EffectId>,
pub scope: Option<std::sync::Arc<str>>,
pub purpose: Option<std::borrow::Cow<'static, str>>,
}
pub trait Recorder: WasmCompatSend + WasmCompatSync + 'static {
fn handlers(&self, handlers: Vec<HandlerDescriptor>);
fn begin(&self, id: EffectId, key: HandlerKey, kind: EffectKind, origin: DispatchContext);
fn discard(&self, id: EffectId);
fn patch(&self, id: EffectId, kind: EffectKind);
fn keep_events(&self) -> bool;
fn event(&self, id: EffectId, item: &Item<StreamEvent>);
fn stream_error(&self, _id: EffectId, _error: &ErrorReport) {}
fn origin(&self, id: EffectId, origin: &crate::message::Origin);
fn tool_output(&self, id: EffectId, output: crate::tool::ToolResultContext);
fn resolve(&self, id: EffectId, outcome: Result<Answered, ErrorReport>);
}
pub(crate) struct RecordingObserver {
pub(crate) recorder: Arc<dyn Recorder + Send + Sync>,
pub(crate) id: EffectId,
pub(crate) published: Option<Arc<crate::tool::PublishedContext>>,
}
impl super::Observe for RecordingObserver {
fn outcome(&mut self, outcome: Result<Answered, ErrorReport>) {
if let Some(output) = self
.published
.as_ref()
.and_then(|published| published.result_context())
{
self.recorder.tool_output(self.id, output);
}
self.recorder.resolve(self.id, outcome);
}
fn keep_events(&self) -> bool {
self.recorder.keep_events()
}
fn event(&mut self, item: &Item<StreamEvent>) {
self.recorder.event(self.id, item);
}
fn stream_error(&mut self, error: &ErrorReport) {
self.recorder.stream_error(self.id, error);
}
fn origin(&mut self, origin: &crate::message::Origin) {
self.recorder.origin(self.id, origin);
}
fn discard(&mut self, _layer: &str) {
self.recorder.discard(self.id);
}
fn patch(&mut self, kind: &EffectKind) {
self.recorder.patch(self.id, kind.clone());
}
}
pub fn catch_panics<T>(
recorder: Option<Arc<dyn Recorder + Send + Sync>>,
id: EffectId,
work: impl Future<Output = Result<T, ErrorReport>>,
) -> impl Future<Output = Result<T, ErrorReport>> {
use futures::FutureExt;
async move {
std::panic::AssertUnwindSafe(work)
.catch_unwind()
.await
.unwrap_or_else(|panic| {
let message = panic
.downcast_ref::<&str>()
.copied()
.or_else(|| panic.downcast_ref::<String>().map(String::as_str))
.unwrap_or("no message");
let report = ErrorReport::new(
crate::error::ErrorKind::Internal,
format!("panicked: {message}"),
);
if let Some(recorder) = recorder {
recorder.resolve(id, Err(report.clone()));
}
Err(report)
})
}
}