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, ),
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), "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), "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), "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}