use hero_dispatcher::{Dispatcher, DispatcherBuilder, ScriptType}; use log::info; use redis::AsyncCommands; use std::collections::HashMap; use std::time::Duration; use tokio::time::sleep; /// Comprehensive example demonstrating the Hero Dispatcher functionality. /// /// This example shows: /// 1. Creating a dispatcher instance /// 2. Creating jobs with different configurations /// 3. Submitting jobs to the queue /// 4. Inspecting Redis entries created by the dispatcher /// 5. Running jobs and awaiting results #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); println!("šŸš€ Hero Dispatcher Demo"); println!("======================\n"); // Create dispatcher client with worker vectors per script type let dispatcher = DispatcherBuilder::new() .caller_id("demo-caller") .context_id("demo-context") .heroscript_workers(vec!["hero-worker-1".to_string(), "hero-worker-2".to_string()]) .rhai_sal_workers(vec!["rhai-sal-worker-1".to_string()]) .rhai_dsl_workers(vec!["rhai-dsl-worker-1".to_string()]) .redis_url("redis://127.0.0.1/") .build()?; println!("āœ… Dispatcher created with:"); println!(" - Caller ID: demo-caller"); println!(" - Worker ID: demo-worker"); println!(" - Context ID: demo-context\n"); // Create Redis connection for inspection let redis_client = redis::Client::open("redis://127.0.0.1:6379")?; let mut redis_conn = redis_client.get_multiplexed_async_connection().await?; // Demo 1: Create a simple job println!("šŸ“ Demo 1: Creating a simple job"); println!("--------------------------------"); let job1 = dispatcher .new_job() .script_type(ScriptType::HeroScript) .script(r#"print("Hello from job 1!");"#) .timeout(Duration::from_secs(10)) .build()?; println!("Job 1 created with ID: {}", job1.id); // Create the job (stores in Redis) dispatcher.create_job(&job1).await?; println!("āœ… Job 1 stored in Redis"); // Inspect Redis entries for this job print_job_redis_entries(&mut redis_conn, &job1.id).await?; println!(); // Demo 2: Create a job with custom settings println!("šŸ“ Demo 2: Creating a job with custom settings"); println!("----------------------------------------------"); let job2 = dispatcher .new_job() .script_type(ScriptType::RhaiSAL) .script(r#" let result = 42 * 2; print("Calculation result: " + result); result "#) .timeout(Duration::from_secs(30)) .build()?; println!("Job 2 created with ID: {}", job2.id); // Create the job dispatcher.create_job(&job2).await?; println!("āœ… Job 2 stored in Redis"); // Inspect Redis entries print_job_redis_entries(&mut redis_conn, &job2.id).await?; println!(); // Demo 3: Environment Variables println!("šŸ“ Demo 3: Jobs with Environment Variables"); println!("------------------------------------------"); // Create environment variables map let mut env_vars = HashMap::new(); env_vars.insert("API_KEY".to_string(), "secret-api-key-123".to_string()); env_vars.insert("DEBUG_MODE".to_string(), "true".to_string()); env_vars.insert("MAX_RETRIES".to_string(), "5".to_string()); env_vars.insert("SERVICE_URL".to_string(), "https://api.example.com".to_string()); let job_with_env = dispatcher .new_job() .script_type(ScriptType::HeroScript) .script(r#" print("Environment variables available:"); print("API_KEY: " + env.API_KEY); print("DEBUG_MODE: " + env.DEBUG_MODE); print("MAX_RETRIES: " + env.MAX_RETRIES); print("SERVICE_URL: " + env.SERVICE_URL); "Environment variables processed successfully" "#) .env_vars(env_vars.clone()) .timeout(Duration::from_secs(15)) .build()?; println!("Job with environment variables created: {}", job_with_env.id); // Store job in Redis dispatcher.create_job(&job_with_env).await?; println!("āœ… Job with env vars stored in Redis"); // Show Redis entries including environment variables print_job_redis_entries(&mut redis_conn, &job_with_env.id).await?; // Demonstrate individual env var setting let job_individual_env = dispatcher .new_job() .script_type(ScriptType::RhaiSAL) .script("print('Single env var: ' + env.SINGLE_VAR); 'done'") .env_var("SINGLE_VAR", "individual-value") .env_var("ANOTHER_VAR", "another-value") .build()?; println!("Job with individual env vars created: {}", job_individual_env.id); dispatcher.create_job(&job_individual_env).await?; println!("āœ… Job with individual env vars stored in Redis"); print_job_redis_entries(&mut redis_conn, &job_individual_env.id).await?; println!(); // Demo 4: Create multiple jobs and show queue state println!("šŸ“ Demo 4: Creating multiple jobs and inspecting queue"); println!("----------------------------------------------------"); let mut job_ids = Vec::new(); for i in 3..=5 { let script_type = match i { 3 => ScriptType::HeroScript, 4 => ScriptType::RhaiSAL, 5 => ScriptType::RhaiDSL, _ => ScriptType::HeroScript, }; let job = dispatcher .new_job() .script_type(script_type) .script(&format!(r#"print("Job {} is running");"#, i)) .timeout(Duration::from_secs(15)) .build()?; job_ids.push(job.id.clone()); dispatcher.create_job(&job).await?; println!("āœ… Job {} created with ID: {}", i, job.id); } // Show all Redis keys related to our jobs print_all_dispatcher_redis_keys(&mut redis_conn).await?; println!(); // Demo 4: Show job status checking println!("šŸ“ Demo 4: Checking job statuses"); println!("--------------------------------"); for job_id in &job_ids { match dispatcher.get_job_status(job_id).await { Ok(status) => println!("Job {}: {:?}", job_id, status), Err(e) => println!("Error getting status for job {}: {}", job_id, e), } } println!(); // Demo 5: Simulate running a job and getting result (if worker is available) println!("šŸ“ Demo 5: Attempting to run job and await result"); println!("------------------------------------------------"); let simple_job = dispatcher .new_job() .script_type(ScriptType::HeroScript) .script(r#"print("This job will complete quickly"); "success""#) .timeout(Duration::from_secs(5)) .build()?; println!("Created job for execution: {}", simple_job.id); // Try to run the job (this will timeout if no worker is available) match dispatcher.run_job_and_await_result(&simple_job).await { Ok(result) => { println!("āœ… Job completed successfully!"); println!("Result: {}", result); } Err(e) => { println!("āš ļø Job execution failed (likely no worker available): {}", e); println!(" This is expected if no Hero worker is running"); } } // Demo 6: List all jobs println!("šŸ“ Demo 6: Listing all jobs"); println!("-------------------------"); let all_job_ids = match dispatcher.list_jobs().await { Ok(job_ids) => { println!("Found {} jobs:", job_ids.len()); for job_id in &job_ids { println!(" - {}", job_id); } job_ids } Err(e) => { println!("Error listing jobs: {}", e); Vec::new() } }; println!(); // Demo 7: Create a job with log path and demonstrate logs functionality println!("šŸ“ Demo 7: Job with log path and logs retrieval"); println!("-----------------------------------------------"); let log_job = dispatcher .new_job() .script(r#"print("This job writes to logs"); "log_test""#) .log_path("/tmp/hero_job_demo.log") .timeout(Duration::from_secs(10)) .build()?; println!("Created job with log path: {}", log_job.id); dispatcher.create_job(&log_job).await?; // Try to get logs (will be empty since job hasn't run) match dispatcher.get_job_logs(&log_job.id).await { Ok(Some(logs)) => println!("Job logs: {}", logs), Ok(None) => println!("No logs available for job (expected - job hasn't run or no log file)"), Err(e) => println!("Error getting logs: {}", e), } println!(); // Demo 8: Stop job functionality println!("šŸ“ Demo 8: Stopping a job"); println!("-------------------------"); if let Some(job_id) = all_job_ids.first() { println!("Attempting to stop job: {}", job_id); match dispatcher.stop_job(job_id).await { Ok(()) => println!("āœ… Stop request sent for job {}", job_id), Err(e) => println!("Error stopping job: {}", e), } // Show stop queue let stop_queue_key = "hero:stop_queue:demo-worker"; let stop_queue_length: i64 = redis_conn.llen(stop_queue_key).await?; println!("šŸ“¤ Stop queue length ({}): {}", stop_queue_key, stop_queue_length); if stop_queue_length > 0 { let stop_items: Vec = redis_conn.lrange(stop_queue_key, 0, -1).await?; println!("šŸ“‹ Stop queue items:"); for (i, item) in stop_items.iter().enumerate() { println!(" {}: {}", i, item); } } } else { println!("No jobs available to stop"); } println!(); // Demo 9: Final Redis state inspection println!("šŸ“ Demo 9: Final Redis state"); println!("----------------------------"); print_all_dispatcher_redis_keys(&mut redis_conn).await?; for job_id in &job_ids { match dispatcher.get_job_status(job_id).await { Ok(status) => println!("Job {}: {:?}", job_id, status), Err(e) => println!("Error getting status for job {}: {}", job_id, e), } } println!(); // Demo 5: Simulate running a job and getting result (if worker is available) println!("šŸ“ Demo 5: Attempting to run job and await result"); println!("------------------------------------------------"); let simple_job = dispatcher .new_job() .script_type(ScriptType::HeroScript) .script(r#"print("This job will complete quickly"); "success""#) .timeout(Duration::from_secs(5)) .build()?; println!("Created job for execution: {}", simple_job.id); // Try to run the job (this will timeout if no worker is available) match dispatcher.run_job_and_await_result(&simple_job).await { Ok(result) => { println!("āœ… Job completed successfully!"); println!("Result: {}", result); } Err(e) => { println!("āš ļø Job execution failed (likely no worker available): {}", e); println!(" This is expected if no Hero worker is running"); } } // Demo 6: List all jobs println!("šŸ“ Demo 6: Listing all jobs"); println!("-------------------------"); let all_job_ids = match dispatcher.list_jobs().await { Ok(job_ids) => { println!("Found {} jobs:", job_ids.len()); for job_id in &job_ids { println!(" - {}", job_id); } job_ids } Err(e) => { println!("Error listing jobs: {}", e); Vec::new() } }; println!(); // Demo 7: Create a job with log path and demonstrate logs functionality println!("šŸ“ Demo 7: Job with log path and logs retrieval"); println!("-----------------------------------------------"); let log_job = dispatcher .new_job() .script(r#"print("This job writes to logs"); "log_test""#) .log_path("/tmp/hero_job_demo.log") .timeout(Duration::from_secs(10)) .build()?; println!("Created job with log path: {}", log_job.id); dispatcher.create_job(&log_job).await?; // Try to get logs (will be empty since job hasn't run) match dispatcher.get_job_logs(&log_job.id).await { Ok(Some(logs)) => println!("Job logs: {}", logs), Ok(None) => println!("No logs available for job (expected - job hasn't run or no log file)"), Err(e) => println!("Error getting logs: {}", e), } println!(); // Demo 8: Stop job functionality println!("šŸ“ Demo 8: Stopping a job"); println!("-------------------------"); if let Some(job_id) = all_job_ids.first() { println!("Attempting to stop job: {}", job_id); match dispatcher.stop_job(job_id).await { Ok(()) => println!("āœ… Stop request sent for job {}", job_id), Err(e) => println!("Error stopping job: {}", e), } // Show stop queue let stop_queue_key = "hero:stop_queue:demo-worker"; let stop_queue_length: i64 = redis_conn.llen(stop_queue_key).await?; println!("šŸ“¤ Stop queue length ({}): {}", stop_queue_key, stop_queue_length); if stop_queue_length > 0 { let stop_items: Vec = redis_conn.lrange(stop_queue_key, 0, -1).await?; println!("šŸ“‹ Stop queue items:"); for (i, item) in stop_items.iter().enumerate() { println!(" {}: {}", i, item); } } } else { println!("No jobs available to stop"); } println!(); // Demo 9: Final Redis state inspection println!("šŸ“ Demo 9: Final Redis state"); println!("----------------------------"); print_all_dispatcher_redis_keys(&mut redis_conn).await?; println!("\nšŸŽ‰ Dispatcher demo completed!"); println!("šŸ’” New features demonstrated:"); println!(" - list_jobs(): List all job IDs"); println!(" - stop_job(): Send stop request to worker"); println!(" - get_job_logs(): Retrieve job logs from file"); println!(" - log_path(): Configure log file for jobs"); println!("šŸ’” To see job execution in action, start a Hero worker that processes the 'demo-worker' queue"); // Demo 6: Demonstrate new job management features println!("šŸ“ Demo 6: Job Management - Delete and Clear Operations"); println!("--------------------------------------------------------"); // List all current jobs match dispatcher.list_jobs().await { Ok(jobs) => { println!("Current jobs in system: {:?}", jobs); if !jobs.is_empty() { // Delete the first job as an example let job_to_delete = &jobs[0]; println!("Deleting job: {}", job_to_delete); match dispatcher.delete_job(job_to_delete).await { Ok(()) => println!("āœ… Job {} deleted successfully", job_to_delete), Err(e) => println!("āŒ Error deleting job {}: {}", job_to_delete, e), } // Show updated job list match dispatcher.list_jobs().await { Ok(remaining_jobs) => println!("Remaining jobs: {:?}", remaining_jobs), Err(e) => println!("Error listing jobs: {}", e), } } } Err(e) => println!("Error listing jobs: {}", e), } println!(); // Demonstrate clear all jobs println!("Clearing all remaining jobs..."); match dispatcher.clear_all_jobs().await { Ok(count) => println!("āœ… Cleared {} jobs from Redis", count), Err(e) => println!("āŒ Error clearing jobs: {}", e), } // Verify all jobs are cleared match dispatcher.list_jobs().await { Ok(jobs) => { if jobs.is_empty() { println!("āœ… All jobs successfully cleared from Redis"); } else { println!("āš ļø Some jobs remain: {:?}", jobs); } } Err(e) => println!("Error verifying job clearance: {}", e), } println!(); println!("šŸŽ‰ Demo completed! The dispatcher now supports:"); println!(" • Script type routing (HeroScript, RhaiSAL, RhaiDSL)"); println!(" • Multiple workers per script type for load balancing"); println!(" • Automatic worker selection based on job script type"); println!(" • Job management: list, delete, and clear operations"); println!(" • Enhanced job logging and monitoring"); Ok(()) } /// Print Redis entries for a specific job async fn print_job_redis_entries( conn: &mut redis::aio::MultiplexedConnection, job_id: &str, ) -> Result<(), redis::RedisError> { let job_key = format!("hero:job:{}", job_id); println!("šŸ” Redis entries for job {}:", job_id); // Check if job hash exists let exists: bool = conn.exists(&job_key).await?; if exists { // Check if the key is actually a hash before trying to get all fields let key_type: String = redis::cmd("TYPE").arg(&job_key).query_async(conn).await?; if key_type == "hash" { let job_data: std::collections::HashMap = conn.hgetall(&job_key).await?; println!(" šŸ“‹ Job data ({}): ", job_key); for (field, value) in job_data { println!(" {}: {}", field, value); } } else { println!(" āš ļø Key {} exists but is not a hash (type: {})", job_key, key_type); } } else { println!(" āŒ No job data found at key: {}", job_key); } // Check work queue let queue_key = "hero:work_queue:demo-worker"; let queue_length: i64 = conn.llen(queue_key).await?; println!(" šŸ“¤ Work queue length ({}): {}", queue_key, queue_length); if queue_length > 0 { let queue_items: Vec = conn.lrange(queue_key, 0, -1).await?; println!(" šŸ“‹ Queue items:"); for (i, item) in queue_items.iter().enumerate() { println!(" {}: {}", i, item); } } Ok(()) } /// Print all dispatcher-related Redis keys async fn print_all_dispatcher_redis_keys( conn: &mut redis::aio::MultiplexedConnection, ) -> Result<(), redis::RedisError> { println!("šŸ” All Hero Dispatcher Redis keys:"); // Get all keys with hero: prefix let keys: Vec = conn.keys("hero:*").await?; if keys.is_empty() { println!(" āŒ No Hero keys found in Redis"); return Ok(()); } // Group keys by type let mut job_keys = Vec::new(); let mut queue_keys = Vec::new(); let mut other_keys = Vec::new(); for key in keys { if key.starts_with("hero:job:") { job_keys.push(key); } else if key.contains("queue") { queue_keys.push(key); } else { other_keys.push(key); } } // Print job keys if !job_keys.is_empty() { println!(" šŸ“‹ Job entries:"); for key in job_keys { // Check if the key is actually a hash before trying to get all fields let key_type: String = redis::cmd("TYPE").arg(&key).query_async(conn).await?; if key_type == "hash" { let job_data: std::collections::HashMap = conn.hgetall(&key).await?; println!(" {}: {} fields", key, job_data.len()); } else { println!(" {}: {} (not a hash, skipping)", key, key_type); } } } // Print queue keys if !queue_keys.is_empty() { println!(" šŸ“¤ Queue entries:"); for key in queue_keys { let length: i64 = conn.llen(&key).await?; println!(" {}: {} items", key, length); } } // Print other keys if !other_keys.is_empty() { println!(" šŸ”§ Other entries:"); for key in other_keys { println!(" {}", key); } } Ok(()) }