use crate::error::Result;
use crate::event::{ActionError, AfterEvent, BeforeEvent, InputEvent, LogEvent, Point, TraceEvent};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Action {
pub call_id: String,
pub parent_id: Option<String>,
pub class: String,
pub method: String,
pub title: Option<String>,
pub page_id: Option<String>,
pub start_time: f64,
pub end_time: Option<f64>,
pub params: Value,
pub result: Option<Value>,
pub error: Option<ActionError>,
pub logs: Vec<LogLine>,
pub input: Option<InputEvent>,
pub before_snapshot: Option<String>,
pub after_snapshot: Option<String>,
pub point: Option<Point>,
}
#[derive(Debug, Clone)]
pub struct LogLine {
pub time: f64,
pub message: String,
}
impl From<LogEvent> for LogLine {
fn from(value: LogEvent) -> Self {
Self {
time: value.time,
message: value.message,
}
}
}
pub struct ActionStream<I> {
events: I,
pending: HashMap<String, ActionBuilder>,
pending_order: Vec<String>,
upstream_done: bool,
}
impl<I> ActionStream<I>
where
I: Iterator<Item = Result<TraceEvent>>,
{
pub fn new(events: I) -> Self {
Self {
events,
pending: HashMap::new(),
pending_order: Vec::new(),
upstream_done: false,
}
}
}
impl<I> Iterator for ActionStream<I>
where
I: Iterator<Item = Result<TraceEvent>>,
{
type Item = Result<Action>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.upstream_done {
while let Some(call_id) = self.pending_order.pop() {
if let Some(builder) = self.pending.remove(&call_id) {
return Some(Ok(builder.finalize_truncated()));
}
}
return None;
}
let event = match self.events.next() {
Some(Ok(e)) => e,
Some(Err(e)) => return Some(Err(e)),
None => {
self.upstream_done = true;
continue;
}
};
match event {
TraceEvent::Before(b) => {
let call_id = b.call_id.clone();
if !self.pending.contains_key(&call_id) {
self.pending_order.push(call_id.clone());
}
self.pending.insert(call_id, ActionBuilder::from_before(b));
}
TraceEvent::Input(i) => {
if let Some(builder) = self.pending.get_mut(&i.call_id) {
builder.input = Some(i);
}
}
TraceEvent::Log(l) => {
if let Some(builder) = self.pending.get_mut(&l.call_id) {
builder.logs.push(l.into());
}
}
TraceEvent::After(a) => {
if let Some(builder) = self.pending.remove(&a.call_id) {
return Some(Ok(builder.finalize(a)));
}
}
_ => {}
}
}
}
}
struct ActionBuilder {
call_id: String,
parent_id: Option<String>,
class: String,
method: String,
title: Option<String>,
page_id: Option<String>,
start_time: f64,
params: Value,
before_snapshot: Option<String>,
logs: Vec<LogLine>,
input: Option<InputEvent>,
}
impl ActionBuilder {
fn from_before(b: BeforeEvent) -> Self {
Self {
call_id: b.call_id,
parent_id: b.parent_id,
class: b.class,
method: b.method,
title: b.title,
page_id: b.page_id,
start_time: b.start_time,
params: b.params,
before_snapshot: b.before_snapshot,
logs: Vec::new(),
input: None,
}
}
fn finalize(self, a: AfterEvent) -> Action {
Action {
call_id: self.call_id,
parent_id: self.parent_id,
class: self.class,
method: self.method,
title: self.title,
page_id: self.page_id,
start_time: self.start_time,
end_time: Some(a.end_time),
params: self.params,
result: a.result,
error: a.error,
logs: self.logs,
input: self.input,
before_snapshot: self.before_snapshot,
after_snapshot: a.after_snapshot,
point: a.point,
}
}
fn finalize_truncated(self) -> Action {
Action {
call_id: self.call_id,
parent_id: self.parent_id,
class: self.class,
method: self.method,
title: self.title,
page_id: self.page_id,
start_time: self.start_time,
end_time: None,
params: self.params,
result: None,
error: None,
logs: self.logs,
input: self.input,
before_snapshot: self.before_snapshot,
after_snapshot: None,
point: None,
}
}
}