//! Mock Runner Binary for Testing OpenRPC Examples //! //! This is a simple mock runner that simulates an actor binary for testing //! the Hero Supervisor OpenRPC integration. It connects to Redis, listens for //! jobs using the proper Hero job queue system, and echoes the job payload. //! //! Usage: //! ```bash //! cargo run --example mock_runner -- --actor-id test_actor --db-path /tmp/test_db --redis-url redis://localhost:6379 //! ``` use std::env; use std::time::Duration; use tokio::time::sleep; use redis::AsyncCommands; use hero_supervisor::{ job::{Job, JobStatus, JobType, keys}, }; #[derive(Debug, Clone)] pub struct MockRunnerConfig { pub actor_id: String, pub db_path: String, pub redis_url: String, } impl MockRunnerConfig { pub fn from_args() -> Result> { let args: Vec = env::args().collect(); let mut actor_id = None; let mut db_path = None; let mut redis_url = None; let mut i = 1; while i < args.len() { match args[i].as_str() { "--actor-id" => { if i + 1 < args.len() { actor_id = Some(args[i + 1].clone()); i += 2; } else { return Err("Missing value for --actor-id".into()); } } "--redis-url" => { if i + 1 < args.len() { redis_url = Some(args[i + 1].clone()); i += 2; } else { return Err("Missing value for --redis-url".into()); } } _ => i += 1, } } Ok(MockRunnerConfig { actor_id: actor_id.ok_or("Missing required --actor-id argument")?, db_path: db_path.ok_or("Missing required --db-path argument")?, redis_url: redis_url.unwrap_or_else(|| "redis://localhost:6379".to_string()), }) } } pub struct MockRunner { config: MockRunnerConfig, redis_client: redis::Client, } impl MockRunner { pub fn new(config: MockRunnerConfig) -> Result> { let redis_client = redis::Client::open(config.redis_url.clone())?; Ok(MockRunner { config, redis_client, }) } pub async fn run(&self) -> Result<(), Box> { println!("🤖 Mock Runner '{}' starting...", self.config.actor_id); println!("📂 DB Path: {}", self.config.db_path); println!("🔗 Redis URL: {}", self.config.redis_url); let mut conn = self.redis_client.get_multiplexed_async_connection().await?; // Use the proper Hero job queue key for this actor instance // Format: hero:q:work:type:{job_type}:group:{group}:inst:{instance} let work_queue_key = keys::work_instance(&JobType::OSIS, "default", &self.config.actor_id); println!("👂 Listening for jobs on queue: {}", work_queue_key); loop { // Try to pop a job ID from the work queue using the Hero protocol let result: redis::RedisResult> = conn.lpop(&work_queue_key, None).await; match result { Ok(Some(job_id)) => { println!("📨 Received job ID: {}", job_id); if let Err(e) = self.process_job(&mut conn, &job_id).await { eprintln!("❌ Error processing job {}: {}", job_id, e); // Mark job as error if let Err(e2) = Job::set_error(&mut conn, &job_id, &format!("Processing error: {}", e)).await { eprintln!("❌ Failed to set job error status: {}", e2); } } } Ok(None) => { // No jobs available, wait a bit sleep(Duration::from_millis(100)).await; } Err(e) => { eprintln!("❌ Redis error: {}", e); sleep(Duration::from_secs(1)).await; } } } } async fn process_job(&self, conn: &mut redis::aio::MultiplexedConnection, job_id: &str) -> Result<(), Box> { // Load the job from Redis using the Hero job system let job = Job::load_from_redis(conn, job_id).await?; println!("📝 Processing job: {}", job.id); println!("📝 Caller: {}", job.caller_id); println!("📝 Context: {}", job.context_id); println!("📝 Payload: {}", job.payload); println!("📝 Job Type: {:?}", job.job_type); // Mark job as started Job::update_status(conn, job_id, JobStatus::Started).await?; println!("🚀 Job {} marked as Started", job_id); // Simulate processing time sleep(Duration::from_millis(500)).await; // Echo the payload (simulate job execution) let output = format!("echo: {}", job.payload); println!("📤 Output: {}", output); // Set the job result Job::set_result(conn, job_id, &output).await?; // Mark job as finished Job::update_status(conn, job_id, JobStatus::Finished).await?; println!("✅ Job {} completed successfully", job_id); Ok(()) } } #[tokio::main] async fn main() -> Result<(), Box> { // Parse command line arguments let config = MockRunnerConfig::from_args()?; // Create and run the mock runner let runner = MockRunner::new(config)?; runner.run().await?; Ok(()) }