escher-execution-engine 0.1.2

Production-ready async execution engine for system commands
Documentation
//! JSON Bridge Example
//!
//! This example creates a simple stdin/stdout bridge to the execution engine.
//! It reads line-delimited JSON `ExecutionRequest` objects from stdin,
//! executes them, and writes line-delimited JSON `ExecutionResult` objects to stdout.
//!
//! Usage:
//!     cargo run --example json_bridge

use async_trait::async_trait;
use execution_engine::{
    EventHandler, ExecutionConfig, ExecutionEngine, ExecutionEvent, ExecutionRequest,
};
use std::future::Future;
use std::io::{BufRead, Write};
use std::pin::Pin;
use std::sync::{Arc, Mutex};

// Simple event handler that writes JSON events to stdout
struct JsonBridgeHandler {
    stdout: Arc<Mutex<std::io::Stdout>>,
}

impl EventHandler for JsonBridgeHandler {
    fn handle_event<'life0, 'async_trait>(
        &'life0 self,
        event: ExecutionEvent,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        Self: 'async_trait,
    {
        Box::pin(async move {
            #[derive(serde::Serialize)]
            struct BridgeEvent {
                r#type: String,
                payload: ExecutionEvent,
            }

            let bridge_event = BridgeEvent {
                r#type: "event".to_string(),
                payload: event,
            };

            if let Ok(json) = serde_json::to_string(&bridge_event) {
                // Use a block to ensure mutex lock is dropped immediately
                let result = {
                    let mut stdout = self.stdout.lock().unwrap();
                    let res = writeln!(stdout, "{}", json);
                    let _ = stdout.flush();
                    res
                };
                // Ignore write errors (e.g. pipe closed)
                let _ = result;
            }
        })
    }
}

#[tokio::main]
async fn main() {
    // Initialize engine with default config (and serial execution enforcement)
    let config = ExecutionConfig::default();

    // Create handler that writes to stdout safely
    // Note: Mutex is used because std::io::Stdout is thread-safe but we want atomic writes
    let stdout_mutex = Arc::new(Mutex::new(std::io::stdout()));
    let handler = Arc::new(JsonBridgeHandler {
        stdout: stdout_mutex,
    });

    // Using init_global_with_handler as required by the singleton pattern
    match ExecutionEngine::init_global_with_handler(config, Some(handler)) {
        Ok(_) => {}
        Err(e) => {
            eprintln!("Failed to initialize engine: {}", e);
            std::process::exit(1);
        }
    }

    let engine = ExecutionEngine::global();

    let stdin = std::io::stdin();
    let mut stdout = std::io::stdout(); // For direct responses
    let mut handle = stdin.lock();
    let mut line = String::new();

    eprintln!("JSON Bridge Ready. Send ExecutionRequest JSON on stdin.");

    while handle.read_line(&mut line).unwrap() > 0 {
        if line.trim().is_empty() {
            line.clear();
            continue;
        }

        match serde_json::from_str::<ExecutionRequest>(&line) {
            Ok(request) => {
                eprintln!("Received request: {}", request.id);

                // Execute
                match engine.execute(request.clone()).await {
                    Ok(id) => {
                        // Wait for completion
                        match engine.wait_for_completion(id).await {
                            Ok(result) => {
                                // Wrap result in response structure
                                #[derive(serde::Serialize)]
                                struct BridgeResponse {
                                    r#type: String,
                                    payload: execution_engine::ExecutionResult,
                                }

                                let response = BridgeResponse {
                                    r#type: "result".to_string(),
                                    payload: result,
                                };

                                let json = serde_json::to_string(&response).unwrap();
                                writeln!(stdout, "{}", json).unwrap();
                                stdout.flush().unwrap();
                            }
                            Err(e) => {
                                send_error(&mut stdout, &request.id.to_string(), &e.to_string());
                            }
                        }
                    }
                    Err(e) => {
                        send_error(&mut stdout, &request.id.to_string(), &e.to_string());
                    }
                }
            }
            Err(e) => {
                eprintln!("Failed to parse request: {}", e);
            }
        }

        line.clear();
    }
}

fn send_error(stdout: &mut std::io::Stdout, id: &str, error: &str) {
    let error_json = serde_json::json!({
        "type": "error",
        "payload": {
            "id": id,
            "error": error
        }
    });
    writeln!(stdout, "{}", error_json).unwrap();
    stdout.flush().unwrap();
}