rename client and move incomplete projects to research

This commit is contained in:
Timur Gordon 2025-07-09 23:38:47 +02:00
parent e75c050745
commit 3b1bc9a838
28 changed files with 229 additions and 126 deletions

View File

@ -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!");

View File

@ -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!");

View File

@ -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" }

View File

@ -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<dyn std::error::Error>> {
.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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
}
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. */
},
}

View File

@ -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();

View File

@ -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"] }

View File

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
log::info!("Output: {}", output);
}
}
Err(RhaiClientError::Timeout(task_id)) => {
Err(RhaiDispatcherError::Timeout(task_id)) => {
log::error!("Task {} timed out.", task_id);
}
Err(e) => {

View File

@ -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

View File

@ -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<String>,
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<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
@ -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<dyn std::error::Error>> {
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()

View File

@ -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

View File

@ -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<dyn std::error::Error>> {
.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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
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())

View File

@ -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<dyn std::error::Error>> {
//! // 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<redis::RedisError> for RhaiClientError {
impl From<redis::RedisError> for RhaiDispatcherError {
fn from(err: redis::RedisError) -> Self {
RhaiClientError::RedisError(err)
RhaiDispatcherError::RedisError(err)
}
}
impl From<serde_json::Error> for RhaiClientError {
impl From<serde_json::Error> 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<String>,
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<RhaiClient, RhaiClientError> {
/// * `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)?;
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<PlayRequest, RhaiClientError> {
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()
@ -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<RhaiTaskDetails, RhaiClientError> {
pub async fn await_response(self) -> Result<RhaiTaskDetails, RhaiDispatcherError> {
// 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<RhaiTaskDetails, RhaiClientError> {
) -> 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
@ -458,7 +493,7 @@ impl RhaiClient {
);
// Optionally, delete the reply queue
let _: redis::RedisResult<i32> = 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<i32> = 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<i32> = 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<RhaiTaskDetails, RhaiClientError> {
) -> 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
@ -535,7 +570,7 @@ impl RhaiClient {
pub async fn get_task_status(
&self,
task_id: &str,
) -> Result<Option<RhaiTaskDetails>, RhaiClientError> {
) -> Result<Option<RhaiTaskDetails>, 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

BIN
src/repl/.DS_Store vendored

Binary file not shown.