From 3b1bc9a8387899663a65668ad2a00e8ce3453f5d Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:38:47 +0200 Subject: [PATCH] rename client and move incomplete projects to research --- .../new_create_payment_intent_error.rhai | 38 +++++ .../new_create_payment_intent_response.rhai | 34 +++++ {src => research}/repl/.gitignore | 0 {src => research}/repl/.rhai_repl_history.txt | 0 {src => research}/repl/Cargo.lock | 0 {src => research}/repl/Cargo.toml | 4 +- {src => research}/repl/README.md | 0 {src => research}/repl/docs/ARCHITECTURE.md | 0 .../repl/examples/connect_and_play.rs | 24 ++-- {src => research}/repl/src/main.rs | 24 ++-- {src => research}/rhai_engine_ui/.gitignore | 0 {src => research}/rhai_engine_ui/Cargo.toml | 0 {src => research}/rhai_engine_ui/README.md | 0 {src => research}/rhai_engine_ui/Trunk.toml | 0 .../rhai_engine_ui/docs/ARCHITECTURE.md | 0 {src => research}/rhai_engine_ui/index.html | 0 {src => research}/rhai_engine_ui/src/app.rs | 0 {src => research}/rhai_engine_ui/src/main.rs | 0 {src => research}/rhai_engine_ui/styles.css | 0 src/{client => dispatcher}/.gitignore | 0 src/{client => dispatcher}/Cargo.toml | 6 +- src/{client => dispatcher}/README.md | 16 +-- src/{client => dispatcher}/cmd/README.md | 2 +- .../cmd/dispatcher.rs} | 43 +++--- .../docs/ARCHITECTURE.md | 16 +-- .../examples/timeout_example.rs | 14 +- src/{client => dispatcher}/src/lib.rs | 134 +++++++++++------- src/repl/.DS_Store | Bin 6148 -> 0 bytes 28 files changed, 229 insertions(+), 126 deletions(-) create mode 100644 examples/flows/new_create_payment_intent_error.rhai create mode 100644 examples/flows/new_create_payment_intent_response.rhai rename {src => research}/repl/.gitignore (100%) rename {src => research}/repl/.rhai_repl_history.txt (100%) rename {src => research}/repl/Cargo.lock (100%) rename {src => research}/repl/Cargo.toml (90%) rename {src => research}/repl/README.md (100%) rename {src => research}/repl/docs/ARCHITECTURE.md (100%) rename {src => research}/repl/examples/connect_and_play.rs (88%) rename {src => research}/repl/src/main.rs (91%) rename {src => research}/rhai_engine_ui/.gitignore (100%) rename {src => research}/rhai_engine_ui/Cargo.toml (100%) rename {src => research}/rhai_engine_ui/README.md (100%) rename {src => research}/rhai_engine_ui/Trunk.toml (100%) rename {src => research}/rhai_engine_ui/docs/ARCHITECTURE.md (100%) rename {src => research}/rhai_engine_ui/index.html (100%) rename {src => research}/rhai_engine_ui/src/app.rs (100%) rename {src => research}/rhai_engine_ui/src/main.rs (100%) rename {src => research}/rhai_engine_ui/styles.css (100%) rename src/{client => dispatcher}/.gitignore (100%) rename src/{client => dispatcher}/Cargo.toml (89%) rename src/{client => dispatcher}/README.md (83%) rename src/{client => dispatcher}/cmd/README.md (97%) rename src/{client/cmd/client.rs => dispatcher/cmd/dispatcher.rs} (83%) rename src/{client => dispatcher}/docs/ARCHITECTURE.md (90%) rename src/{client => dispatcher}/examples/timeout_example.rs (85%) rename src/{client => dispatcher}/src/lib.rs (84%) delete mode 100644 src/repl/.DS_Store diff --git a/examples/flows/new_create_payment_intent_error.rhai b/examples/flows/new_create_payment_intent_error.rhai new file mode 100644 index 0000000..6f32d82 --- /dev/null +++ b/examples/flows/new_create_payment_intent_error.rhai @@ -0,0 +1,38 @@ +// Error handler for failed payment intent creation +// This script is triggered when a payment intent creation fails + +print("❌ Payment Intent Creation Failed!"); +print("=================================="); + +// The error data is available as 'parsed_error' +if parsed_error != () { + print(`Error: ${parsed_error}`); + + // You can handle different types of errors + if parsed_error.contains("authentication") { + print("🔑 Authentication error - check API key"); + // eval_file("flows/handle_auth_error.rhai"); + } else if parsed_error.contains("insufficient_funds") { + print("💰 Insufficient funds error"); + // eval_file("flows/handle_insufficient_funds.rhai"); + } else if parsed_error.contains("card_declined") { + print("💳 Card declined error"); + // eval_file("flows/handle_card_declined.rhai"); + } else { + print("⚠️ General payment error"); + // eval_file("flows/handle_general_payment_error.rhai"); + } + + // Log the error for monitoring + print("📊 Logging error for analytics..."); + // eval_file("flows/log_payment_error.rhai"); + + // Notify relevant parties + print("📧 Sending error notifications..."); + // eval_file("flows/send_error_notification.rhai"); + +} else { + print("⚠️ No error data received"); +} + +print("🔄 Error handling complete!"); \ No newline at end of file diff --git a/examples/flows/new_create_payment_intent_response.rhai b/examples/flows/new_create_payment_intent_response.rhai new file mode 100644 index 0000000..d848a47 --- /dev/null +++ b/examples/flows/new_create_payment_intent_response.rhai @@ -0,0 +1,34 @@ +// Response handler for successful payment intent creation +// This script is triggered when a payment intent is successfully created + +print("✅ Payment Intent Created Successfully!"); +print("====================================="); + +// The response data is available as 'parsed_data' +if parsed_data != () { + print(`Payment Intent ID: ${parsed_data.id}`); + print(`Amount: ${parsed_data.amount}`); + print(`Currency: ${parsed_data.currency}`); + print(`Status: ${parsed_data.status}`); + + if parsed_data.client_secret != () { + print(`Client Secret: ${parsed_data.client_secret}`); + } + + // You can now trigger additional workflows + print("🔄 Triggering next steps..."); + + // Example: Send confirmation email + // eval_file("flows/send_payment_confirmation_email.rhai"); + + // Example: Update user account + // eval_file("flows/update_user_payment_status.rhai"); + + // Example: Log analytics event + // eval_file("flows/log_payment_analytics.rhai"); + +} else { + print("⚠️ No response data received"); +} + +print("🎉 Payment intent response processing complete!"); \ No newline at end of file diff --git a/src/repl/.gitignore b/research/repl/.gitignore similarity index 100% rename from src/repl/.gitignore rename to research/repl/.gitignore diff --git a/src/repl/.rhai_repl_history.txt b/research/repl/.rhai_repl_history.txt similarity index 100% rename from src/repl/.rhai_repl_history.txt rename to research/repl/.rhai_repl_history.txt diff --git a/src/repl/Cargo.lock b/research/repl/Cargo.lock similarity index 100% rename from src/repl/Cargo.lock rename to research/repl/Cargo.lock diff --git a/src/repl/Cargo.toml b/research/repl/Cargo.toml similarity index 90% rename from src/repl/Cargo.toml rename to research/repl/Cargo.toml index f23ff54..9079656 100644 --- a/src/repl/Cargo.toml +++ b/research/repl/Cargo.toml @@ -8,11 +8,11 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync" url = "2" # For parsing Redis URL tracing = "0.1" # For logging tracing-subscriber = { version = "0.3", features = ["env-filter"] } -log = "0.4" # rhai_client uses log crate +log = "0.4" # rhai_dispatcher uses log crate rustyline = { version = "13.0.0", features = ["derive"] } # For enhanced REPL input tempfile = "3.8" # For creating temporary files for editing -rhai_client = { path = "../client" } +rhai_dispatcher = { path = "../client" } anyhow = "1.0" # For simpler error handling rhailib_worker = { path = "../worker", package = "rhailib_worker" } diff --git a/src/repl/README.md b/research/repl/README.md similarity index 100% rename from src/repl/README.md rename to research/repl/README.md diff --git a/src/repl/docs/ARCHITECTURE.md b/research/repl/docs/ARCHITECTURE.md similarity index 100% rename from src/repl/docs/ARCHITECTURE.md rename to research/repl/docs/ARCHITECTURE.md diff --git a/src/repl/examples/connect_and_play.rs b/research/repl/examples/connect_and_play.rs similarity index 88% rename from src/repl/examples/connect_and_play.rs rename to research/repl/examples/connect_and_play.rs index 698f025..2568d5c 100644 --- a/src/repl/examples/connect_and_play.rs +++ b/research/repl/examples/connect_and_play.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use rhai_client::{RhaiClient, RhaiClientError, RhaiTaskDetails}; +use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherError, RhaiTaskDetails}; use std::env; use std::sync::Arc; use std::time::Duration; @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box> { .with_env_filter( EnvFilter::from_default_env() .add_directive("connect_and_play=info".parse().unwrap()) - .add_directive("rhai_client=info".parse().unwrap()), + .add_directive("rhai_dispatcher=info".parse().unwrap()), ) .init(); @@ -94,12 +94,12 @@ async fn main() -> Result<(), Box> { tokio::time::sleep(Duration::from_secs(1)).await; println!( - "Initializing RhaiClient for Redis at {} to target worker '{}'...", + "Initializing RhaiDispatcher for Redis at {} to target worker '{}'...", redis_url, worker_name ); - let client = RhaiClient::new(&redis_url) - .with_context(|| format!("Failed to create RhaiClient for Redis URL: {}", redis_url))?; - println!("RhaiClient initialized."); + let client = RhaiDispatcher::new(&redis_url) + .with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?; + println!("RhaiDispatcher initialized."); let script = "let a = 10; let b = 32; let message = \"Hello from example script!\"; message + \" Result: \" + (a + b)"; println!("\nSending script:\n```rhai\n{}\n```", script); @@ -125,28 +125,28 @@ async fn main() -> Result<(), Box> { } } Err(e) => match e { - RhaiClientError::Timeout(task_id) => { + RhaiDispatcherError::Timeout(task_id) => { eprintln!( "\nError: Script execution timed out for task_id: {}.", task_id ); } - RhaiClientError::RedisError(redis_err) => { + RhaiDispatcherError::RedisError(redis_err) => { eprintln!( "\nError: Redis communication failed: {}. Check Redis connection and server status.", redis_err ); } - RhaiClientError::SerializationError(serde_err) => { + RhaiDispatcherError::SerializationError(serde_err) => { eprintln!( "\nError: Failed to serialize/deserialize task data: {}.", serde_err ); } - RhaiClientError::TaskNotFound(task_id) => { + RhaiDispatcherError::TaskNotFound(task_id) => { eprintln!("\nError: Task {} not found after submission.", task_id); - } /* All RhaiClientError variants are handled, so _ arm is not strictly needed - unless RhaiClientError becomes non-exhaustive in the future. */ + } /* All RhaiDispatcherError variants are handled, so _ arm is not strictly needed + unless RhaiDispatcherError becomes non-exhaustive in the future. */ }, } diff --git a/src/repl/src/main.rs b/research/repl/src/main.rs similarity index 91% rename from src/repl/src/main.rs rename to research/repl/src/main.rs index caea646..bd0e87a 100644 --- a/src/repl/src/main.rs +++ b/research/repl/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use rhai_client::{RhaiClient, RhaiClientBuilder, RhaiClientError}; +use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder, RhaiDispatcherError}; use rustyline::error::ReadlineError; use rustyline::{Config, DefaultEditor, EditMode}; use std::env; @@ -12,7 +12,7 @@ use tracing_subscriber::EnvFilter; // Default timeout for script execution const DEFAULT_SCRIPT_TIMEOUT_SECONDS: u64 = 30; -async fn execute_script(client: &RhaiClient, circle_name: &str, script_content: String) { +async fn execute_script(client: &RhaiDispatcher, circle_name: &str, script_content: String) { if script_content.trim().is_empty() { println!("Script is empty, not sending."); return; @@ -47,25 +47,25 @@ async fn execute_script(client: &RhaiClient, circle_name: &str, script_content: } } Err(e) => match e { - RhaiClientError::Timeout(task_id) => { + RhaiDispatcherError::Timeout(task_id) => { eprintln!( "Error: Script execution timed out for task_id: {}.", task_id ); } - RhaiClientError::RedisError(redis_err) => { + RhaiDispatcherError::RedisError(redis_err) => { eprintln!( "Error: Redis communication failed: {}. Check Redis connection and server status.", redis_err ); } - RhaiClientError::SerializationError(serde_err) => { + RhaiDispatcherError::SerializationError(serde_err) => { eprintln!( "Error: Failed to serialize/deserialize task data: {}.", serde_err ); } - RhaiClientError::TaskNotFound(task_id) => { + RhaiDispatcherError::TaskNotFound(task_id) => { eprintln!( "Error: Task {} not found after submission (this should be rare).", task_id @@ -81,15 +81,15 @@ async fn run_repl(redis_url: String, circle_name: String) -> anyhow::Result<()> circle_name, redis_url ); - let client = RhaiClientBuilder::new() + let client = RhaiDispatcherBuilder::new() .redis_url(&redis_url) .caller_id("ui_repl") // Set a caller_id .build() - .with_context(|| format!("Failed to create RhaiClient for Redis URL: {}", redis_url))?; + .with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?; - // No explicit connect() needed for rhai_client, connection is handled per-operation or pooled. + // No explicit connect() needed for rhai_dispatcher, connection is handled per-operation or pooled. println!( - "RhaiClient initialized. Ready to send scripts to worker '{}'.", + "RhaiDispatcher initialized. Ready to send scripts to worker '{}'.", circle_name ); println!( @@ -212,7 +212,7 @@ async fn run_repl(redis_url: String, circle_name: String) -> anyhow::Result<()> // Failed to save history, not critical } - // No explicit disconnect for RhaiClient as it manages connections internally. + // No explicit disconnect for RhaiDispatcher as it manages connections internally. println!("Exited REPL."); Ok(()) } @@ -223,7 +223,7 @@ async fn main() -> anyhow::Result<()> { .with_env_filter( EnvFilter::from_default_env() .add_directive("ui_repl=info".parse()?) - .add_directive("rhai_client=info".parse()?), + .add_directive("rhai_dispatcher=info".parse()?), ) .init(); diff --git a/src/rhai_engine_ui/.gitignore b/research/rhai_engine_ui/.gitignore similarity index 100% rename from src/rhai_engine_ui/.gitignore rename to research/rhai_engine_ui/.gitignore diff --git a/src/rhai_engine_ui/Cargo.toml b/research/rhai_engine_ui/Cargo.toml similarity index 100% rename from src/rhai_engine_ui/Cargo.toml rename to research/rhai_engine_ui/Cargo.toml diff --git a/src/rhai_engine_ui/README.md b/research/rhai_engine_ui/README.md similarity index 100% rename from src/rhai_engine_ui/README.md rename to research/rhai_engine_ui/README.md diff --git a/src/rhai_engine_ui/Trunk.toml b/research/rhai_engine_ui/Trunk.toml similarity index 100% rename from src/rhai_engine_ui/Trunk.toml rename to research/rhai_engine_ui/Trunk.toml diff --git a/src/rhai_engine_ui/docs/ARCHITECTURE.md b/research/rhai_engine_ui/docs/ARCHITECTURE.md similarity index 100% rename from src/rhai_engine_ui/docs/ARCHITECTURE.md rename to research/rhai_engine_ui/docs/ARCHITECTURE.md diff --git a/src/rhai_engine_ui/index.html b/research/rhai_engine_ui/index.html similarity index 100% rename from src/rhai_engine_ui/index.html rename to research/rhai_engine_ui/index.html diff --git a/src/rhai_engine_ui/src/app.rs b/research/rhai_engine_ui/src/app.rs similarity index 100% rename from src/rhai_engine_ui/src/app.rs rename to research/rhai_engine_ui/src/app.rs diff --git a/src/rhai_engine_ui/src/main.rs b/research/rhai_engine_ui/src/main.rs similarity index 100% rename from src/rhai_engine_ui/src/main.rs rename to research/rhai_engine_ui/src/main.rs diff --git a/src/rhai_engine_ui/styles.css b/research/rhai_engine_ui/styles.css similarity index 100% rename from src/rhai_engine_ui/styles.css rename to research/rhai_engine_ui/styles.css diff --git a/src/client/.gitignore b/src/dispatcher/.gitignore similarity index 100% rename from src/client/.gitignore rename to src/dispatcher/.gitignore diff --git a/src/client/Cargo.toml b/src/dispatcher/Cargo.toml similarity index 89% rename from src/client/Cargo.toml rename to src/dispatcher/Cargo.toml index 4e756f2..6761a16 100644 --- a/src/client/Cargo.toml +++ b/src/dispatcher/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "rhai_client" +name = "rhai_dispatcher" version = "0.1.0" edition = "2021" [[bin]] -name = "client" -path = "cmd/client.rs" +name = "dispatcher" +path = "cmd/dispatcher.rs" [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/src/client/README.md b/src/dispatcher/README.md similarity index 83% rename from src/client/README.md rename to src/dispatcher/README.md index ac1ea5e..b583c90 100644 --- a/src/client/README.md +++ b/src/dispatcher/README.md @@ -4,7 +4,7 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting ## Features -- **Fluent Builder API**: A `RhaiClientBuilder` for easy client configuration and a `PlayRequestBuilder` for constructing and submitting script execution requests. +- **Fluent Builder API**: A `RhaiDispatcherBuilder` for easy client configuration and a `PlayRequestBuilder` for constructing and submitting script execution requests. - **Asynchronous Operations**: Built with `tokio` for non-blocking I/O. - **Request-Reply Pattern**: Submits tasks and awaits results on a dedicated reply queue, eliminating the need for polling. - **Configurable Timeouts**: Set timeouts for how long the client should wait for a task to complete. @@ -13,8 +13,8 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting ## Core Components -- **`RhaiClientBuilder`**: A builder to construct a `RhaiClient`. Requires a `caller_id` and Redis URL. -- **`RhaiClient`**: The main client for interacting with the task system. It's used to create `PlayRequestBuilder` instances. +- **`RhaiDispatcherBuilder`**: A builder to construct a `RhaiDispatcher`. Requires a `caller_id` and Redis URL. +- **`RhaiDispatcher`**: The main client for interacting with the task system. It's used to create `PlayRequestBuilder` instances. - **`PlayRequestBuilder`**: A fluent builder for creating and dispatching a script execution request. You can set: - `worker_id`: The ID of the worker queue to send the task to. - `script` or `script_path`: The Rhai script to execute. @@ -24,11 +24,11 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting - `submit()`: Submits the request and returns immediately (fire-and-forget). - `await_response()`: Submits the request and waits for the result or a timeout. - **`RhaiTaskDetails`**: A struct representing the details of a task, including its script, status (`pending`, `processing`, `completed`, `error`), output, and error messages. -- **`RhaiClientError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts. +- **`RhaiDispatcherError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts. ## How It Works -1. A `RhaiClient` is created using the `RhaiClientBuilder`, configured with a `caller_id` and Redis URL. +1. A `RhaiDispatcher` is created using the `RhaiDispatcherBuilder`, configured with a `caller_id` and Redis URL. 2. A `PlayRequestBuilder` is created from the client. 3. The script, `worker_id`, and an optional `timeout` are configured on the builder. 4. When `await_response()` is called: @@ -48,7 +48,7 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting The following example demonstrates how to build a client, submit a script, and wait for the result. ```rust -use rhai_client::{RhaiClientBuilder, RhaiClientError}; +use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError}; use std::time::Duration; #[tokio::main] @@ -56,7 +56,7 @@ async fn main() -> Result<(), Box> { env_logger::init(); // 1. Build the client - let client = RhaiClientBuilder::new() + let client = RhaiDispatcherBuilder::new() .caller_id("my-app-instance-1") .redis_url("redis://127.0.0.1/") .build()?; @@ -82,7 +82,7 @@ async fn main() -> Result<(), Box> { log::info!("Output: {}", output); } } - Err(RhaiClientError::Timeout(task_id)) => { + Err(RhaiDispatcherError::Timeout(task_id)) => { log::error!("Task {} timed out.", task_id); } Err(e) => { diff --git a/src/client/cmd/README.md b/src/dispatcher/cmd/README.md similarity index 97% rename from src/client/cmd/README.md rename to src/dispatcher/cmd/README.md index 89cd1e4..d297055 100644 --- a/src/client/cmd/README.md +++ b/src/dispatcher/cmd/README.md @@ -150,7 +150,7 @@ The client provides clear error messages for: ### Dependencies -- `rhai_client`: Core client library for Redis-based script execution +- `rhai_dispatcher`: Core client library for Redis-based script execution - `redis`: Redis client for task queue communication - `clap`: Command-line argument parsing - `env_logger`: Logging infrastructure diff --git a/src/client/cmd/client.rs b/src/dispatcher/cmd/dispatcher.rs similarity index 83% rename from src/client/cmd/client.rs rename to src/dispatcher/cmd/dispatcher.rs index fe6c3bb..2f42b34 100644 --- a/src/client/cmd/client.rs +++ b/src/dispatcher/cmd/dispatcher.rs @@ -1,5 +1,5 @@ use clap::Parser; -use rhai_client::{RhaiClient, RhaiClientBuilder}; +use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder}; use log::{error, info}; use std::io::{self, Write}; use std::time::Duration; @@ -9,15 +9,15 @@ use std::time::Duration; struct Args { /// Caller public key (caller ID) #[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")] - caller_public_key: String, + caller_id: String, /// Circle public key (context ID) #[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")] - circle_public_key: String, + context_id: String, /// Worker public key (defaults to circle public key if not provided) #[arg(short = 'w', long = "worker-key", help = "Worker public key (defaults to circle key)")] - worker_public_key: Option, + worker_id: String, /// Redis URL #[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")] @@ -50,8 +50,8 @@ async fn main() -> Result<(), Box> { // Configure logging based on verbosity level let log_config = match args.verbose { - 0 => "warn,rhai_client=info", - 1 => "info,rhai_client=debug", + 0 => "warn,rhai_dispatcher=info", + 1 => "info,rhai_dispatcher=debug", 2 => "debug", _ => "trace", }; @@ -67,21 +67,20 @@ async fn main() -> Result<(), Box> { env_logger::init(); } - // Use worker key or default to circle key - let worker_key = args.worker_public_key.unwrap_or_else(|| args.circle_public_key.clone()); - - info!("🔗 Starting Rhai Client"); + info!("🔗 Starting Rhai Dispatcher"); info!("📋 Configuration:"); - info!(" Caller Key: {}", args.caller_public_key); - info!(" Circle Key: {}", args.circle_public_key); - info!(" Worker Key: {}", worker_key); + info!(" Caller ID: {}", args.caller_id); + info!(" Context ID: {}", args.context_id); + info!(" Worker ID: {}", args.worker_id); info!(" Redis URL: {}", args.redis_url); info!(" Timeout: {}s", args.timeout); info!(""); // Create the Rhai client - let client = RhaiClientBuilder::new() - .caller_id(&args.caller_public_key) + let client = RhaiDispatcherBuilder::new() + .caller_id(&args.caller_id) + .worker_id(&args.worker_id) + .context_id(&args.context_id) .redis_url(&args.redis_url) .build()?; @@ -91,26 +90,25 @@ async fn main() -> Result<(), Box> { if let Some(script_content) = args.script { // Execute inline script info!("📜 Executing inline script"); - execute_script(&client, &worker_key, script_content, args.timeout).await?; + execute_script(&client, script_content, args.timeout).await?; } else if let Some(file_path) = args.file { // Execute script from file info!("📁 Loading script from file: {}", file_path); let script_content = std::fs::read_to_string(&file_path) .map_err(|e| format!("Failed to read script file '{}': {}", file_path, e))?; - execute_script(&client, &worker_key, script_content, args.timeout).await?; + execute_script(&client, script_content, args.timeout).await?; } else { // Interactive mode info!("🎮 Entering interactive mode"); info!("Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close."); - run_interactive_mode(&client, &worker_key, args.timeout).await?; + run_interactive_mode(&client, args.timeout).await?; } Ok(()) } async fn execute_script( - client: &RhaiClient, - worker_key: &str, + client: &RhaiDispatcher, script: String, timeout_secs: u64, ) -> Result<(), Box> { @@ -120,7 +118,6 @@ async fn execute_script( match client .new_play_request() - .recipient_id(worker_key) .script(&script) .timeout(timeout) .await_response() @@ -146,8 +143,7 @@ async fn execute_script( } async fn run_interactive_mode( - client: &RhaiClient, - worker_key: &str, + client: &RhaiDispatcher, timeout_secs: u64, ) -> Result<(), Box> { let timeout = Duration::from_secs(timeout_secs); @@ -174,7 +170,6 @@ async fn run_interactive_mode( match client .new_play_request() - .recipient_id(worker_key) .script(input) .timeout(timeout) .await_response() diff --git a/src/client/docs/ARCHITECTURE.md b/src/dispatcher/docs/ARCHITECTURE.md similarity index 90% rename from src/client/docs/ARCHITECTURE.md rename to src/dispatcher/docs/ARCHITECTURE.md index fc876e4..4ceecd4 100644 --- a/src/client/docs/ARCHITECTURE.md +++ b/src/dispatcher/docs/ARCHITECTURE.md @@ -1,6 +1,6 @@ -# Architecture of the `rhai_client` Crate +# Architecture of the `rhai_dispatcher` Crate -The `rhai_client` crate provides a Redis-based client library for submitting Rhai scripts to distributed worker services and awaiting their execution results. It implements a request-reply pattern using Redis as the message broker. +The `rhai_dispatcher` crate provides a Redis-based client library for submitting Rhai scripts to distributed worker services and awaiting their execution results. It implements a request-reply pattern using Redis as the message broker. ## Core Architecture @@ -8,7 +8,7 @@ The client follows a builder pattern design with clear separation of concerns: ```mermaid graph TD - A[RhaiClientBuilder] --> B[RhaiClient] + A[RhaiDispatcherBuilder] --> B[RhaiDispatcher] B --> C[PlayRequestBuilder] C --> D[PlayRequest] D --> E[Redis Task Queue] @@ -35,9 +35,9 @@ graph TD ## Key Components -### 1. RhaiClientBuilder +### 1. RhaiDispatcherBuilder -A builder pattern implementation for constructing `RhaiClient` instances with proper configuration validation. +A builder pattern implementation for constructing `RhaiDispatcher` instances with proper configuration validation. **Responsibilities:** - Configure Redis connection URL @@ -47,9 +47,9 @@ A builder pattern implementation for constructing `RhaiClient` instances with pr **Key Methods:** - `caller_id(id: &str)` - Sets the caller identifier - `redis_url(url: &str)` - Configures Redis connection -- `build()` - Creates the final `RhaiClient` instance +- `build()` - Creates the final `RhaiDispatcher` instance -### 2. RhaiClient +### 2. RhaiDispatcher The main client interface that manages Redis connections and provides factory methods for creating play requests. @@ -103,7 +103,7 @@ pub struct RhaiTaskDetails { } ``` -#### RhaiClientError +#### RhaiDispatcherError Comprehensive error handling for various failure scenarios: - `RedisError` - Redis connection/operation failures - `SerializationError` - JSON serialization/deserialization issues diff --git a/src/client/examples/timeout_example.rs b/src/dispatcher/examples/timeout_example.rs similarity index 85% rename from src/client/examples/timeout_example.rs rename to src/dispatcher/examples/timeout_example.rs index b69b833..3948696 100644 --- a/src/client/examples/timeout_example.rs +++ b/src/dispatcher/examples/timeout_example.rs @@ -1,5 +1,5 @@ use log::info; -use rhai_client::{RhaiClientBuilder, RhaiClientError}; +use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError}; use std::time::{Duration, Instant}; #[tokio::main] @@ -9,11 +9,11 @@ async fn main() -> Result<(), Box> { .init(); // Build the client using the new builder pattern - let client = RhaiClientBuilder::new() + let client = RhaiDispatcherBuilder::new() .caller_id("timeout-example-runner") .redis_url("redis://127.0.0.1/") .build()?; - info!("RhaiClient created."); + info!("RhaiDispatcher created."); let script_content = r#" // This script will never be executed by a worker because the recipient does not exist. @@ -56,8 +56,8 @@ async fn main() -> Result<(), Box> { info!("Elapsed time: {:?}", elapsed); match e { - RhaiClientError::Timeout(task_id) => { - info!("Timeout Example PASSED: Correctly received RhaiClientError::Timeout for task_id: {}", task_id); + RhaiDispatcherError::Timeout(task_id) => { + info!("Timeout Example PASSED: Correctly received RhaiDispatcherError::Timeout for task_id: {}", task_id); // Ensure the elapsed time is close to the timeout duration // Allow for some buffer for processing assert!( @@ -75,11 +75,11 @@ async fn main() -> Result<(), Box> { } other_error => { log::error!( - "Timeout Example FAILED: Expected RhaiClientError::Timeout, but got other error: {:?}", + "Timeout Example FAILED: Expected RhaiDispatcherError::Timeout, but got other error: {:?}", other_error ); Err(format!( - "Expected RhaiClientError::Timeout, got other error: {:?}", + "Expected RhaiDispatcherError::Timeout, got other error: {:?}", other_error ) .into()) diff --git a/src/client/src/lib.rs b/src/dispatcher/src/lib.rs similarity index 84% rename from src/client/src/lib.rs rename to src/dispatcher/src/lib.rs index 5083180..3cc33ee 100644 --- a/src/client/src/lib.rs +++ b/src/dispatcher/src/lib.rs @@ -7,13 +7,13 @@ //! ## Quick Start //! //! ```rust -//! use rhai_client::{RhaiClientBuilder, RhaiClientError}; +//! use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError}; //! use std::time::Duration; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! // Build the client -//! let client = RhaiClientBuilder::new() +//! let client = RhaiDispatcherBuilder::new() //! .caller_id("my-app-instance-1") //! .redis_url("redis://127.0.0.1/") //! .build()?; @@ -76,6 +76,8 @@ pub struct RhaiTaskDetails { pub caller_id: String, #[serde(rename = "contextId")] pub context_id: String, + #[serde(rename = "workerId")] + pub worker_id: String, } /// Comprehensive error type for all possible failures in the Rhai client. @@ -83,7 +85,7 @@ pub struct RhaiTaskDetails { /// This enum covers all error scenarios that can occur during client operations, /// from Redis connectivity issues to task execution timeouts. #[derive(Debug)] -pub enum RhaiClientError { +pub enum RhaiDispatcherError { /// Redis connection or operation error RedisError(redis::RedisError), /// JSON serialization/deserialization error @@ -96,37 +98,37 @@ pub enum RhaiClientError { ContextIdMissing, } -impl From for RhaiClientError { +impl From for RhaiDispatcherError { fn from(err: redis::RedisError) -> Self { - RhaiClientError::RedisError(err) + RhaiDispatcherError::RedisError(err) } } -impl From for RhaiClientError { +impl From for RhaiDispatcherError { fn from(err: serde_json::Error) -> Self { - RhaiClientError::SerializationError(err) + RhaiDispatcherError::SerializationError(err) } } -impl std::fmt::Display for RhaiClientError { +impl std::fmt::Display for RhaiDispatcherError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - RhaiClientError::RedisError(e) => write!(f, "Redis error: {}", e), - RhaiClientError::SerializationError(e) => write!(f, "Serialization error: {}", e), - RhaiClientError::Timeout(task_id) => { + RhaiDispatcherError::RedisError(e) => write!(f, "Redis error: {}", e), + RhaiDispatcherError::SerializationError(e) => write!(f, "Serialization error: {}", e), + RhaiDispatcherError::Timeout(task_id) => { write!(f, "Timeout waiting for task {} to complete", task_id) } - RhaiClientError::TaskNotFound(task_id) => { + RhaiDispatcherError::TaskNotFound(task_id) => { write!(f, "Task {} not found after submission", task_id) } - RhaiClientError::ContextIdMissing => { + RhaiDispatcherError::ContextIdMissing => { write!(f, "Context ID is missing") } } } } -impl std::error::Error for RhaiClientError {} +impl std::error::Error for RhaiDispatcherError {} /// The main client for interacting with the Rhai task execution system. /// @@ -137,19 +139,21 @@ impl std::error::Error for RhaiClientError {} /// # Example /// /// ```rust -/// use rhai_client::RhaiClientBuilder; +/// use rhai_dispatcher::RhaiDispatcherBuilder; /// -/// let client = RhaiClientBuilder::new() +/// let client = RhaiDispatcherBuilder::new() /// .caller_id("my-service") /// .redis_url("redis://localhost/") /// .build()?; /// ``` -pub struct RhaiClient { +pub struct RhaiDispatcher { redis_client: redis::Client, caller_id: String, + worker_id: String, + context_id: String, } -/// Builder for constructing `RhaiClient` instances with proper configuration. +/// Builder for constructing `RhaiDispatcher` instances with proper configuration. /// /// This builder ensures that all required configuration is provided before /// creating a client instance. It validates the configuration and provides @@ -162,13 +166,15 @@ pub struct RhaiClient { /// # Optional Configuration /// /// - `redis_url`: Redis connection URL (defaults to "redis://127.0.0.1/") -pub struct RhaiClientBuilder { +pub struct RhaiDispatcherBuilder { redis_url: Option, caller_id: String, + worker_id: String, + context_id: String, } -impl RhaiClientBuilder { - /// Creates a new `RhaiClientBuilder` with default settings. +impl RhaiDispatcherBuilder { + /// Creates a new `RhaiDispatcherBuilder` with default settings. /// /// The builder starts with no Redis URL (will default to "redis://127.0.0.1/") /// and an empty caller ID (which must be set before building). @@ -176,6 +182,8 @@ impl RhaiClientBuilder { Self { redis_url: None, caller_id: "".to_string(), + worker_id: "".to_string(), + context_id: "".to_string(), } } @@ -192,6 +200,31 @@ impl RhaiClientBuilder { self.caller_id = caller_id.to_string(); self } + /// Sets the circle ID for this client instance. + /// + /// The circle ID is used to identify which circle's context a task should be executed in. + /// This is required at the time the client dispatches a script, but can be set on construction or on script dispatch. + /// + /// # Arguments + /// + /// * `context_id` - A unique identifier for this client instance + pub fn context_id(mut self, context_id: &str) -> Self { + self.context_id = context_id.to_string(); + self + } + + /// Sets the worker ID for this client instance. + /// + /// The worker ID is used to identify which worker a task should be executed on. + /// This is required at the time the client dispatches a script, but can be set on construction or on script dispatch. + /// + /// # Arguments + /// + /// * `worker_id` - A unique identifier for this client instance + pub fn worker_id(mut self, worker_id: &str) -> Self { + self.worker_id = worker_id.to_string(); + self + } /// Sets the Redis connection URL. /// @@ -205,7 +238,7 @@ impl RhaiClientBuilder { self } - /// Builds the final `RhaiClient` instance. + /// Builds the final `RhaiDispatcher` instance. /// /// This method validates the configuration and creates the Redis client. /// It will return an error if the caller ID is empty or if the Redis @@ -213,22 +246,18 @@ impl RhaiClientBuilder { /// /// # Returns /// - /// * `Ok(RhaiClient)` - Successfully configured client - /// * `Err(RhaiClientError)` - Configuration or connection error - pub fn build(self) -> Result { + /// * `Ok(RhaiDispatcher)` - Successfully configured client + /// * `Err(RhaiDispatcherError)` - Configuration or connection error + pub fn build(self) -> Result { let url = self .redis_url .unwrap_or_else(|| "redis://127.0.0.1/".to_string()); let client = redis::Client::open(url)?; - if self.caller_id.is_empty() { - return Err(RhaiClientError::RedisError(redis::RedisError::from(( - redis::ErrorKind::InvalidClientConfig, - "Caller ID is empty", - )))); - } - Ok(RhaiClient { + Ok(RhaiDispatcher { redis_client: client, caller_id: self.caller_id, + worker_id: self.worker_id, + context_id: self.context_id, }) } } @@ -265,21 +294,23 @@ pub struct PlayRequest { /// .await?; /// ``` pub struct PlayRequestBuilder<'a> { - client: &'a RhaiClient, + client: &'a RhaiDispatcher, request_id: String, worker_id: String, context_id: String, + caller_id: String, script: String, timeout: Duration, } impl<'a> PlayRequestBuilder<'a> { - pub fn new(client: &'a RhaiClient) -> Self { + pub fn new(client: &'a RhaiDispatcher) -> Self { Self { client, request_id: "".to_string(), - worker_id: "".to_string(), - context_id: "".to_string(), + worker_id: client.worker_id.clone(), + context_id: client.context_id.clone(), + caller_id: client.caller_id.clone(), script: "".to_string(), timeout: Duration::from_secs(10), } @@ -315,7 +346,7 @@ impl<'a> PlayRequestBuilder<'a> { self } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let request_id = if self.request_id.is_empty() { // Generate a UUID for the request_id Uuid::new_v4().to_string() @@ -324,7 +355,11 @@ impl<'a> PlayRequestBuilder<'a> { }; if self.context_id.is_empty() { - return Err(RhaiClientError::ContextIdMissing); + return Err(RhaiDispatcherError::ContextIdMissing); + } + + if self.caller_id.is_empty() { + return Err(RhaiDispatcherError::ContextIdMissing); } let play_request = PlayRequest { @@ -337,7 +372,7 @@ impl<'a> PlayRequestBuilder<'a> { Ok(play_request) } - pub async fn submit(self) -> Result<(), RhaiClientError> { + pub async fn submit(self) -> Result<(), RhaiDispatcherError> { // Build the request and submit using self.client println!( "Submitting request {} with timeout {:?}", @@ -347,7 +382,7 @@ impl<'a> PlayRequestBuilder<'a> { Ok(()) } - pub async fn await_response(self) -> Result { + pub async fn await_response(self) -> Result { // Build the request and submit using self.client println!( "Awaiting response for request {} with timeout {:?}", @@ -361,7 +396,7 @@ impl<'a> PlayRequestBuilder<'a> { } } -impl RhaiClient { +impl RhaiDispatcher { pub fn new_play_request(&self) -> PlayRequestBuilder { PlayRequestBuilder::new(self) } @@ -371,7 +406,7 @@ impl RhaiClient { &self, conn: &mut redis::aio::MultiplexedConnection, play_request: &PlayRequest, - ) -> Result<(), RhaiClientError> { + ) -> Result<(), RhaiDispatcherError> { let now = Utc::now(); let task_key = format!("{}{}", NAMESPACE_PREFIX, play_request.id); @@ -422,7 +457,7 @@ impl RhaiClient { task_key: &String, reply_queue_key: &String, timeout: Duration, - ) -> Result { + ) -> Result { // BLPOP on the reply queue // The timeout for BLPOP is in seconds (integer) let blpop_timeout_secs = timeout.as_secs().max(1); // Ensure at least 1 second for BLPOP timeout @@ -458,7 +493,7 @@ impl RhaiClient { ); // Optionally, delete the reply queue let _: redis::RedisResult = conn.del(&reply_queue_key).await; - Err(RhaiClientError::SerializationError(e)) + Err(RhaiDispatcherError::SerializationError(e)) } } } @@ -470,7 +505,7 @@ impl RhaiClient { ); // Optionally, delete the reply queue let _: redis::RedisResult = conn.del(&reply_queue_key).await; - Err(RhaiClientError::Timeout(task_key.clone())) + Err(RhaiDispatcherError::Timeout(task_key.clone())) } Err(e) => { // Redis error @@ -480,7 +515,7 @@ impl RhaiClient { ); // Optionally, delete the reply queue let _: redis::RedisResult = conn.del(&reply_queue_key).await; - Err(RhaiClientError::RedisError(e)) + Err(RhaiDispatcherError::RedisError(e)) } } } @@ -489,7 +524,7 @@ impl RhaiClient { pub async fn submit_play_request( &self, play_request: &PlayRequest, - ) -> Result<(), RhaiClientError> { + ) -> Result<(), RhaiDispatcherError> { let mut conn = self.redis_client.get_multiplexed_async_connection().await?; self.submit_play_request_using_connection( @@ -504,7 +539,7 @@ impl RhaiClient { pub async fn submit_play_request_and_await_result( &self, play_request: &PlayRequest, - ) -> Result { + ) -> Result { let mut conn = self.redis_client.get_multiplexed_async_connection().await?; let reply_queue_key = format!("{}:reply:{}", NAMESPACE_PREFIX, play_request.id); // Derived from the passed task_id @@ -535,7 +570,7 @@ impl RhaiClient { pub async fn get_task_status( &self, task_id: &str, - ) -> Result, RhaiClientError> { + ) -> Result, RhaiDispatcherError> { let mut conn = self.redis_client.get_multiplexed_async_connection().await?; let task_key = format!("{}{}", NAMESPACE_PREFIX, task_id); @@ -573,6 +608,7 @@ impl RhaiClient { Utc::now() }), caller_id: map.get("callerId").cloned().expect("callerId field missing from Redis hash"), + worker_id: map.get("workerId").cloned().expect("workerId field missing from Redis hash"), context_id: map.get("contextId").cloned().expect("contextId field missing from Redis hash"), }; // It's important to also check if the 'taskId' field exists in the map and matches the input task_id diff --git a/src/repl/.DS_Store b/src/repl/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0