use capnweb_core::{CapId, Op, Plan, Source};
use serde_json::Value;
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct Recorder {
inner: Arc<Mutex<RecorderInner>>,
}
struct RecorderInner {
captures: Vec<CapId>,
ops: Vec<Op>,
next_result_index: u32,
capability_map: BTreeMap<String, u32>,
}
impl Recorder {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(RecorderInner {
captures: Vec::new(),
ops: Vec::new(),
next_result_index: 0,
capability_map: BTreeMap::new(),
})),
}
}
pub fn capture(&self, name: &str, cap_id: CapId) -> RecordedCapability {
let mut inner = self.inner.lock().unwrap();
let index = inner.captures.len() as u32;
inner.captures.push(cap_id);
inner.capability_map.insert(name.to_string(), index);
RecordedCapability {
recorder: self.clone(),
index,
name: name.to_string(),
}
}
pub fn call(&self, target: Source, method: &str, args: Vec<Source>) -> RecordedResult {
let mut inner = self.inner.lock().unwrap();
let result_index = inner.next_result_index;
inner.next_result_index += 1;
inner
.ops
.push(Op::call(target, method.to_string(), args, result_index));
RecordedResult {
recorder: self.clone(),
index: result_index,
}
}
pub fn object(&self, fields: BTreeMap<String, Source>) -> RecordedResult {
let mut inner = self.inner.lock().unwrap();
let result_index = inner.next_result_index;
inner.next_result_index += 1;
inner.ops.push(Op::object(fields, result_index));
RecordedResult {
recorder: self.clone(),
index: result_index,
}
}
pub fn array(&self, items: Vec<Source>) -> RecordedResult {
let mut inner = self.inner.lock().unwrap();
let result_index = inner.next_result_index;
inner.next_result_index += 1;
inner.ops.push(Op::array(items, result_index));
RecordedResult {
recorder: self.clone(),
index: result_index,
}
}
pub fn build(&self, result: Source) -> Plan {
let inner = self.inner.lock().unwrap();
Plan::new(inner.captures.clone(), inner.ops.clone(), result)
}
pub fn cap(&self, name: &str) -> Option<Source> {
let inner = self.inner.lock().unwrap();
inner
.capability_map
.get(name)
.map(|&index| Source::capture(index))
}
}
impl Default for Recorder {
fn default() -> Self {
Self::new()
}
}
pub struct RecordedCapability {
recorder: Recorder,
index: u32,
#[allow(dead_code)]
name: String,
}
impl RecordedCapability {
pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
self.recorder
.call(Source::capture(self.index), method, args)
}
pub fn as_source(&self) -> Source {
Source::capture(self.index)
}
}
pub struct RecordedResult {
recorder: Recorder,
index: u32,
}
impl RecordedResult {
pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
self.recorder.call(Source::result(self.index), method, args)
}
pub fn as_source(&self) -> Source {
Source::result(self.index)
}
pub fn field(&self, name: &str) -> RecordedField {
RecordedField {
recorder: self.recorder.clone(),
result_index: self.index,
field_name: name.to_string(),
}
}
}
pub struct RecordedField {
recorder: Recorder,
result_index: u32,
#[allow(dead_code)]
field_name: String,
}
impl RecordedField {
pub fn call(&self, method: &str, args: Vec<Source>) -> RecordedResult {
let field_source = self.as_source();
self.recorder.call(field_source, method, args)
}
pub fn as_source(&self) -> Source {
Source::result(self.result_index)
}
}
pub struct Param;
impl Param {
pub fn path(segments: &[&str]) -> Source {
Source::param(segments.iter().map(|s| s.to_string()).collect())
}
pub fn value(val: Value) -> Source {
Source::by_value(val)
}
}
pub struct RecordedPlan {
recorder: Recorder,
}
impl RecordedPlan {
pub fn new() -> Self {
Self {
recorder: Recorder::new(),
}
}
pub fn capture(&self, name: &str, cap_id: CapId) -> RecordedCapability {
self.recorder.capture(name, cap_id)
}
pub fn object(&self, fields: BTreeMap<String, Source>) -> RecordedResult {
self.recorder.object(fields)
}
pub fn array(&self, items: Vec<Source>) -> RecordedResult {
self.recorder.array(items)
}
pub fn finish(self, result: Source) -> Plan {
self.recorder.build(result)
}
}
impl Default for RecordedPlan {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recorder_basic() {
let recorder = Recorder::new();
let cap = recorder.capture("calculator", CapId::new(1));
let result = cap.call(
"add",
vec![
Param::value(serde_json::json!(5)),
Param::value(serde_json::json!(3)),
],
);
let plan = recorder.build(result.as_source());
assert_eq!(plan.captures.len(), 1);
assert_eq!(plan.ops.len(), 1);
}
#[test]
fn test_recorded_plan_builder() {
let plan_builder = RecordedPlan::new();
let calc = plan_builder.capture("calc", CapId::new(1));
let sum = calc.call("add", vec![Param::path(&["a"]), Param::path(&["b"])]);
let plan = plan_builder.finish(sum.as_source());
assert_eq!(plan.captures.len(), 1);
assert_eq!(plan.ops.len(), 1);
}
#[test]
fn test_chained_calls() {
let recorder = Recorder::new();
let api = recorder.capture("api", CapId::new(1));
let result = api
.call("getUser", vec![Param::value(serde_json::json!(123))])
.call("getName", vec![]);
let plan = recorder.build(result.as_source());
assert_eq!(plan.ops.len(), 2);
}
#[test]
fn test_object_construction() {
let recorder = Recorder::new();
let api = recorder.capture("api", CapId::new(1));
let name = api.call("getName", vec![]);
let age = api.call("getAge", vec![]);
let mut fields = BTreeMap::new();
fields.insert("name".to_string(), name.as_source());
fields.insert("age".to_string(), age.as_source());
let obj = recorder.object(fields);
let plan = recorder.build(obj.as_source());
assert_eq!(plan.ops.len(), 3); }
#[test]
fn test_array_construction() {
let recorder = Recorder::new();
let api = recorder.capture("api", CapId::new(1));
let items = vec![
api.call("getValue", vec![Param::value(serde_json::json!(1))])
.as_source(),
api.call("getValue", vec![Param::value(serde_json::json!(2))])
.as_source(),
api.call("getValue", vec![Param::value(serde_json::json!(3))])
.as_source(),
];
let arr = recorder.array(items);
let plan = recorder.build(arr.as_source());
assert_eq!(plan.ops.len(), 4); }
}