1use capnweb_core::{CapId, Op, Plan, Source};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::sync::{Arc, Mutex};
5
6#[derive(Clone)]
8pub struct Recorder {
9 inner: Arc<Mutex<RecorderInner>>,
10}
11
12struct RecorderInner {
13 captures: Vec<CapId>,
14 ops: Vec<Op>,
15 next_result_index: u32,
16 capability_map: BTreeMap<String, u32>,
17}
18
19impl Recorder {
20 pub fn new() -> Self {
22 Self {
23 inner: Arc::new(Mutex::new(RecorderInner {
24 captures: Vec::new(),
25 ops: Vec::new(),
26 next_result_index: 0,
27 capability_map: BTreeMap::new(),
28 })),
29 }
30 }
31
32 pub fn capture(&self, name: &str, cap_id: CapId) -> RecordedCapability {
34 let mut inner = self.inner.lock().unwrap();
35 let index = inner.captures.len() as u32;
36 inner.captures.push(cap_id);
37 inner.capability_map.insert(name.to_string(), index);
38
39 RecordedCapability {
40 recorder: self.clone(),
41 index,
42 name: name.to_string(),
43 }
44 }
45
46 pub fn call(&self, target: Source, method: &str, args: Vec<Source>) -> RecordedResult {
48 let mut inner = self.inner.lock().unwrap();
49 let result_index = inner.next_result_index;
50 inner.next_result_index += 1;
51
52 inner
53 .ops
54 .push(Op::call(target, method.to_string(), args, result_index));
55
56 RecordedResult {
57 recorder: self.clone(),
58 index: result_index,
59 }
60 }
61
62 pub fn object(&self, fields: BTreeMap<String, Source>) -> RecordedResult {
64 let mut inner = self.inner.lock().unwrap();
65 let result_index = inner.next_result_index;
66 inner.next_result_index += 1;
67
68 inner.ops.push(Op::object(fields, result_index));
69
70 RecordedResult {
71 recorder: self.clone(),
72 index: result_index,
73 }
74 }
75
76 pub fn array(&self, items: Vec<Source>) -> RecordedResult {
78 let mut inner = self.inner.lock().unwrap();
79 let result_index = inner.next_result_index;
80 inner.next_result_index += 1;
81
82 inner.ops.push(Op::array(items, result_index));
83
84 RecordedResult {
85 recorder: self.clone(),
86 index: result_index,
87 }
88 }
89
90 pub fn build(&self, result: Source) -> Plan {
92 let inner = self.inner.lock().unwrap();
93 Plan::new(inner.captures.clone(), inner.ops.clone(), result)
94 }
95
96 pub fn cap(&self, name: &str) -> Option<Source> {
98 let inner = self.inner.lock().unwrap();
99 inner
100 .capability_map
101 .get(name)
102 .map(|&index| Source::capture(index))
103 }
104}
105
106impl Default for Recorder {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112pub struct RecordedCapability {
114 recorder: Recorder,
115 index: u32,
116 #[allow(dead_code)]
117 name: String,
118}
119
120impl RecordedCapability {
121 pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
123 self.recorder
124 .call(Source::capture(self.index), method, args)
125 }
126
127 pub fn as_source(&self) -> Source {
129 Source::capture(self.index)
130 }
131}
132
133pub struct RecordedResult {
135 recorder: Recorder,
136 index: u32,
137}
138
139impl RecordedResult {
140 pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
142 self.recorder.call(Source::result(self.index), method, args)
143 }
144
145 pub fn as_source(&self) -> Source {
147 Source::result(self.index)
148 }
149
150 pub fn field(&self, name: &str) -> RecordedField {
152 RecordedField {
153 recorder: self.recorder.clone(),
154 result_index: self.index,
155 field_name: name.to_string(),
156 }
157 }
158}
159
160pub struct RecordedField {
162 recorder: Recorder,
163 result_index: u32,
164 #[allow(dead_code)]
165 field_name: String,
166}
167
168impl RecordedField {
169 pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
171 let field_source = self.as_source();
173 self.recorder.call(field_source, method, args)
174 }
175
176 pub fn as_source(&self) -> Source {
178 Source::result(self.result_index)
181 }
182}
183
184pub struct Param;
186
187impl Param {
188 pub fn path(segments: &[&str]) -> Source {
190 Source::param(segments.iter().map(|s| s.to_string()).collect())
191 }
192
193 pub fn value(val: Value) -> Source {
195 Source::by_value(val)
196 }
197}
198
199pub struct RecordedPlan {
201 recorder: Recorder,
202}
203
204impl RecordedPlan {
205 pub fn new() -> Self {
207 Self {
208 recorder: Recorder::new(),
209 }
210 }
211
212 pub fn capture(&self, name: &str, cap_id: CapId) -> RecordedCapability {
214 self.recorder.capture(name, cap_id)
215 }
216
217 pub fn object(&self, fields: BTreeMap<String, Source>) -> RecordedResult {
219 self.recorder.object(fields)
220 }
221
222 pub fn array(&self, items: Vec<Source>) -> RecordedResult {
224 self.recorder.array(items)
225 }
226
227 pub fn finish(self, result: Source) -> Plan {
229 self.recorder.build(result)
230 }
231}
232
233impl Default for RecordedPlan {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_recorder_basic() {
245 let recorder = Recorder::new();
246
247 let cap = recorder.capture("calculator", CapId::new(1));
249
250 let result = cap.call(
252 "add",
253 vec![
254 Param::value(serde_json::json!(5)),
255 Param::value(serde_json::json!(3)),
256 ],
257 );
258
259 let plan = recorder.build(result.as_source());
261
262 assert_eq!(plan.captures.len(), 1);
264 assert_eq!(plan.ops.len(), 1);
265 }
266
267 #[test]
268 fn test_recorded_plan_builder() {
269 let plan_builder = RecordedPlan::new();
270
271 let calc = plan_builder.capture("calc", CapId::new(1));
273
274 let sum = calc.call("add", vec![Param::path(&["a"]), Param::path(&["b"])]);
276
277 let plan = plan_builder.finish(sum.as_source());
279
280 assert_eq!(plan.captures.len(), 1);
281 assert_eq!(plan.ops.len(), 1);
282 }
283
284 #[test]
285 fn test_chained_calls() {
286 let recorder = Recorder::new();
287
288 let api = recorder.capture("api", CapId::new(1));
289
290 let result = api
292 .call("getUser", vec![Param::value(serde_json::json!(123))])
293 .call("getName", vec![]);
294
295 let plan = recorder.build(result.as_source());
296
297 assert_eq!(plan.ops.len(), 2);
298 }
299
300 #[test]
301 fn test_object_construction() {
302 let recorder = Recorder::new();
303
304 let api = recorder.capture("api", CapId::new(1));
305 let name = api.call("getName", vec![]);
306 let age = api.call("getAge", vec![]);
307
308 let mut fields = BTreeMap::new();
309 fields.insert("name".to_string(), name.as_source());
310 fields.insert("age".to_string(), age.as_source());
311
312 let obj = recorder.object(fields);
313 let plan = recorder.build(obj.as_source());
314
315 assert_eq!(plan.ops.len(), 3); }
317
318 #[test]
319 fn test_array_construction() {
320 let recorder = Recorder::new();
321
322 let api = recorder.capture("api", CapId::new(1));
323
324 let items = vec![
325 api.call("getValue", vec![Param::value(serde_json::json!(1))])
326 .as_source(),
327 api.call("getValue", vec![Param::value(serde_json::json!(2))])
328 .as_source(),
329 api.call("getValue", vec![Param::value(serde_json::json!(3))])
330 .as_source(),
331 ];
332
333 let arr = recorder.array(items);
334 let plan = recorder.build(arr.as_source());
335
336 assert_eq!(plan.ops.len(), 4); }
338}