capnweb_core/
il.rs

1use crate::CapId;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum Source {
9    Capture {
10        capture: CaptureRef,
11    },
12    Result {
13        result: ResultRef,
14    },
15    Param {
16        param: ParamRef,
17    },
18    ByValue {
19        #[serde(rename = "byValue")]
20        by_value: ValueRef,
21    },
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct CaptureRef {
26    pub index: u32,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct ResultRef {
31    pub index: u32,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct ParamRef {
36    pub path: Vec<String>,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct ValueRef {
41    pub value: Value,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45#[serde(untagged)]
46pub enum Op {
47    Call { call: CallOp },
48    Object { object: ObjectOp },
49    Array { array: ArrayOp },
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct CallOp {
54    pub target: Source,
55    pub member: String,
56    pub args: Vec<Source>,
57    pub result: u32,
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct ObjectOp {
62    pub fields: BTreeMap<String, Source>,
63    pub result: u32,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct ArrayOp {
68    pub items: Vec<Source>,
69    pub result: u32,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct Plan {
74    pub captures: Vec<CapId>,
75    pub ops: Vec<Op>,
76    pub result: Source,
77}
78
79impl Source {
80    pub fn capture(index: u32) -> Self {
81        Source::Capture {
82            capture: CaptureRef { index },
83        }
84    }
85
86    pub fn result(index: u32) -> Self {
87        Source::Result {
88            result: ResultRef { index },
89        }
90    }
91
92    pub fn param(path: Vec<String>) -> Self {
93        Source::Param {
94            param: ParamRef { path },
95        }
96    }
97
98    pub fn by_value(value: Value) -> Self {
99        Source::ByValue {
100            by_value: ValueRef { value },
101        }
102    }
103
104    pub fn get_capture_index(&self) -> Option<u32> {
105        match self {
106            Source::Capture { capture } => Some(capture.index),
107            _ => None,
108        }
109    }
110
111    pub fn get_result_index(&self) -> Option<u32> {
112        match self {
113            Source::Result { result } => Some(result.index),
114            _ => None,
115        }
116    }
117}
118
119impl Op {
120    pub fn call(target: Source, member: String, args: Vec<Source>, result: u32) -> Self {
121        Op::Call {
122            call: CallOp {
123                target,
124                member,
125                args,
126                result,
127            },
128        }
129    }
130
131    pub fn object(fields: BTreeMap<String, Source>, result: u32) -> Self {
132        Op::Object {
133            object: ObjectOp { fields, result },
134        }
135    }
136
137    pub fn array(items: Vec<Source>, result: u32) -> Self {
138        Op::Array {
139            array: ArrayOp { items, result },
140        }
141    }
142
143    pub fn get_result_index(&self) -> u32 {
144        match self {
145            Op::Call { call } => call.result,
146            Op::Object { object } => object.result,
147            Op::Array { array } => array.result,
148        }
149    }
150}
151
152impl Plan {
153    pub fn new(captures: Vec<CapId>, ops: Vec<Op>, result: Source) -> Self {
154        Plan {
155            captures,
156            ops,
157            result,
158        }
159    }
160
161    pub fn validate(&self) -> Result<(), String> {
162        let mut result_indices = std::collections::HashSet::new();
163
164        for op in &self.ops {
165            let result_index = op.get_result_index();
166
167            if !result_indices.insert(result_index) {
168                return Err(format!("Duplicate result index: {}", result_index));
169            }
170        }
171
172        for (i, op) in self.ops.iter().enumerate() {
173            let sources = match op {
174                Op::Call { call } => {
175                    let mut sources = vec![&call.target];
176                    sources.extend(&call.args);
177                    sources
178                }
179                Op::Object { object } => object.fields.values().collect(),
180                Op::Array { array } => array.items.iter().collect(),
181            };
182
183            for source in sources {
184                if let Some(index) = source.get_result_index() {
185                    let found = self.ops[..i]
186                        .iter()
187                        .any(|prev_op| prev_op.get_result_index() == index);
188
189                    if !found {
190                        return Err(format!("Result {} referenced before being defined", index));
191                    }
192                }
193
194                if let Some(index) = source.get_capture_index() {
195                    if index as usize >= self.captures.len() {
196                        return Err(format!("Capture index {} out of bounds", index));
197                    }
198                }
199            }
200        }
201
202        self.validate_source(&self.result, self.ops.len())?;
203
204        Ok(())
205    }
206
207    fn validate_source(&self, source: &Source, _ops_count: usize) -> Result<(), String> {
208        if let Some(index) = source.get_capture_index() {
209            if index as usize >= self.captures.len() {
210                return Err(format!("Capture index {} out of bounds", index));
211            }
212        }
213
214        if let Some(index) = source.get_result_index() {
215            let found = self.ops.iter().any(|op| op.get_result_index() == index);
216
217            if !found {
218                return Err(format!("Result {} not found in ops", index));
219            }
220        }
221
222        Ok(())
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use serde_json::json;
230
231    #[test]
232    fn test_source_serialization() {
233        let sources = vec![
234            Source::capture(0),
235            Source::result(1),
236            Source::param(vec!["user".to_string(), "name".to_string()]),
237            Source::by_value(json!(42)),
238        ];
239
240        for source in sources {
241            let json = serde_json::to_string(&source).unwrap();
242            let decoded: Source = serde_json::from_str(&json).unwrap();
243            assert_eq!(source, decoded);
244        }
245    }
246
247    #[test]
248    fn test_op_serialization() {
249        let op = Op::call(
250            Source::capture(0),
251            "method".to_string(),
252            vec![Source::by_value(json!("arg"))],
253            0,
254        );
255
256        let json = serde_json::to_string(&op).unwrap();
257        let decoded: Op = serde_json::from_str(&json).unwrap();
258        assert_eq!(op, decoded);
259    }
260
261    #[test]
262    fn test_plan_validation_valid() {
263        let plan = Plan::new(
264            vec![CapId::new(1)],
265            vec![
266                Op::call(Source::capture(0), "method1".to_string(), vec![], 0),
267                Op::call(Source::result(0), "method2".to_string(), vec![], 1),
268            ],
269            Source::result(1),
270        );
271
272        assert!(plan.validate().is_ok());
273    }
274
275    #[test]
276    fn test_plan_validation_duplicate_result() {
277        let plan = Plan::new(
278            vec![CapId::new(1)],
279            vec![
280                Op::call(Source::capture(0), "method1".to_string(), vec![], 0),
281                Op::call(
282                    Source::capture(0),
283                    "method2".to_string(),
284                    vec![],
285                    0, // Duplicate!
286                ),
287            ],
288            Source::result(0),
289        );
290
291        assert!(plan.validate().is_err());
292    }
293
294    #[test]
295    fn test_plan_validation_undefined_result() {
296        let plan = Plan::new(
297            vec![CapId::new(1)],
298            vec![Op::call(
299                Source::result(99), // Undefined!
300                "method".to_string(),
301                vec![],
302                0,
303            )],
304            Source::result(0),
305        );
306
307        assert!(plan.validate().is_err());
308    }
309
310    #[test]
311    fn test_plan_validation_forward_reference() {
312        let plan = Plan::new(
313            vec![CapId::new(1)],
314            vec![
315                Op::call(
316                    Source::result(1), // Forward reference!
317                    "method1".to_string(),
318                    vec![],
319                    0,
320                ),
321                Op::call(Source::capture(0), "method2".to_string(), vec![], 1),
322            ],
323            Source::result(0),
324        );
325
326        assert!(plan.validate().is_err());
327    }
328
329    #[test]
330    fn test_plan_validation_capture_out_of_bounds() {
331        let plan = Plan::new(
332            vec![CapId::new(1)],
333            vec![Op::call(
334                Source::capture(1), // Out of bounds!
335                "method".to_string(),
336                vec![],
337                0,
338            )],
339            Source::result(0),
340        );
341
342        assert!(plan.validate().is_err());
343    }
344
345    #[test]
346    fn test_object_op() {
347        let op = Op::object(
348            BTreeMap::from([
349                ("field1".to_string(), Source::by_value(json!(123))),
350                ("field2".to_string(), Source::result(0)),
351            ]),
352            1,
353        );
354
355        let json = serde_json::to_string(&op).unwrap();
356        let decoded: Op = serde_json::from_str(&json).unwrap();
357        assert_eq!(op, decoded);
358    }
359
360    #[test]
361    fn test_array_op() {
362        let op = Op::array(
363            vec![
364                Source::by_value(json!(1)),
365                Source::by_value(json!(2)),
366                Source::result(0),
367            ],
368            1,
369        );
370
371        let json = serde_json::to_string(&op).unwrap();
372        let decoded: Op = serde_json::from_str(&json).unwrap();
373        assert_eq!(op, decoded);
374    }
375
376    #[test]
377    fn test_complex_plan() {
378        let plan = Plan::new(
379            vec![CapId::new(1), CapId::new(2)],
380            vec![
381                Op::call(Source::capture(0), "getData".to_string(), vec![], 0),
382                Op::object(
383                    BTreeMap::from([
384                        ("data".to_string(), Source::result(0)),
385                        ("extra".to_string(), Source::by_value(json!("info"))),
386                    ]),
387                    1,
388                ),
389                Op::array(vec![Source::result(1), Source::capture(1)], 2),
390            ],
391            Source::result(2),
392        );
393
394        assert!(plan.validate().is_ok());
395
396        let json = serde_json::to_string(&plan).unwrap();
397        let decoded: Plan = serde_json::from_str(&json).unwrap();
398        assert_eq!(plan, decoded);
399    }
400}