remove local deps paths
This commit is contained in:
		
							
								
								
									
										1
									
								
								_archive/dispatcher/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								_archive/dispatcher/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
/target
 | 
			
		||||
							
								
								
									
										24
									
								
								_archive/dispatcher/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								_archive/dispatcher/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "rhai_dispatcher"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "dispatcher"
 | 
			
		||||
path = "cmd/dispatcher.rs"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
clap = { version = "4.4", features = ["derive"] }
 | 
			
		||||
env_logger = "0.10"
 | 
			
		||||
redis = { version = "0.25.0", features = ["tokio-comp"] }
 | 
			
		||||
serde = { version = "1.0", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0"
 | 
			
		||||
uuid = { version = "1.6", features = ["v4", "serde"] }
 | 
			
		||||
chrono = { version = "0.4", features = ["serde"] }
 | 
			
		||||
log = "0.4"
 | 
			
		||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # For async main in examples, and general async
 | 
			
		||||
colored = "2.0"
 | 
			
		||||
 | 
			
		||||
[dev-dependencies] # For examples later
 | 
			
		||||
env_logger = "0.10"
 | 
			
		||||
rhai = "1.18.0" # For examples that might need to show engine setup
 | 
			
		||||
							
								
								
									
										107
									
								
								_archive/dispatcher/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								_archive/dispatcher/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
# Rhai Client
 | 
			
		||||
 | 
			
		||||
The `rhai-client` crate provides a fluent builder-based interface for submitting Rhai scripts to a distributed task execution system over Redis. It enables applications to offload Rhai script execution to one or more worker services and await the results.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
-   **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.
 | 
			
		||||
-   **Direct-to-Worker-Queue Submission**: Tasks are sent to a queue named after the `worker_id`, allowing for direct and clear task routing.
 | 
			
		||||
-   **Manual Status Check**: Provides an option to manually check the status of a task by its ID.
 | 
			
		||||
 | 
			
		||||
## Core Components
 | 
			
		||||
 | 
			
		||||
-   **`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.
 | 
			
		||||
    -   `request_id`: An optional unique ID for the request.
 | 
			
		||||
    -   `timeout`: How long to wait for a result.
 | 
			
		||||
-   **Submission Methods**:
 | 
			
		||||
    -   `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.
 | 
			
		||||
-   **`RhaiDispatcherError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts.
 | 
			
		||||
 | 
			
		||||
## How It Works
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
    a.  A unique `task_id` (UUID v4) is generated.
 | 
			
		||||
    b.  Task details are stored in a Redis hash with a key like `rhailib:<task_id>`.
 | 
			
		||||
    c.  The `task_id` is pushed to the worker's queue, named `rhailib:<worker_id>`.
 | 
			
		||||
    d.  The client performs a blocking pop (`BLPOP`) on a dedicated reply queue (`rhailib:reply:<task_id>`), waiting for the worker to send the result.
 | 
			
		||||
5.  A `rhai-worker` process, listening on the `rhailib:<worker_id>` queue, picks up the task, executes it, and pushes the final `RhaiTaskDetails` to the reply queue.
 | 
			
		||||
6.  The client receives the result from the reply queue and returns it to the caller.
 | 
			
		||||
 | 
			
		||||
## Prerequisites
 | 
			
		||||
 | 
			
		||||
-   A running Redis instance accessible by the client and the worker services.
 | 
			
		||||
 | 
			
		||||
## Usage Example
 | 
			
		||||
 | 
			
		||||
The following example demonstrates how to build a client, submit a script, and wait for the result.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    env_logger::init();
 | 
			
		||||
 | 
			
		||||
    // 1. Build the client
 | 
			
		||||
    let client = RhaiDispatcherBuilder::new()
 | 
			
		||||
        .caller_id("my-app-instance-1")
 | 
			
		||||
        .redis_url("redis://127.0.0.1/")
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // 2. Define the script and target worker
 | 
			
		||||
    let script = r#" "Hello, " + worker_id + "!" "#;
 | 
			
		||||
    let worker_id = "worker-1";
 | 
			
		||||
 | 
			
		||||
    // 3. Use the PlayRequestBuilder to configure and submit the request
 | 
			
		||||
    let result = client
 | 
			
		||||
        .new_play_request()
 | 
			
		||||
        .worker_id(worker_id)
 | 
			
		||||
        .script(script)
 | 
			
		||||
        .timeout(Duration::from_secs(5))
 | 
			
		||||
        .await_response()
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
    match result {
 | 
			
		||||
        Ok(details) => {
 | 
			
		||||
            log::info!("Task completed successfully!");
 | 
			
		||||
            log::info!("Status: {}", details.status);
 | 
			
		||||
            if let Some(output) = details.output {
 | 
			
		||||
                log::info!("Output: {}", output);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Err(RhaiDispatcherError::Timeout(task_id)) => {
 | 
			
		||||
            log::error!("Task {} timed out.", task_id);
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("An unexpected error occurred: {}", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Refer to the `examples/` directory for more specific use cases, such as `timeout_example.rs` which tests the timeout mechanism.
 | 
			
		||||
 | 
			
		||||
## Building and Running Examples
 | 
			
		||||
 | 
			
		||||
To run an example (e.g., `timeout_example`):
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cd src/client # (or wherever this client's Cargo.toml is)
 | 
			
		||||
cargo run --example timeout_example
 | 
			
		||||
```
 | 
			
		||||
Ensure a Redis server is running and accessible at `redis://127.0.0.1/`.
 | 
			
		||||
							
								
								
									
										157
									
								
								_archive/dispatcher/cmd/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								_archive/dispatcher/cmd/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
# Rhai Client Binary
 | 
			
		||||
 | 
			
		||||
A command-line client for executing Rhai scripts on remote workers via Redis.
 | 
			
		||||
 | 
			
		||||
## Binary: `client`
 | 
			
		||||
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
Build the binary:
 | 
			
		||||
```bash
 | 
			
		||||
cargo build --bin client --release
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Usage
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Basic usage - requires caller and circle keys
 | 
			
		||||
client --caller-key <CALLER_KEY> --circle-key <CIRCLE_KEY>
 | 
			
		||||
 | 
			
		||||
# Execute inline script
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --script "print('Hello World!')"
 | 
			
		||||
 | 
			
		||||
# Execute script from file
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --file script.rhai
 | 
			
		||||
 | 
			
		||||
# Use specific worker (defaults to circle key)
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> -w <WORKER_KEY> --script "2 + 2"
 | 
			
		||||
 | 
			
		||||
# Custom Redis and timeout
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --redis-url redis://localhost:6379/1 --timeout 60
 | 
			
		||||
 | 
			
		||||
# Remove timestamps from logs
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --no-timestamp
 | 
			
		||||
 | 
			
		||||
# Increase verbosity
 | 
			
		||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> -v --script "debug_info()"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Command-Line Options
 | 
			
		||||
 | 
			
		||||
| Option | Short | Default | Description |
 | 
			
		||||
|--------|-------|---------|-------------|
 | 
			
		||||
| `--caller-key` | `-c` | **Required** | Caller public key (your identity) |
 | 
			
		||||
| `--circle-key` | `-k` | **Required** | Circle public key (execution context) |
 | 
			
		||||
| `--worker-key` | `-w` | `circle-key` | Worker public key (target worker) |
 | 
			
		||||
| `--redis-url` | `-r` | `redis://localhost:6379` | Redis connection URL |
 | 
			
		||||
| `--script` | `-s` | | Rhai script to execute |
 | 
			
		||||
| `--file` | `-f` | | Path to Rhai script file |
 | 
			
		||||
| `--timeout` | `-t` | `30` | Timeout for script execution (seconds) |
 | 
			
		||||
| `--no-timestamp` | | `false` | Remove timestamps from log output |
 | 
			
		||||
| `--verbose` | `-v` | | Increase verbosity (stackable) |
 | 
			
		||||
 | 
			
		||||
### Execution Modes
 | 
			
		||||
 | 
			
		||||
#### Inline Script Execution
 | 
			
		||||
```bash
 | 
			
		||||
# Execute a simple calculation
 | 
			
		||||
client -c caller_123 -k circle_456 -s "let result = 2 + 2; print(result);"
 | 
			
		||||
 | 
			
		||||
# Execute with specific worker
 | 
			
		||||
client -c caller_123 -k circle_456 -w worker_789 -s "get_user_data()"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Script File Execution
 | 
			
		||||
```bash
 | 
			
		||||
# Execute script from file
 | 
			
		||||
client -c caller_123 -k circle_456 -f examples/data_processing.rhai
 | 
			
		||||
 | 
			
		||||
# Execute with custom timeout
 | 
			
		||||
client -c caller_123 -k circle_456 -f long_running_script.rhai -t 120
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Interactive Mode
 | 
			
		||||
```bash
 | 
			
		||||
# Enter interactive REPL mode (when no script or file provided)
 | 
			
		||||
client -c caller_123 -k circle_456
 | 
			
		||||
 | 
			
		||||
# Interactive mode with verbose logging
 | 
			
		||||
client -c caller_123 -k circle_456 -v --no-timestamp
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Interactive Mode
 | 
			
		||||
 | 
			
		||||
When no script (`-s`) or file (`-f`) is provided, the client enters interactive mode:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
🔗 Starting Rhai Client
 | 
			
		||||
📋 Configuration:
 | 
			
		||||
   Caller Key: caller_123
 | 
			
		||||
   Circle Key: circle_456
 | 
			
		||||
   Worker Key: circle_456
 | 
			
		||||
   Redis URL: redis://localhost:6379
 | 
			
		||||
   Timeout: 30s
 | 
			
		||||
 | 
			
		||||
✅ Connected to Redis at redis://localhost:6379
 | 
			
		||||
🎮 Entering interactive mode
 | 
			
		||||
Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close.
 | 
			
		||||
rhai> let x = 42; print(x);
 | 
			
		||||
Status: completed
 | 
			
		||||
Output: 42
 | 
			
		||||
rhai> exit
 | 
			
		||||
👋 Goodbye!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Configuration Examples
 | 
			
		||||
 | 
			
		||||
#### Development Usage
 | 
			
		||||
```bash
 | 
			
		||||
# Simple development client
 | 
			
		||||
client -c dev_user -k dev_circle
 | 
			
		||||
 | 
			
		||||
# Development with clean logs
 | 
			
		||||
client -c dev_user -k dev_circle --no-timestamp -v
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Production Usage
 | 
			
		||||
```bash
 | 
			
		||||
# Production client with specific worker
 | 
			
		||||
client \
 | 
			
		||||
  --caller-key prod_user_123 \
 | 
			
		||||
  --circle-key prod_circle_456 \
 | 
			
		||||
  --worker-key prod_worker_789 \
 | 
			
		||||
  --redis-url redis://redis-cluster:6379/0 \
 | 
			
		||||
  --timeout 300 \
 | 
			
		||||
  --file production_script.rhai
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Batch Processing
 | 
			
		||||
```bash
 | 
			
		||||
# Process multiple scripts
 | 
			
		||||
for script in scripts/*.rhai; do
 | 
			
		||||
  client -c batch_user -k batch_circle -f "$script" --no-timestamp
 | 
			
		||||
done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Key Concepts
 | 
			
		||||
 | 
			
		||||
- **Caller Key**: Your identity - used for authentication and tracking
 | 
			
		||||
- **Circle Key**: Execution context - defines the environment/permissions
 | 
			
		||||
- **Worker Key**: Target worker - which worker should execute the script (defaults to circle key)
 | 
			
		||||
 | 
			
		||||
### Error Handling
 | 
			
		||||
 | 
			
		||||
The client provides clear error messages for:
 | 
			
		||||
- Missing required keys
 | 
			
		||||
- Redis connection failures
 | 
			
		||||
- Script execution timeouts
 | 
			
		||||
- Worker unavailability
 | 
			
		||||
- Script syntax errors
 | 
			
		||||
 | 
			
		||||
### Dependencies
 | 
			
		||||
 | 
			
		||||
- `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
 | 
			
		||||
- `tokio`: Async runtime
 | 
			
		||||
							
								
								
									
										207
									
								
								_archive/dispatcher/cmd/dispatcher.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								_archive/dispatcher/cmd/dispatcher.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,207 @@
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder};
 | 
			
		||||
use log::{error, info};
 | 
			
		||||
use colored::Colorize;
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
#[command(author, version, about = "Rhai Client - Script execution client", long_about = None)]
 | 
			
		||||
struct Args {
 | 
			
		||||
    /// Caller public key (caller ID)
 | 
			
		||||
    #[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")]
 | 
			
		||||
    caller_id: String,
 | 
			
		||||
 | 
			
		||||
    /// Circle public key (context ID)
 | 
			
		||||
    #[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")]
 | 
			
		||||
    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_id: String,
 | 
			
		||||
 | 
			
		||||
    /// Redis URL
 | 
			
		||||
    #[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")]
 | 
			
		||||
    redis_url: String,
 | 
			
		||||
 | 
			
		||||
    /// Rhai script to execute
 | 
			
		||||
    #[arg(short, long, help = "Rhai script to execute")]
 | 
			
		||||
    script: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// Path to Rhai script file
 | 
			
		||||
    #[arg(short, long, help = "Path to Rhai script file")]
 | 
			
		||||
    file: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// Timeout for script execution (in seconds)
 | 
			
		||||
    #[arg(short, long, default_value = "30", help = "Timeout for script execution in seconds")]
 | 
			
		||||
    timeout: u64,
 | 
			
		||||
 | 
			
		||||
    /// Increase verbosity (can be used multiple times)
 | 
			
		||||
    #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity (-v for debug, -vv for trace)")]
 | 
			
		||||
    verbose: u8,
 | 
			
		||||
 | 
			
		||||
    /// Disable timestamps in log output
 | 
			
		||||
    #[arg(long, help = "Remove timestamps from log output")]
 | 
			
		||||
    no_timestamp: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let args = Args::parse();
 | 
			
		||||
 | 
			
		||||
    // Configure logging based on verbosity level
 | 
			
		||||
    let log_config = match args.verbose {
 | 
			
		||||
        0 => "warn,rhai_dispatcher=warn",
 | 
			
		||||
        1 => "info,rhai_dispatcher=info",
 | 
			
		||||
        2 => "debug,rhai_dispatcher=debug",
 | 
			
		||||
        _ => "trace,rhai_dispatcher=trace",
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    std::env::set_var("RUST_LOG", log_config);
 | 
			
		||||
    
 | 
			
		||||
    // Configure env_logger with or without timestamps
 | 
			
		||||
    if args.no_timestamp {
 | 
			
		||||
        env_logger::Builder::from_default_env()
 | 
			
		||||
            .format_timestamp(None)
 | 
			
		||||
            .init();
 | 
			
		||||
    } else {
 | 
			
		||||
        env_logger::init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if args.verbose > 0 {
 | 
			
		||||
        info!("🔗 Starting Rhai Dispatcher");
 | 
			
		||||
        info!("📋 Configuration:");
 | 
			
		||||
        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 = RhaiDispatcherBuilder::new()
 | 
			
		||||
        .caller_id(&args.caller_id)
 | 
			
		||||
        .worker_id(&args.worker_id)
 | 
			
		||||
        .context_id(&args.context_id)
 | 
			
		||||
        .redis_url(&args.redis_url)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    if args.verbose > 0 {
 | 
			
		||||
        info!("✅ Connected to Redis at {}", args.redis_url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine execution mode
 | 
			
		||||
    if let Some(script_content) = args.script {
 | 
			
		||||
        // Execute inline script
 | 
			
		||||
        if args.verbose > 0 {
 | 
			
		||||
            info!("📜 Executing inline script");
 | 
			
		||||
        }
 | 
			
		||||
        execute_script(&client, script_content, args.timeout).await?;
 | 
			
		||||
    } else if let Some(file_path) = args.file {
 | 
			
		||||
        // Execute script from file
 | 
			
		||||
        if args.verbose > 0 {
 | 
			
		||||
            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, 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, args.timeout, args.verbose).await?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn execute_script(
 | 
			
		||||
    client: &RhaiDispatcher,
 | 
			
		||||
    script: String,
 | 
			
		||||
    timeout_secs: u64,
 | 
			
		||||
) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    info!("⚡ Executing script: {:.50}...", script);
 | 
			
		||||
    
 | 
			
		||||
    let timeout = Duration::from_secs(timeout_secs);
 | 
			
		||||
    
 | 
			
		||||
    match client
 | 
			
		||||
        .new_play_request()
 | 
			
		||||
        .script(&script)
 | 
			
		||||
        .timeout(timeout)
 | 
			
		||||
        .await_response()
 | 
			
		||||
        .await
 | 
			
		||||
    {
 | 
			
		||||
        Ok(result) => {
 | 
			
		||||
            info!("✅ Script execution completed");
 | 
			
		||||
            println!("Status: {}", result.status);
 | 
			
		||||
            if let Some(output) = result.output {
 | 
			
		||||
                println!("Output: {}", output);
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(error) = result.error {
 | 
			
		||||
                println!("Error: {}", error);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            error!("❌ Script execution failed: {}", e);
 | 
			
		||||
            return Err(Box::new(e));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run_interactive_mode(
 | 
			
		||||
    client: &RhaiDispatcher,
 | 
			
		||||
    timeout_secs: u64,
 | 
			
		||||
    verbose: u8,
 | 
			
		||||
) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let timeout = Duration::from_secs(timeout_secs);
 | 
			
		||||
    
 | 
			
		||||
    loop {
 | 
			
		||||
        print!("rhai> ");
 | 
			
		||||
        io::stdout().flush()?;
 | 
			
		||||
        
 | 
			
		||||
        let mut input = String::new();
 | 
			
		||||
        io::stdin().read_line(&mut input)?;
 | 
			
		||||
        
 | 
			
		||||
        let input = input.trim();
 | 
			
		||||
        
 | 
			
		||||
        if input.is_empty() {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if input == "exit" || input == "quit" {
 | 
			
		||||
            info!("👋 Goodbye!");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if verbose > 0 {
 | 
			
		||||
            info!("⚡ Executing: {}", input);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        match client
 | 
			
		||||
            .new_play_request()
 | 
			
		||||
            .script(input)
 | 
			
		||||
            .timeout(timeout)
 | 
			
		||||
            .await_response()
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            Ok(result) => {
 | 
			
		||||
                if let Some(output) = result.output {
 | 
			
		||||
                    println!("{}", output.color("green"));
 | 
			
		||||
                }
 | 
			
		||||
                if let Some(error) = result.error {
 | 
			
		||||
                    println!("{}", format!("error: {}", error).color("red"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                println!("{}", format!("error: {}", e).red());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        println!(); // Add blank line for readability
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										190
									
								
								_archive/dispatcher/docs/ARCHITECTURE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								_archive/dispatcher/docs/ARCHITECTURE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
# Architecture of the `rhai_dispatcher` Crate
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
The client follows a builder pattern design with clear separation of concerns:
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
graph TD
 | 
			
		||||
    A[RhaiDispatcherBuilder] --> B[RhaiDispatcher]
 | 
			
		||||
    B --> C[PlayRequestBuilder]
 | 
			
		||||
    C --> D[PlayRequest]
 | 
			
		||||
    D --> E[Redis Task Queue]
 | 
			
		||||
    E --> F[Worker Service]
 | 
			
		||||
    F --> G[Redis Reply Queue]
 | 
			
		||||
    G --> H[Client Response]
 | 
			
		||||
    
 | 
			
		||||
    subgraph "Client Components"
 | 
			
		||||
        A
 | 
			
		||||
        B
 | 
			
		||||
        C
 | 
			
		||||
        D
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    subgraph "Redis Infrastructure"
 | 
			
		||||
        E
 | 
			
		||||
        G
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    subgraph "External Services"
 | 
			
		||||
        F
 | 
			
		||||
    end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Key Components
 | 
			
		||||
 | 
			
		||||
### 1. RhaiDispatcherBuilder
 | 
			
		||||
 | 
			
		||||
A builder pattern implementation for constructing `RhaiDispatcher` instances with proper configuration validation.
 | 
			
		||||
 | 
			
		||||
**Responsibilities:**
 | 
			
		||||
- Configure Redis connection URL
 | 
			
		||||
- Set caller ID for task attribution
 | 
			
		||||
- Validate configuration before building client
 | 
			
		||||
 | 
			
		||||
**Key Methods:**
 | 
			
		||||
- `caller_id(id: &str)` - Sets the caller identifier
 | 
			
		||||
- `redis_url(url: &str)` - Configures Redis connection
 | 
			
		||||
- `build()` - Creates the final `RhaiDispatcher` instance
 | 
			
		||||
 | 
			
		||||
### 2. RhaiDispatcher
 | 
			
		||||
 | 
			
		||||
The main client interface that manages Redis connections and provides factory methods for creating play requests.
 | 
			
		||||
 | 
			
		||||
**Responsibilities:**
 | 
			
		||||
- Maintain Redis connection pool
 | 
			
		||||
- Provide factory methods for request builders
 | 
			
		||||
- Handle low-level Redis operations
 | 
			
		||||
- Manage task status queries
 | 
			
		||||
 | 
			
		||||
**Key Methods:**
 | 
			
		||||
- `new_play_request()` - Creates a new `PlayRequestBuilder`
 | 
			
		||||
- `get_task_status(task_id)` - Queries task status from Redis
 | 
			
		||||
- Internal methods for Redis operations
 | 
			
		||||
 | 
			
		||||
### 3. PlayRequestBuilder
 | 
			
		||||
 | 
			
		||||
A fluent builder for constructing and submitting script execution requests.
 | 
			
		||||
 | 
			
		||||
**Responsibilities:**
 | 
			
		||||
- Configure script execution parameters
 | 
			
		||||
- Handle script loading from files or strings
 | 
			
		||||
- Manage request timeouts
 | 
			
		||||
- Provide submission methods (fire-and-forget vs await-response)
 | 
			
		||||
 | 
			
		||||
**Key Methods:**
 | 
			
		||||
- `worker_id(id: &str)` - Target worker queue (determines which worker processes the task)
 | 
			
		||||
- `context_id(id: &str)` - Target context ID (determines execution context/circle)
 | 
			
		||||
- `script(content: &str)` - Set script content directly
 | 
			
		||||
- `script_path(path: &str)` - Load script from file
 | 
			
		||||
- `timeout(duration: Duration)` - Set execution timeout
 | 
			
		||||
- `submit()` - Fire-and-forget submission
 | 
			
		||||
- `await_response()` - Submit and wait for result
 | 
			
		||||
 | 
			
		||||
**Architecture Note:** The decoupling of `worker_id` and `context_id` allows a single worker to process tasks for multiple contexts (circles), providing greater deployment flexibility.
 | 
			
		||||
 | 
			
		||||
### 4. Data Structures
 | 
			
		||||
 | 
			
		||||
#### RhaiTaskDetails
 | 
			
		||||
Represents the complete state of a task throughout its lifecycle.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
pub struct RhaiTaskDetails {
 | 
			
		||||
    pub task_id: String,
 | 
			
		||||
    pub script: String,
 | 
			
		||||
    pub status: String,        // "pending", "processing", "completed", "error"
 | 
			
		||||
    pub output: Option<String>,
 | 
			
		||||
    pub error: Option<String>,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    pub caller_id: String,
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### RhaiDispatcherError
 | 
			
		||||
Comprehensive error handling for various failure scenarios:
 | 
			
		||||
- `RedisError` - Redis connection/operation failures
 | 
			
		||||
- `SerializationError` - JSON serialization/deserialization issues
 | 
			
		||||
- `Timeout` - Task execution timeouts
 | 
			
		||||
- `TaskNotFound` - Missing tasks after submission
 | 
			
		||||
 | 
			
		||||
## Communication Protocol
 | 
			
		||||
 | 
			
		||||
### Task Submission Flow
 | 
			
		||||
 | 
			
		||||
1. **Task Creation**: Client generates unique UUID for task identification
 | 
			
		||||
2. **Task Storage**: Task details stored in Redis hash: `rhailib:<task_id>`
 | 
			
		||||
3. **Queue Submission**: Task ID pushed to worker queue: `rhailib:<worker_id>`
 | 
			
		||||
4. **Reply Queue Setup**: Client listens on: `rhailib:reply:<task_id>`
 | 
			
		||||
 | 
			
		||||
### Redis Key Patterns
 | 
			
		||||
 | 
			
		||||
- **Task Storage**: `rhailib:<task_id>` (Redis Hash)
 | 
			
		||||
- **Worker Queues**: `rhailib:<worker_id>` (Redis List)
 | 
			
		||||
- **Reply Queues**: `rhailib:reply:<task_id>` (Redis List)
 | 
			
		||||
 | 
			
		||||
### Message Flow Diagram
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
sequenceDiagram
 | 
			
		||||
    participant C as Client
 | 
			
		||||
    participant R as Redis
 | 
			
		||||
    participant W as Worker
 | 
			
		||||
    
 | 
			
		||||
    C->>R: HSET rhailib:task_id (task details)
 | 
			
		||||
    C->>R: LPUSH rhailib:worker_id task_id
 | 
			
		||||
    C->>R: BLPOP rhailib:reply:task_id (blocking)
 | 
			
		||||
    
 | 
			
		||||
    W->>R: BRPOP rhailib:worker_id (blocking)
 | 
			
		||||
    W->>W: Execute Rhai Script
 | 
			
		||||
    W->>R: LPUSH rhailib:reply:task_id (result)
 | 
			
		||||
    
 | 
			
		||||
    R->>C: Return result from BLPOP
 | 
			
		||||
    C->>R: DEL rhailib:reply:task_id (cleanup)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Concurrency and Async Design
 | 
			
		||||
 | 
			
		||||
The client is built on `tokio` for asynchronous operations:
 | 
			
		||||
 | 
			
		||||
- **Connection Pooling**: Uses Redis multiplexed connections for efficiency
 | 
			
		||||
- **Non-blocking Operations**: All Redis operations are async
 | 
			
		||||
- **Timeout Handling**: Configurable timeouts with proper cleanup
 | 
			
		||||
- **Error Propagation**: Comprehensive error handling with context
 | 
			
		||||
 | 
			
		||||
## Configuration and Deployment
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
- Redis server accessible to both client and workers
 | 
			
		||||
- Proper network connectivity between components
 | 
			
		||||
- Sufficient Redis memory for task storage
 | 
			
		||||
 | 
			
		||||
### Configuration Options
 | 
			
		||||
- **Redis URL**: Connection string for Redis instance
 | 
			
		||||
- **Caller ID**: Unique identifier for client instance
 | 
			
		||||
- **Timeouts**: Per-request timeout configuration
 | 
			
		||||
- **Worker Targeting**: Direct worker queue addressing
 | 
			
		||||
 | 
			
		||||
## Security Considerations
 | 
			
		||||
 | 
			
		||||
- **Task Isolation**: Each task uses unique identifiers
 | 
			
		||||
- **Queue Separation**: Worker-specific queues prevent cross-contamination
 | 
			
		||||
- **Cleanup**: Automatic cleanup of reply queues after completion
 | 
			
		||||
- **Error Handling**: Secure error propagation without sensitive data leakage
 | 
			
		||||
 | 
			
		||||
## Performance Characteristics
 | 
			
		||||
 | 
			
		||||
- **Scalability**: Horizontal scaling through multiple worker instances
 | 
			
		||||
- **Throughput**: Limited by Redis performance and network latency
 | 
			
		||||
- **Memory Usage**: Efficient with connection pooling and cleanup
 | 
			
		||||
- **Latency**: Low latency for local Redis deployments
 | 
			
		||||
 | 
			
		||||
## Integration Points
 | 
			
		||||
 | 
			
		||||
The client integrates with:
 | 
			
		||||
- **Worker Services**: Via Redis queue protocol
 | 
			
		||||
- **Monitoring Systems**: Through structured logging
 | 
			
		||||
- **Application Code**: Via builder pattern API
 | 
			
		||||
- **Configuration Systems**: Through environment variables and builders
 | 
			
		||||
							
								
								
									
										90
									
								
								_archive/dispatcher/examples/timeout_example.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								_archive/dispatcher/examples/timeout_example.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
use log::info;
 | 
			
		||||
use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError};
 | 
			
		||||
use std::time::{Duration, Instant};
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    env_logger::builder()
 | 
			
		||||
        .filter_level(log::LevelFilter::Info)
 | 
			
		||||
        .init();
 | 
			
		||||
 | 
			
		||||
    // Build the client using the new builder pattern
 | 
			
		||||
    let client = RhaiDispatcherBuilder::new()
 | 
			
		||||
        .caller_id("timeout-example-runner")
 | 
			
		||||
        .redis_url("redis://127.0.0.1/")
 | 
			
		||||
        .build()?;
 | 
			
		||||
    info!("RhaiDispatcher created.");
 | 
			
		||||
 | 
			
		||||
    let script_content = r#"
 | 
			
		||||
        // This script will never be executed by a worker because the recipient does not exist.
 | 
			
		||||
        let x = 10;
 | 
			
		||||
        let y = x + 32;
 | 
			
		||||
        y
 | 
			
		||||
    "#;
 | 
			
		||||
 | 
			
		||||
    // The worker_id points to a worker queue that doesn't have a worker.
 | 
			
		||||
    let non_existent_recipient = "non_existent_worker_for_timeout_test";
 | 
			
		||||
    let very_short_timeout = Duration::from_secs(2);
 | 
			
		||||
 | 
			
		||||
    info!(
 | 
			
		||||
        "Submitting script to non-existent recipient '{}' with a timeout of {:?}...",
 | 
			
		||||
        non_existent_recipient, very_short_timeout
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let start_time = Instant::now();
 | 
			
		||||
 | 
			
		||||
    // Use the new PlayRequestBuilder
 | 
			
		||||
    let result = client
 | 
			
		||||
        .new_play_request()
 | 
			
		||||
        .worker_id(non_existent_recipient)
 | 
			
		||||
        .script(script_content)
 | 
			
		||||
        .timeout(very_short_timeout)
 | 
			
		||||
        .await_response()
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
    match result {
 | 
			
		||||
        Ok(details) => {
 | 
			
		||||
            log::error!(
 | 
			
		||||
                "Timeout Example FAILED: Expected a timeout, but got Ok: {:?}",
 | 
			
		||||
                details
 | 
			
		||||
            );
 | 
			
		||||
            Err("Expected timeout, but task completed successfully.".into())
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            let elapsed = start_time.elapsed();
 | 
			
		||||
            info!("Timeout Example: Received error as expected: {}", e);
 | 
			
		||||
            info!("Elapsed time: {:?}", elapsed);
 | 
			
		||||
 | 
			
		||||
            match e {
 | 
			
		||||
                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!(
 | 
			
		||||
                        elapsed >= very_short_timeout
 | 
			
		||||
                            && elapsed < very_short_timeout + Duration::from_secs(1),
 | 
			
		||||
                        "Elapsed time {:?} should be close to timeout {:?}",
 | 
			
		||||
                        elapsed,
 | 
			
		||||
                        very_short_timeout
 | 
			
		||||
                    );
 | 
			
		||||
                    info!(
 | 
			
		||||
                        "Elapsed time {:?} is consistent with timeout duration {:?}.",
 | 
			
		||||
                        elapsed, very_short_timeout
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                other_error => {
 | 
			
		||||
                    log::error!(
 | 
			
		||||
                        "Timeout Example FAILED: Expected RhaiDispatcherError::Timeout, but got other error: {:?}",
 | 
			
		||||
                        other_error
 | 
			
		||||
                    );
 | 
			
		||||
                    Err(format!(
 | 
			
		||||
                        "Expected RhaiDispatcherError::Timeout, got other error: {:?}",
 | 
			
		||||
                        other_error
 | 
			
		||||
                    )
 | 
			
		||||
                    .into())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										638
									
								
								_archive/dispatcher/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										638
									
								
								_archive/dispatcher/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,638 @@
 | 
			
		||||
//! # Rhai Client Library
 | 
			
		||||
//!
 | 
			
		||||
//! A Redis-based client library for submitting Rhai scripts to distributed worker services
 | 
			
		||||
//! and awaiting their execution results. This crate implements a request-reply pattern
 | 
			
		||||
//! using Redis as the message broker.
 | 
			
		||||
//!
 | 
			
		||||
//! ## Quick Start
 | 
			
		||||
//!
 | 
			
		||||
//! ```rust
 | 
			
		||||
//! use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError};
 | 
			
		||||
//! use std::time::Duration;
 | 
			
		||||
//!
 | 
			
		||||
//! #[tokio::main]
 | 
			
		||||
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
//!     // Build the client
 | 
			
		||||
//!     let client = RhaiDispatcherBuilder::new()
 | 
			
		||||
//!         .caller_id("my-app-instance-1")
 | 
			
		||||
//!         .redis_url("redis://127.0.0.1/")
 | 
			
		||||
//!         .build()?;
 | 
			
		||||
//!
 | 
			
		||||
//!     // Submit a script and await the result
 | 
			
		||||
//!     let result = client
 | 
			
		||||
//!         .new_play_request()
 | 
			
		||||
//!         .worker_id("worker-1")
 | 
			
		||||
//!         .script(r#""Hello, World!""#)
 | 
			
		||||
//!         .timeout(Duration::from_secs(5))
 | 
			
		||||
//!         .await_response()
 | 
			
		||||
//!         .await?;
 | 
			
		||||
//!
 | 
			
		||||
//!     println!("Result: {:?}", result);
 | 
			
		||||
//!     Ok(())
 | 
			
		||||
//! }
 | 
			
		||||
//! ```
 | 
			
		||||
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use log::{debug, error, info, warn}; // Added error
 | 
			
		||||
use redis::AsyncCommands;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::time::Duration; // Duration is still used, Instant and sleep were removed
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
/// Redis namespace prefix for all rhailib-related keys
 | 
			
		||||
const NAMESPACE_PREFIX: &str = "rhailib:";
 | 
			
		||||
 | 
			
		||||
/// Represents the complete details and state of a Rhai task execution.
 | 
			
		||||
///
 | 
			
		||||
/// This structure contains all information about a task throughout its lifecycle,
 | 
			
		||||
/// from submission to completion. It's used for both storing task state in Redis
 | 
			
		||||
/// and returning results to clients.
 | 
			
		||||
///
 | 
			
		||||
/// # Fields
 | 
			
		||||
///
 | 
			
		||||
/// * `task_id` - Unique identifier for the task (UUID)
 | 
			
		||||
/// * `script` - The Rhai script content to execute
 | 
			
		||||
/// * `status` - Current execution status: "pending", "processing", "completed", or "error"
 | 
			
		||||
/// * `output` - Script execution output (if successful)
 | 
			
		||||
/// * `error` - Error message (if execution failed)
 | 
			
		||||
/// * `created_at` - Timestamp when the task was created
 | 
			
		||||
/// * `updated_at` - Timestamp when the task was last modified
 | 
			
		||||
/// * `caller_id` - Identifier of the client that submitted the task
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
pub struct RhaiTaskDetails {
 | 
			
		||||
    #[serde(rename = "taskId")] // Ensure consistent naming with other fields
 | 
			
		||||
    pub task_id: String,
 | 
			
		||||
    pub script: String,
 | 
			
		||||
    pub status: String, // "pending", "processing", "completed", "error"
 | 
			
		||||
    // client_rpc_id: Option<Value> is removed.
 | 
			
		||||
    // Worker responses should ideally not include it, or Serde will ignore unknown fields by default.
 | 
			
		||||
    pub output: Option<String>,
 | 
			
		||||
    pub error: Option<String>, // Renamed from error_message for consistency
 | 
			
		||||
    #[serde(rename = "createdAt")]
 | 
			
		||||
    pub created_at: chrono::DateTime<chrono::Utc>,
 | 
			
		||||
    #[serde(rename = "updatedAt")]
 | 
			
		||||
    pub updated_at: chrono::DateTime<chrono::Utc>,
 | 
			
		||||
    #[serde(rename = "callerId")]
 | 
			
		||||
    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.
 | 
			
		||||
///
 | 
			
		||||
/// This enum covers all error scenarios that can occur during client operations,
 | 
			
		||||
/// from Redis connectivity issues to task execution timeouts.
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum RhaiDispatcherError {
 | 
			
		||||
    /// Redis connection or operation error
 | 
			
		||||
    RedisError(redis::RedisError),
 | 
			
		||||
    /// JSON serialization/deserialization error
 | 
			
		||||
    SerializationError(serde_json::Error),
 | 
			
		||||
    /// Task execution timeout - contains the task_id that timed out
 | 
			
		||||
    Timeout(String),
 | 
			
		||||
    /// Task not found after submission - contains the task_id (rare occurrence)
 | 
			
		||||
    TaskNotFound(String),
 | 
			
		||||
    /// Context ID is missing
 | 
			
		||||
    ContextIdMissing,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<redis::RedisError> for RhaiDispatcherError {
 | 
			
		||||
    fn from(err: redis::RedisError) -> Self {
 | 
			
		||||
        RhaiDispatcherError::RedisError(err)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<serde_json::Error> for RhaiDispatcherError {
 | 
			
		||||
    fn from(err: serde_json::Error) -> Self {
 | 
			
		||||
        RhaiDispatcherError::SerializationError(err)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for RhaiDispatcherError {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            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)
 | 
			
		||||
            }
 | 
			
		||||
            RhaiDispatcherError::TaskNotFound(task_id) => {
 | 
			
		||||
                write!(f, "Task {} not found after submission", task_id)
 | 
			
		||||
            }
 | 
			
		||||
            RhaiDispatcherError::ContextIdMissing => {
 | 
			
		||||
                write!(f, "Context ID is missing")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::error::Error for RhaiDispatcherError {}
 | 
			
		||||
 | 
			
		||||
/// The main client for interacting with the Rhai task execution system.
 | 
			
		||||
///
 | 
			
		||||
/// This client manages Redis connections and provides factory methods for creating
 | 
			
		||||
/// script execution requests. It maintains a caller ID for task attribution and
 | 
			
		||||
/// handles all low-level Redis operations.
 | 
			
		||||
///
 | 
			
		||||
/// # Example
 | 
			
		||||
///
 | 
			
		||||
/// ```rust
 | 
			
		||||
/// use rhai_dispatcher::RhaiDispatcherBuilder;
 | 
			
		||||
///
 | 
			
		||||
/// let client = RhaiDispatcherBuilder::new()
 | 
			
		||||
///     .caller_id("my-service")
 | 
			
		||||
///     .redis_url("redis://localhost/")
 | 
			
		||||
///     .build()?;
 | 
			
		||||
/// ```
 | 
			
		||||
pub struct RhaiDispatcher {
 | 
			
		||||
    redis_client: redis::Client,
 | 
			
		||||
    caller_id: String,
 | 
			
		||||
    worker_id: String,
 | 
			
		||||
    context_id: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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
 | 
			
		||||
/// sensible defaults where appropriate.
 | 
			
		||||
///
 | 
			
		||||
/// # Required Configuration
 | 
			
		||||
///
 | 
			
		||||
/// - `caller_id`: A unique identifier for this client instance
 | 
			
		||||
///
 | 
			
		||||
/// # Optional Configuration
 | 
			
		||||
///
 | 
			
		||||
/// - `redis_url`: Redis connection URL (defaults to "redis://127.0.0.1/")
 | 
			
		||||
pub struct RhaiDispatcherBuilder {
 | 
			
		||||
    redis_url: Option<String>,
 | 
			
		||||
    caller_id: String,
 | 
			
		||||
    worker_id: String,
 | 
			
		||||
    context_id: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            redis_url: None,
 | 
			
		||||
            caller_id: "".to_string(),
 | 
			
		||||
            worker_id: "".to_string(),
 | 
			
		||||
            context_id: "".to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets the caller ID for this client instance.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The caller ID is used to identify which client submitted a task and is
 | 
			
		||||
    /// included in task metadata. This is required and the build will fail if
 | 
			
		||||
    /// not provided.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// * `caller_id` - A unique identifier for this client instance
 | 
			
		||||
    pub fn caller_id(mut self, caller_id: &str) -> Self {
 | 
			
		||||
        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.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If not provided, defaults to "redis://127.0.0.1/".
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// * `url` - Redis connection URL (e.g., "redis://localhost:6379/0")
 | 
			
		||||
    pub fn redis_url(mut self, url: &str) -> Self {
 | 
			
		||||
        self.redis_url = Some(url.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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
 | 
			
		||||
    /// connection cannot be established.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Returns
 | 
			
		||||
    ///
 | 
			
		||||
    /// * `Ok(RhaiDispatcher)` - Successfully configured client
 | 
			
		||||
    /// * `Err(RhaiDispatcherError)` - Configuration or connection error
 | 
			
		||||
    pub fn build(self) -> Result<RhaiDispatcher, RhaiDispatcherError> {
 | 
			
		||||
        let url = self
 | 
			
		||||
            .redis_url
 | 
			
		||||
            .unwrap_or_else(|| "redis://127.0.0.1/".to_string());
 | 
			
		||||
        let client = redis::Client::open(url)?;
 | 
			
		||||
        Ok(RhaiDispatcher {
 | 
			
		||||
            redis_client: client,
 | 
			
		||||
            caller_id: self.caller_id,
 | 
			
		||||
            worker_id: self.worker_id,
 | 
			
		||||
            context_id: self.context_id,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Representation of a script execution request.
 | 
			
		||||
///
 | 
			
		||||
/// This structure contains all the information needed to execute a Rhai script
 | 
			
		||||
/// on a worker service, including the script content, target worker, and timeout.
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct PlayRequest {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub worker_id: String,
 | 
			
		||||
    pub context_id: String,
 | 
			
		||||
    pub script: String,
 | 
			
		||||
    pub timeout: Duration,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for constructing and submitting script execution requests.
 | 
			
		||||
///
 | 
			
		||||
/// This builder provides a fluent interface for configuring script execution
 | 
			
		||||
/// parameters and offers two submission modes: fire-and-forget (`submit()`)
 | 
			
		||||
/// and request-reply (`await_response()`).
 | 
			
		||||
///
 | 
			
		||||
/// # Example
 | 
			
		||||
///
 | 
			
		||||
/// ```rust
 | 
			
		||||
/// use std::time::Duration;
 | 
			
		||||
///
 | 
			
		||||
/// let result = client
 | 
			
		||||
///     .new_play_request()
 | 
			
		||||
///     .worker_id("worker-1")
 | 
			
		||||
///     .script(r#"print("Hello, World!");"#)
 | 
			
		||||
///     .timeout(Duration::from_secs(30))
 | 
			
		||||
///     .await_response()
 | 
			
		||||
///     .await?;
 | 
			
		||||
/// ```
 | 
			
		||||
pub struct PlayRequestBuilder<'a> {
 | 
			
		||||
    client: &'a RhaiDispatcher,
 | 
			
		||||
    request_id: String,
 | 
			
		||||
    worker_id: String,
 | 
			
		||||
    context_id: String,
 | 
			
		||||
    caller_id: String,
 | 
			
		||||
    script: String,
 | 
			
		||||
    timeout: Duration,
 | 
			
		||||
    retries: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> PlayRequestBuilder<'a> {
 | 
			
		||||
    pub fn new(client: &'a RhaiDispatcher) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            client,
 | 
			
		||||
            request_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(5),
 | 
			
		||||
            retries: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn request_id(mut self, request_id: &str) -> Self {
 | 
			
		||||
        self.request_id = request_id.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn worker_id(mut self, worker_id: &str) -> Self {
 | 
			
		||||
        self.worker_id = worker_id.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn context_id(mut self, context_id: &str) -> Self {
 | 
			
		||||
        self.context_id = context_id.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn script(mut self, script: &str) -> Self {
 | 
			
		||||
        self.script = script.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn script_path(mut self, script_path: &str) -> Self {
 | 
			
		||||
        self.script = std::fs::read_to_string(script_path).unwrap();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn timeout(mut self, timeout: Duration) -> Self {
 | 
			
		||||
        self.timeout = timeout;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn build(self) -> Result<PlayRequest, RhaiDispatcherError> {
 | 
			
		||||
        let request_id = if self.request_id.is_empty() {
 | 
			
		||||
            // Generate a UUID for the request_id
 | 
			
		||||
            Uuid::new_v4().to_string()
 | 
			
		||||
        } else {
 | 
			
		||||
            self.request_id.clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if self.context_id.is_empty() {
 | 
			
		||||
            return Err(RhaiDispatcherError::ContextIdMissing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.caller_id.is_empty() {
 | 
			
		||||
            return Err(RhaiDispatcherError::ContextIdMissing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let play_request = PlayRequest {
 | 
			
		||||
            id: request_id,
 | 
			
		||||
            worker_id: self.worker_id.clone(),
 | 
			
		||||
            context_id: self.context_id.clone(),
 | 
			
		||||
            script: self.script.clone(),
 | 
			
		||||
            timeout: self.timeout,
 | 
			
		||||
        };
 | 
			
		||||
        Ok(play_request)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn submit(self) -> Result<(), RhaiDispatcherError> {
 | 
			
		||||
        // Build the request and submit using self.client
 | 
			
		||||
        println!(
 | 
			
		||||
            "Submitting request {} with timeout {:?}",
 | 
			
		||||
            self.request_id, self.timeout
 | 
			
		||||
        );
 | 
			
		||||
        self.client.submit_play_request(&self.build()?).await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn await_response(self) -> Result<RhaiTaskDetails, RhaiDispatcherError> {
 | 
			
		||||
        // Build the request and submit using self.client
 | 
			
		||||
        let result = self
 | 
			
		||||
            .client
 | 
			
		||||
            .submit_play_request_and_await_result(&self.build()?)
 | 
			
		||||
            .await;
 | 
			
		||||
        result
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RhaiDispatcher {
 | 
			
		||||
    pub fn new_play_request(&self) -> PlayRequestBuilder {
 | 
			
		||||
        PlayRequestBuilder::new(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Internal helper to submit script details and push to work queue
 | 
			
		||||
    async fn submit_play_request_using_connection(
 | 
			
		||||
        &self,
 | 
			
		||||
        conn: &mut redis::aio::MultiplexedConnection,
 | 
			
		||||
        play_request: &PlayRequest,
 | 
			
		||||
    ) -> Result<(), RhaiDispatcherError> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
 | 
			
		||||
        let task_key = format!("{}{}", NAMESPACE_PREFIX, play_request.id);
 | 
			
		||||
 | 
			
		||||
        let worker_queue_key = format!(
 | 
			
		||||
            "{}{}",
 | 
			
		||||
            NAMESPACE_PREFIX,
 | 
			
		||||
            play_request.worker_id.replace(" ", "_").to_lowercase()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Submitting play request: {} to worker: {} with namespace prefix: {}",
 | 
			
		||||
            play_request.id, play_request.worker_id, NAMESPACE_PREFIX
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let hset_args: Vec<(String, String)> = vec![
 | 
			
		||||
            ("taskId".to_string(), play_request.id.to_string()), // Add taskId
 | 
			
		||||
            ("script".to_string(), play_request.script.clone()), // script is moved here
 | 
			
		||||
            ("callerId".to_string(), self.caller_id.clone()),    // script is moved here
 | 
			
		||||
            ("contextId".to_string(), play_request.context_id.clone()), // script is moved here
 | 
			
		||||
            ("status".to_string(), "pending".to_string()),
 | 
			
		||||
            ("createdAt".to_string(), now.to_rfc3339()),
 | 
			
		||||
            ("updatedAt".to_string(), now.to_rfc3339()),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // Ensure hset_args is a slice of tuples (String, String)
 | 
			
		||||
        // The redis crate's hset_multiple expects &[(K, V)]
 | 
			
		||||
        // conn.hset_multiple::<_, String, String, ()>(&task_key, &hset_args).await?;
 | 
			
		||||
        // Simpler:
 | 
			
		||||
        // Explicitly type K, F, V for hset_multiple if inference is problematic.
 | 
			
		||||
        // RV (return value of the command itself) is typically () for HSET type commands.
 | 
			
		||||
        conn.hset_multiple::<_, _, _, ()>(&task_key, &hset_args)
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
        // lpush also infers its types, RV is typically i64 (length of list) or () depending on exact command variant
 | 
			
		||||
        // For `redis::AsyncCommands::lpush`, it's `RedisResult<R>` where R: FromRedisValue
 | 
			
		||||
        // Often this is the length of the list. Let's allow inference or specify if needed.
 | 
			
		||||
        let _: redis::RedisResult<i64> =
 | 
			
		||||
            conn.lpush(&worker_queue_key, play_request.id.clone()).await;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Internal helper to await response from worker
 | 
			
		||||
    async fn await_response_from_connection(
 | 
			
		||||
        &self,
 | 
			
		||||
        conn: &mut redis::aio::MultiplexedConnection,
 | 
			
		||||
        task_key: &String,
 | 
			
		||||
        reply_queue_key: &String,
 | 
			
		||||
        timeout: Duration,
 | 
			
		||||
    ) -> Result<RhaiTaskDetails, RhaiDispatcherError> {
 | 
			
		||||
        // 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
 | 
			
		||||
 | 
			
		||||
        match conn
 | 
			
		||||
            .blpop::<&String, Option<(String, String)>>(reply_queue_key, blpop_timeout_secs as f64)
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            Ok(Some((_queue, result_message_str))) => {
 | 
			
		||||
                // Attempt to deserialize the result message into RhaiTaskDetails or a similar structure
 | 
			
		||||
                // For now, we assume the worker sends back a JSON string of RhaiTaskDetails
 | 
			
		||||
                // or at least status, output, error.
 | 
			
		||||
                // Let's refine what the worker sends. For now, assume it's a simplified result.
 | 
			
		||||
                // The worker should ideally send a JSON string that can be parsed into RhaiTaskDetails.
 | 
			
		||||
                // For this example, let's assume the worker sends a JSON string of a simplified result structure.
 | 
			
		||||
                // A more robust approach would be for the worker to send the full RhaiTaskDetails (or relevant parts)
 | 
			
		||||
                // and the client deserializes that.
 | 
			
		||||
                // For now, let's assume the worker sends a JSON string of RhaiTaskDetails.
 | 
			
		||||
                match serde_json::from_str::<RhaiTaskDetails>(&result_message_str) {
 | 
			
		||||
                    Ok(details) => {
 | 
			
		||||
                        info!(
 | 
			
		||||
                            "Task {} finished with status: {}",
 | 
			
		||||
                            details.task_id, details.status
 | 
			
		||||
                        );
 | 
			
		||||
                        // Optionally, delete the reply queue
 | 
			
		||||
                        let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await;
 | 
			
		||||
                        Ok(details)
 | 
			
		||||
                    }
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        error!(
 | 
			
		||||
                            "Failed to deserialize result message from reply queue: {}",
 | 
			
		||||
                            e
 | 
			
		||||
                        );
 | 
			
		||||
                        // Optionally, delete the reply queue
 | 
			
		||||
                        let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await;
 | 
			
		||||
                        Err(RhaiDispatcherError::SerializationError(e))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Ok(None) => {
 | 
			
		||||
                // BLPOP timed out
 | 
			
		||||
                warn!(
 | 
			
		||||
                    "Timeout waiting for result on reply queue {} for task {}",
 | 
			
		||||
                    reply_queue_key, task_key
 | 
			
		||||
                );
 | 
			
		||||
                // Optionally, delete the reply queue
 | 
			
		||||
                let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await;
 | 
			
		||||
                Err(RhaiDispatcherError::Timeout(task_key.clone()))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                // Redis error
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Redis error on BLPOP for reply queue {}: {}",
 | 
			
		||||
                    reply_queue_key, e
 | 
			
		||||
                );
 | 
			
		||||
                // Optionally, delete the reply queue
 | 
			
		||||
                let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await;
 | 
			
		||||
                Err(RhaiDispatcherError::RedisError(e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // New method using dedicated reply queue
 | 
			
		||||
    pub async fn submit_play_request(
 | 
			
		||||
        &self,
 | 
			
		||||
        play_request: &PlayRequest,
 | 
			
		||||
    ) -> Result<(), RhaiDispatcherError> {
 | 
			
		||||
        let mut conn = self.redis_client.get_multiplexed_async_connection().await?;
 | 
			
		||||
 | 
			
		||||
        self.submit_play_request_using_connection(
 | 
			
		||||
            &mut conn,
 | 
			
		||||
            &play_request, // Pass the task_id parameter
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // New method using dedicated reply queue
 | 
			
		||||
    pub async fn submit_play_request_and_await_result(
 | 
			
		||||
        &self,
 | 
			
		||||
        play_request: &PlayRequest,
 | 
			
		||||
    ) -> Result<RhaiTaskDetails, RhaiDispatcherError> {
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        self.submit_play_request_using_connection(
 | 
			
		||||
            &mut conn,
 | 
			
		||||
            &play_request, // Pass the task_id parameter
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
        info!(
 | 
			
		||||
            "Task {} submitted. Waiting for result on queue {} with timeout {:?}...",
 | 
			
		||||
            play_request.id, // This is the UUID
 | 
			
		||||
            reply_queue_key,
 | 
			
		||||
            play_request.timeout
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self.await_response_from_connection(
 | 
			
		||||
            &mut conn,
 | 
			
		||||
            &play_request.id,
 | 
			
		||||
            &reply_queue_key,
 | 
			
		||||
            play_request.timeout,
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Method to get task status
 | 
			
		||||
    pub async fn get_task_status(
 | 
			
		||||
        &self,
 | 
			
		||||
        task_id: &str,
 | 
			
		||||
    ) -> Result<Option<RhaiTaskDetails>, RhaiDispatcherError> {
 | 
			
		||||
        let mut conn = self.redis_client.get_multiplexed_async_connection().await?;
 | 
			
		||||
        let task_key = format!("{}{}", NAMESPACE_PREFIX, task_id);
 | 
			
		||||
 | 
			
		||||
        let result_map: Option<std::collections::HashMap<String, String>> =
 | 
			
		||||
            conn.hgetall(&task_key).await?;
 | 
			
		||||
 | 
			
		||||
        match result_map {
 | 
			
		||||
            Some(map) => {
 | 
			
		||||
                // Reconstruct RhaiTaskDetails from HashMap
 | 
			
		||||
                let details = RhaiTaskDetails {
 | 
			
		||||
                    task_id: task_id.to_string(), // Use the task_id parameter passed to the function
 | 
			
		||||
                    script: map.get("script").cloned().unwrap_or_else(|| {
 | 
			
		||||
                        warn!("Task {}: 'script' field missing from Redis hash, defaulting to empty.", task_id);
 | 
			
		||||
                        String::new()
 | 
			
		||||
                    }),
 | 
			
		||||
                    status: map.get("status").cloned().unwrap_or_else(|| {
 | 
			
		||||
                        warn!("Task {}: 'status' field missing from Redis hash, defaulting to empty.", task_id);
 | 
			
		||||
                        String::new()
 | 
			
		||||
                    }),
 | 
			
		||||
                    // client_rpc_id is no longer a field in RhaiTaskDetails
 | 
			
		||||
                    output: map.get("output").cloned(),
 | 
			
		||||
                    error: map.get("error").cloned(),
 | 
			
		||||
                    created_at: map.get("createdAt")
 | 
			
		||||
                                    .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
 | 
			
		||||
                                    .map(|dt| dt.with_timezone(&Utc))
 | 
			
		||||
                                    .unwrap_or_else(|| {
 | 
			
		||||
                                        warn!("Task {}: 'createdAt' field missing or invalid in Redis hash, defaulting to Utc::now().", task_id);
 | 
			
		||||
                                        Utc::now()
 | 
			
		||||
                                    }),
 | 
			
		||||
                    updated_at: map.get("updatedAt")
 | 
			
		||||
                                    .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
 | 
			
		||||
                                    .map(|dt| dt.with_timezone(&Utc))
 | 
			
		||||
                                    .unwrap_or_else(|| {
 | 
			
		||||
                                        warn!("Task {}: 'updatedAt' field missing or invalid in Redis hash, defaulting to Utc::now().", task_id);
 | 
			
		||||
                                        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
 | 
			
		||||
                // for data integrity, though the struct construction above uses the input task_id directly.
 | 
			
		||||
                if let Some(redis_task_id) = map.get("taskId") {
 | 
			
		||||
                    if redis_task_id != task_id {
 | 
			
		||||
                        warn!("Task {}: Mismatch between requested task_id and taskId found in Redis hash ('{}'). Proceeding with requested task_id.", task_id, redis_task_id);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    warn!("Task {}: 'taskId' field missing from Redis hash.", task_id);
 | 
			
		||||
                }
 | 
			
		||||
                Ok(Some(details))
 | 
			
		||||
            }
 | 
			
		||||
            None => Ok(None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    // use super::*;
 | 
			
		||||
    // Basic tests can be added later, especially once examples are in place.
 | 
			
		||||
    // For now, ensuring it compiles is the priority.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn it_compiles() {
 | 
			
		||||
        assert_eq!(2 + 2, 4);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user