capnweb_core/protocol/
remap_engine.rs

1// Advanced Remap Execution Engine for Cap'n Web Protocol
2// Implements the sophisticated remap operation: ["remap", import_id, property_path, captures, instructions]
3
4use super::evaluator::ExpressionEvaluator;
5use super::expression::{CaptureRef, Expression, PropertyKey, RemapExpression};
6use super::ids::{ExportId, ImportId};
7use super::tables::{ExportTable, ExportValueRef, ImportTable, ImportValue, Value};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11/// Context for remap execution containing captured values
12#[derive(Debug, Clone)]
13pub struct RemapContext {
14    /// Captured values indexed by their capture index
15    captured_values: HashMap<usize, Value>,
16    /// Original context values for reference
17    context_imports: HashMap<ImportId, Value>,
18    context_exports: HashMap<ExportId, Value>,
19}
20
21impl Default for RemapContext {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl RemapContext {
28    pub fn new() -> Self {
29        Self {
30            captured_values: HashMap::new(),
31            context_imports: HashMap::new(),
32            context_exports: HashMap::new(),
33        }
34    }
35
36    /// Add a captured value at the specified index
37    pub fn add_capture(&mut self, index: usize, value: Value) {
38        self.captured_values.insert(index, value);
39    }
40
41    /// Get a captured value by index
42    pub fn get_capture(&self, index: usize) -> Option<&Value> {
43        self.captured_values.get(&index)
44    }
45
46    /// Add context values for reference resolution
47    pub fn add_context_import(&mut self, id: ImportId, value: Value) {
48        self.context_imports.insert(id, value);
49    }
50
51    pub fn add_context_export(&mut self, id: ExportId, value: Value) {
52        self.context_exports.insert(id, value);
53    }
54}
55
56/// Advanced Remap Execution Engine
57pub struct RemapEngine {
58    imports: Arc<ImportTable>,
59    exports: Arc<ExportTable>,
60}
61
62impl RemapEngine {
63    /// Create a new remap engine
64    pub fn new(imports: Arc<ImportTable>, exports: Arc<ExportTable>) -> Self {
65        Self { imports, exports }
66    }
67
68    /// Execute a remap expression
69    pub async fn execute_remap(
70        &self,
71        remap: &RemapExpression,
72        evaluator: &ExpressionEvaluator,
73    ) -> Result<Value, RemapError> {
74        tracing::debug!(
75            import_id = %remap.import_id,
76            captures_count = remap.captures.len(),
77            instructions_count = remap.instructions.len(),
78            "Starting remap execution"
79        );
80
81        // Step 1: Resolve the base import and property path
82        let base_value = self.resolve_base_import(remap).await?;
83        tracing::debug!("Base import resolved: {:?}", base_value);
84
85        // Step 2: Capture all referenced values
86        let mut context = RemapContext::new();
87        self.capture_values(remap, &mut context).await?;
88        tracing::debug!("Captured {} values", context.captured_values.len());
89
90        // Step 3: Execute instruction sequence
91        let result = self
92            .execute_instructions(&remap.instructions, &context, evaluator)
93            .await?;
94        tracing::debug!("Remap execution completed: {:?}", result);
95
96        Ok(result)
97    }
98
99    /// Resolve the base import value with optional property path
100    async fn resolve_base_import(&self, remap: &RemapExpression) -> Result<Value, RemapError> {
101        // Get the base import
102        let import_value = self
103            .imports
104            .get(remap.import_id)
105            .ok_or(RemapError::UnknownImport(remap.import_id))?;
106
107        let base_value = match import_value {
108            ImportValue::Value(value) => value,
109            ImportValue::Stub(_) => {
110                return Err(RemapError::UnsupportedImportType(
111                    "Stub remapping not yet implemented".to_string(),
112                ));
113            }
114            ImportValue::Promise(_) => {
115                return Err(RemapError::UnsupportedImportType(
116                    "Promise remapping not yet implemented".to_string(),
117                ));
118            }
119        };
120
121        // Apply property path if specified
122        if let Some(path) = &remap.property_path {
123            self.resolve_property_path(&base_value, path)
124        } else {
125            Ok(base_value)
126        }
127    }
128
129    /// Resolve a property path on a value
130    fn resolve_property_path(
131        &self,
132        value: &Value,
133        path: &[PropertyKey],
134    ) -> Result<Value, RemapError> {
135        let mut current = value;
136        #[allow(unused_assignments)]
137        let mut owned_value: Option<Value> = None;
138
139        for key in path {
140            match key {
141                PropertyKey::String(prop) => match current {
142                    Value::Object(obj) => {
143                        if let Some(val) = obj.get(prop) {
144                            owned_value = Some((**val).clone());
145                            current = owned_value.as_ref().expect("Just set owned_value to Some");
146                        } else {
147                            return Err(RemapError::PropertyNotFound(prop.clone()));
148                        }
149                    }
150                    _ => {
151                        return Err(RemapError::InvalidPropertyAccess(format!(
152                            "Cannot access property '{}' on non-object",
153                            prop
154                        )))
155                    }
156                },
157                PropertyKey::Number(index) => match current {
158                    Value::Array(arr) => {
159                        if *index < arr.len() {
160                            owned_value = Some(arr[*index].clone());
161                            current = owned_value.as_ref().expect("Just set owned_value to Some");
162                        } else {
163                            return Err(RemapError::IndexOutOfBounds(*index));
164                        }
165                    }
166                    _ => {
167                        return Err(RemapError::InvalidPropertyAccess(format!(
168                            "Cannot index with {} on non-array",
169                            index
170                        )))
171                    }
172                },
173            }
174        }
175
176        Ok(current.clone())
177    }
178
179    /// Capture values from imports and exports as specified
180    async fn capture_values(
181        &self,
182        remap: &RemapExpression,
183        context: &mut RemapContext,
184    ) -> Result<(), RemapError> {
185        for (index, capture_ref) in remap.captures.iter().enumerate() {
186            let captured_value = match capture_ref {
187                CaptureRef::Import(import_id) => {
188                    let import_value = self
189                        .imports
190                        .get(*import_id)
191                        .ok_or(RemapError::UnknownImport(*import_id))?;
192
193                    match import_value {
194                        ImportValue::Value(value) => value,
195                        ImportValue::Stub(_) => {
196                            return Err(RemapError::UnsupportedCaptureType(
197                                "Cannot capture stub".to_string(),
198                            ));
199                        }
200                        ImportValue::Promise(_) => {
201                            return Err(RemapError::UnsupportedCaptureType(
202                                "Cannot capture unresolved promise".to_string(),
203                            ));
204                        }
205                    }
206                }
207                CaptureRef::Export(export_id) => {
208                    let export_value = self
209                        .exports
210                        .get(*export_id)
211                        .ok_or(RemapError::UnknownExport(*export_id))?;
212
213                    match export_value {
214                        ExportValueRef::Resolved(value) => value,
215                        ExportValueRef::Rejected(error) => {
216                            return Err(RemapError::CapturedRejectedPromise(error.clone()));
217                        }
218                        ExportValueRef::Stub(_) => {
219                            return Err(RemapError::UnsupportedCaptureType(
220                                "Cannot capture stub".to_string(),
221                            ));
222                        }
223                        ExportValueRef::Promise(_) => {
224                            return Err(RemapError::UnsupportedCaptureType(
225                                "Cannot capture unresolved promise".to_string(),
226                            ));
227                        }
228                    }
229                }
230            };
231
232            context.add_capture(index, captured_value);
233        }
234
235        Ok(())
236    }
237
238    /// Execute the instruction sequence with captured values
239    async fn execute_instructions(
240        &self,
241        instructions: &[Expression],
242        context: &RemapContext,
243        evaluator: &ExpressionEvaluator,
244    ) -> Result<Value, RemapError> {
245        if instructions.is_empty() {
246            return Err(RemapError::EmptyInstructions);
247        }
248
249        let mut result = Value::Null;
250
251        for (i, instruction) in instructions.iter().enumerate() {
252            tracing::debug!("Executing instruction {}: {:?}", i, instruction);
253
254            // Replace capture references in the instruction
255            let resolved_instruction = Self::resolve_instruction_captures(instruction, context)?;
256
257            // Evaluate the instruction
258            result = evaluator
259                .evaluate(resolved_instruction)
260                .await
261                .map_err(|e| RemapError::InstructionExecutionError(i, e.to_string()))?;
262
263            tracing::debug!("Instruction {} result: {:?}", i, result);
264        }
265
266        Ok(result)
267    }
268
269    /// Resolve capture references within an instruction
270    fn resolve_instruction_captures(
271        instruction: &Expression,
272        context: &RemapContext,
273    ) -> Result<Expression, RemapError> {
274        match instruction {
275            // Handle special capture reference syntax (e.g., $0, $1, etc.)
276            Expression::String(s) if s.starts_with('$') => {
277                if let Ok(index) = s[1..].parse::<usize>() {
278                    if let Some(captured_value) = context.get_capture(index) {
279                        Ok(Self::value_to_expression(captured_value))
280                    } else {
281                        Err(RemapError::InvalidCaptureReference(index))
282                    }
283                } else {
284                    Ok(instruction.clone())
285                }
286            }
287
288            // Handle arrays recursively
289            Expression::Array(elements) => {
290                let resolved_elements: Result<Vec<Expression>, RemapError> = elements
291                    .iter()
292                    .map(|elem| Self::resolve_instruction_captures(elem, context))
293                    .collect();
294                Ok(Expression::Array(resolved_elements?))
295            }
296
297            // Handle objects recursively
298            Expression::Object(obj) => {
299                let mut resolved_obj = std::collections::HashMap::new();
300                for (key, value) in obj {
301                    let resolved_value = Self::resolve_instruction_captures(value, context)?;
302                    resolved_obj.insert(key.clone(), Box::new(resolved_value));
303                }
304                Ok(Expression::Object(resolved_obj))
305            }
306
307            // For other expression types, return as-is (more complex resolution could be added)
308            _ => Ok(instruction.clone()),
309        }
310    }
311
312    /// Convert a value back to an expression for evaluation
313    fn value_to_expression(value: &Value) -> Expression {
314        match value {
315            Value::Null => Expression::Null,
316            Value::Bool(b) => Expression::Bool(*b),
317            Value::Number(n) => Expression::Number(n.clone()),
318            Value::String(s) => Expression::String(s.clone()),
319            Value::Array(arr) => {
320                let elements = arr.iter().map(Self::value_to_expression).collect();
321                Expression::Array(elements)
322            }
323            Value::Object(obj) => {
324                let mut map = std::collections::HashMap::new();
325                for (key, val) in obj {
326                    map.insert(key.clone(), Box::new(Self::value_to_expression(val)));
327                }
328                Expression::Object(map)
329            }
330            Value::Date(timestamp) => Expression::Date(*timestamp),
331            Value::Error {
332                error_type,
333                message,
334                stack,
335            } => Expression::Error(super::expression::ErrorExpression {
336                error_type: error_type.clone(),
337                message: message.clone(),
338                stack: stack.clone(),
339            }),
340            // For complex types, create placeholder expressions
341            Value::Stub(_) | Value::Promise(_) => {
342                Expression::String("[Complex value - not serializable]".to_string())
343            }
344        }
345    }
346}
347
348/// Errors that can occur during remap execution
349#[derive(Debug, thiserror::Error)]
350pub enum RemapError {
351    #[error("Unknown import: {0}")]
352    UnknownImport(ImportId),
353
354    #[error("Unknown export: {0}")]
355    UnknownExport(ExportId),
356
357    #[error("Property not found: {0}")]
358    PropertyNotFound(String),
359
360    #[error("Index out of bounds: {0}")]
361    IndexOutOfBounds(usize),
362
363    #[error("Invalid property access: {0}")]
364    InvalidPropertyAccess(String),
365
366    #[error("Unsupported import type: {0}")]
367    UnsupportedImportType(String),
368
369    #[error("Unsupported capture type: {0}")]
370    UnsupportedCaptureType(String),
371
372    #[error("Captured rejected promise: {0:?}")]
373    CapturedRejectedPromise(Value),
374
375    #[error("Empty instruction sequence")]
376    EmptyInstructions,
377
378    #[error("Invalid capture reference: ${0}")]
379    InvalidCaptureReference(usize),
380
381    #[error("Instruction {0} execution error: {1}")]
382    InstructionExecutionError(usize, String),
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use crate::protocol::{IdAllocator, ImportValue};
389    use serde_json::Number;
390    use std::sync::Arc;
391
392    #[tokio::test]
393    async fn test_basic_remap_execution() {
394        let allocator = Arc::new(IdAllocator::new());
395        let imports = Arc::new(ImportTable::new(allocator.clone()));
396        let exports = Arc::new(ExportTable::new(allocator));
397
398        // Set up test data
399        let import_id = ImportId(1);
400        imports
401            .insert(
402                import_id,
403                ImportValue::Value(Value::Number(Number::from(42))),
404            )
405            .unwrap();
406
407        // Create remap expression: ["remap", 1, null, [], ["$0"]]
408        let remap = RemapExpression {
409            import_id,
410            property_path: None,
411            captures: vec![CaptureRef::Import(import_id)],
412            instructions: vec![Expression::String("$0".to_string())],
413        };
414
415        let engine = RemapEngine::new(imports.clone(), exports.clone());
416        let evaluator = ExpressionEvaluator::new(imports, exports);
417
418        let result = engine.execute_remap(&remap, &evaluator).await.unwrap();
419
420        match result {
421            Value::Number(n) => assert_eq!(n.as_i64(), Some(42)),
422            _ => panic!("Expected number result"),
423        }
424    }
425
426    #[tokio::test]
427    async fn test_property_path_resolution() {
428        let allocator = Arc::new(IdAllocator::new());
429        let imports = Arc::new(ImportTable::new(allocator.clone()));
430        let exports = Arc::new(ExportTable::new(allocator));
431
432        // Create object with nested properties
433        let mut obj = std::collections::HashMap::new();
434        obj.insert(
435            "user".to_string(),
436            Box::new(Value::Object({
437                let mut user_obj = std::collections::HashMap::new();
438                user_obj.insert(
439                    "name".to_string(),
440                    Box::new(Value::String("Alice".to_string())),
441                );
442                user_obj.insert("age".to_string(), Box::new(Value::Number(Number::from(30))));
443                user_obj
444            })),
445        );
446
447        let import_id = ImportId(1);
448        imports
449            .insert(import_id, ImportValue::Value(Value::Object(obj)))
450            .unwrap();
451
452        // Create remap with property path: ["remap", 1, ["user", "name"], [], ["$0"]]
453        let remap = RemapExpression {
454            import_id,
455            property_path: Some(vec![
456                PropertyKey::String("user".to_string()),
457                PropertyKey::String("name".to_string()),
458            ]),
459            captures: vec![],
460            instructions: vec![Expression::String("Alice".to_string())], // Simple return for test
461        };
462
463        let engine = RemapEngine::new(imports.clone(), exports.clone());
464        let evaluator = ExpressionEvaluator::new(imports, exports);
465
466        let result = engine.execute_remap(&remap, &evaluator).await.unwrap();
467
468        match result {
469            Value::String(s) => assert_eq!(s, "Alice"),
470            _ => panic!("Expected string result, got: {:?}", result),
471        }
472    }
473
474    #[tokio::test]
475    async fn test_capture_resolution() {
476        let allocator = Arc::new(IdAllocator::new());
477        let imports = Arc::new(ImportTable::new(allocator.clone()));
478        let exports = Arc::new(ExportTable::new(allocator));
479
480        // Set up multiple values to capture
481        let import1 = ImportId(1);
482        let import2 = ImportId(2);
483
484        imports
485            .insert(import1, ImportValue::Value(Value::Number(Number::from(10))))
486            .unwrap();
487        imports
488            .insert(import2, ImportValue::Value(Value::Number(Number::from(20))))
489            .unwrap();
490
491        // Create remap that captures both values: ["remap", 1, null, [["import", 1], ["import", 2]], [...]]
492        let remap = RemapExpression {
493            import_id: import1,
494            property_path: None,
495            captures: vec![CaptureRef::Import(import1), CaptureRef::Import(import2)],
496            instructions: vec![
497                // Simple instruction that would use captures (simplified for test)
498                Expression::Array(vec![
499                    Expression::String("$0".to_string()),
500                    Expression::String("$1".to_string()),
501                ]),
502            ],
503        };
504
505        let engine = RemapEngine::new(imports.clone(), exports.clone());
506        let evaluator = ExpressionEvaluator::new(imports, exports);
507
508        let result = engine.execute_remap(&remap, &evaluator).await.unwrap();
509
510        match result {
511            Value::Array(arr) => {
512                assert_eq!(arr.len(), 2);
513                // Note: In a real implementation, $0 and $1 would be resolved to captured values
514            }
515            _ => panic!("Expected array result"),
516        }
517    }
518}