1use crate::error::Result;
10use crate::event::{ActionError, AfterEvent, BeforeEvent, InputEvent, LogEvent, Point, TraceEvent};
11use serde_json::Value;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct Action {
17 pub call_id: String,
18 pub parent_id: Option<String>,
19 pub class: String,
20 pub method: String,
21 pub title: Option<String>,
22 pub page_id: Option<String>,
23 pub start_time: f64,
24 pub end_time: Option<f64>,
27 pub params: Value,
28 pub result: Option<Value>,
29 pub error: Option<ActionError>,
30 pub logs: Vec<LogLine>,
31 pub input: Option<InputEvent>,
32 pub before_snapshot: Option<String>,
33 pub after_snapshot: Option<String>,
34 pub point: Option<Point>,
35}
36
37#[derive(Debug, Clone)]
39pub struct LogLine {
40 pub time: f64,
41 pub message: String,
42}
43
44impl From<LogEvent> for LogLine {
45 fn from(value: LogEvent) -> Self {
46 Self {
47 time: value.time,
48 message: value.message,
49 }
50 }
51}
52
53pub struct ActionStream<I> {
57 events: I,
58 pending: HashMap<String, ActionBuilder>,
59 pending_order: Vec<String>,
62 upstream_done: bool,
63}
64
65impl<I> ActionStream<I>
66where
67 I: Iterator<Item = Result<TraceEvent>>,
68{
69 pub fn new(events: I) -> Self {
70 Self {
71 events,
72 pending: HashMap::new(),
73 pending_order: Vec::new(),
74 upstream_done: false,
75 }
76 }
77}
78
79impl<I> Iterator for ActionStream<I>
80where
81 I: Iterator<Item = Result<TraceEvent>>,
82{
83 type Item = Result<Action>;
84
85 fn next(&mut self) -> Option<Self::Item> {
86 loop {
87 if self.upstream_done {
88 while let Some(call_id) = self.pending_order.pop() {
90 if let Some(builder) = self.pending.remove(&call_id) {
91 return Some(Ok(builder.finalize_truncated()));
92 }
93 }
94 return None;
95 }
96
97 let event = match self.events.next() {
98 Some(Ok(e)) => e,
99 Some(Err(e)) => return Some(Err(e)),
100 None => {
101 self.upstream_done = true;
102 continue;
103 }
104 };
105
106 match event {
107 TraceEvent::Before(b) => {
108 let call_id = b.call_id.clone();
109 if !self.pending.contains_key(&call_id) {
110 self.pending_order.push(call_id.clone());
111 }
112 self.pending.insert(call_id, ActionBuilder::from_before(b));
113 }
114 TraceEvent::Input(i) => {
115 if let Some(builder) = self.pending.get_mut(&i.call_id) {
116 builder.input = Some(i);
117 }
118 }
122 TraceEvent::Log(l) => {
123 if let Some(builder) = self.pending.get_mut(&l.call_id) {
124 builder.logs.push(l.into());
125 }
126 }
127 TraceEvent::After(a) => {
128 if let Some(builder) = self.pending.remove(&a.call_id) {
129 return Some(Ok(builder.finalize(a)));
134 }
135 }
137 _ => {}
138 }
139 }
140 }
141}
142
143struct ActionBuilder {
144 call_id: String,
145 parent_id: Option<String>,
146 class: String,
147 method: String,
148 title: Option<String>,
149 page_id: Option<String>,
150 start_time: f64,
151 params: Value,
152 before_snapshot: Option<String>,
153 logs: Vec<LogLine>,
154 input: Option<InputEvent>,
155}
156
157impl ActionBuilder {
158 fn from_before(b: BeforeEvent) -> Self {
159 Self {
160 call_id: b.call_id,
161 parent_id: b.parent_id,
162 class: b.class,
163 method: b.method,
164 title: b.title,
165 page_id: b.page_id,
166 start_time: b.start_time,
167 params: b.params,
168 before_snapshot: b.before_snapshot,
169 logs: Vec::new(),
170 input: None,
171 }
172 }
173
174 fn finalize(self, a: AfterEvent) -> Action {
175 Action {
176 call_id: self.call_id,
177 parent_id: self.parent_id,
178 class: self.class,
179 method: self.method,
180 title: self.title,
181 page_id: self.page_id,
182 start_time: self.start_time,
183 end_time: Some(a.end_time),
184 params: self.params,
185 result: a.result,
186 error: a.error,
187 logs: self.logs,
188 input: self.input,
189 before_snapshot: self.before_snapshot,
190 after_snapshot: a.after_snapshot,
191 point: a.point,
192 }
193 }
194
195 fn finalize_truncated(self) -> Action {
196 Action {
197 call_id: self.call_id,
198 parent_id: self.parent_id,
199 class: self.class,
200 method: self.method,
201 title: self.title,
202 page_id: self.page_id,
203 start_time: self.start_time,
204 end_time: None,
205 params: self.params,
206 result: None,
207 error: None,
208 logs: self.logs,
209 input: self.input,
210 before_snapshot: self.before_snapshot,
211 after_snapshot: None,
212 point: None,
213 }
214 }
215}