cargo fix and fmt

This commit is contained in:
timurgordon
2025-06-19 10:44:40 +03:00
parent 32bcef1d1d
commit d6c47b8f13
58 changed files with 2190 additions and 1463 deletions

View File

@@ -12,7 +12,7 @@
//! ```
use circle_client_ws::CircleWsClientBuilder;
use log::{info, error};
use log::{error, info};
use std::time::Duration;
use tokio::time::sleep;
@@ -29,11 +29,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Example 1: Authenticate with private key
info!("=== Example 1: Private Key Authentication ===");
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
let mut client = CircleWsClientBuilder::new(ws_url.clone())
.with_keypair(private_key.to_string())
.build();
match client.connect().await {
Ok(_) => {
info!("Successfully connected to WebSocket");
@@ -67,27 +67,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
error!("Play request failed: {}", e);
}
}
// Keep connection alive for a moment
sleep(Duration::from_secs(2)).await;
// Disconnect
client.disconnect().await;
info!("Disconnected from WebSocket");
// Example 3: Different private key authentication
info!("=== Example 3: Different Private Key Authentication ===");
let private_key2 = "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321";
let mut client2 = CircleWsClientBuilder::new(ws_url.clone())
.with_keypair(private_key2.to_string())
.build();
match client2.connect().await {
Ok(_) => {
info!("Connected with second private key authentication");
match client2.authenticate().await {
Ok(true) => {
info!("Successfully authenticated with second private key");
@@ -108,7 +107,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
error!("Second private key authentication failed: {}", e);
}
}
client2.disconnect().await;
}
Err(e) => {
@@ -119,11 +118,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Example 4: Non-authenticated connection (fallback)
info!("=== Example 4: Non-Authenticated Connection ===");
let mut client3 = CircleWsClientBuilder::new(ws_url).build();
match client3.connect().await {
Ok(()) => {
info!("Connected without authentication (fallback mode)");
let script = "print('Hello from non-auth client!');".to_string();
match client3.play(script).await {
Ok(result) => {
@@ -133,7 +132,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
error!("Non-auth request failed: {}", e);
}
}
client3.disconnect().await;
}
Err(e) => {
@@ -143,4 +142,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("Simplified authentication example completed");
Ok(())
}
}

View File

@@ -1,5 +1,5 @@
//! Authentication simulation example
//!
//!
//! This example simulates the authentication flow without requiring a running server.
//! It demonstrates:
//! 1. Key generation and management
@@ -8,32 +8,28 @@
//! 4. Credential management
//! 5. Authentication state checking
use std::time::{SystemTime, UNIX_EPOCH};
use log::info;
use std::time::{SystemTime, UNIX_EPOCH};
// Import authentication modules
use circle_client_ws::CircleWsClientBuilder;
#[cfg(feature = "crypto")]
use circle_client_ws::auth::{
generate_private_key,
derive_public_key,
sign_message,
verify_signature,
AuthCredentials,
NonceResponse
derive_public_key, generate_private_key, sign_message, verify_signature, AuthCredentials,
NonceResponse,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
env_logger::init();
info!("🔐 Starting authentication simulation example");
// Step 1: Generate cryptographic keys
info!("🔑 Generating cryptographic keys...");
#[cfg(feature = "crypto")]
let (private_key, public_key) = {
let private_key = generate_private_key()?;
@@ -42,38 +38,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("✅ Derived public key: {}...", &public_key[..20]);
(private_key, public_key)
};
#[cfg(not(feature = "crypto"))]
let (private_key, _public_key) = {
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
let private_key =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
let public_key = "04abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string();
info!("📝 Using fallback keys (crypto feature disabled)");
(private_key, public_key)
};
// Step 2: Simulate nonce request and response
info!("📡 Simulating nonce request...");
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let simulated_nonce = format!("nonce_{}_{}", current_time, "abcdef123456");
let expires_at = current_time + 300; // 5 minutes from now
#[cfg(feature = "crypto")]
let nonce_response = NonceResponse {
nonce: simulated_nonce.clone(),
expires_at,
};
info!("✅ Simulated nonce response:");
info!(" Nonce: {}", simulated_nonce);
info!(" Expires at: {}", expires_at);
// Step 3: Sign the nonce
info!("✍️ Signing nonce with private key...");
#[cfg(feature = "crypto")]
let signature = {
match sign_message(&private_key, &simulated_nonce) {
@@ -87,16 +84,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
};
#[cfg(not(feature = "crypto"))]
let _signature = {
info!("📝 Using fallback signature (crypto feature disabled)");
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string()
};
// Step 4: Verify the signature
info!("🔍 Verifying signature...");
#[cfg(feature = "crypto")]
{
match verify_signature(&public_key, &simulated_nonce, &signature) {
@@ -111,12 +108,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
}
#[cfg(not(feature = "crypto"))]
{
info!("📝 Skipping signature verification (crypto feature disabled)");
}
// Step 5: Create authentication credentials
info!("📋 Creating authentication credentials...");
#[cfg(feature = "crypto")]
@@ -124,9 +121,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
public_key.clone(),
signature.clone(),
nonce_response.nonce.clone(),
expires_at
expires_at,
);
#[cfg(feature = "crypto")]
{
info!("✅ Credentials created:");
@@ -136,77 +133,86 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(" Expires at: {}", credentials.expires_at);
info!(" Is expired: {}", credentials.is_expired());
info!(" Expires within 60s: {}", credentials.expires_within(60));
info!(" Expires within 400s: {}", credentials.expires_within(400));
info!(
" Expires within 400s: {}",
credentials.expires_within(400)
);
}
// Step 6: Create client with authentication
info!("🔌 Creating WebSocket client with authentication...");
let _client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
.with_keypair(private_key.clone())
.build();
info!("✅ Client created");
// Step 7: Demonstrate key rotation
info!("🔄 Demonstrating key rotation...");
#[cfg(feature = "crypto")]
{
let new_private_key = generate_private_key()?;
let new_public_key = derive_public_key(&new_private_key)?;
info!("✅ Generated new keys:");
info!(" New private key: {}...", &new_private_key[..10]);
info!(" New public key: {}...", &new_public_key[..20]);
// Create new client with rotated keys
let _new_client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
.with_keypair(new_private_key)
.build();
info!("✅ Created client with rotated keys");
}
#[cfg(not(feature = "crypto"))]
{
info!("📝 Skipping key rotation (crypto feature disabled)");
}
// Step 8: Demonstrate credential expiration
info!("⏰ Demonstrating credential expiration...");
// Create credentials that expire soon
#[cfg(feature = "crypto")]
let short_lived_credentials = AuthCredentials::new(
public_key,
signature,
nonce_response.nonce,
current_time + 5 // Expires in 5 seconds
current_time + 5, // Expires in 5 seconds
);
#[cfg(feature = "crypto")]
{
info!("✅ Created short-lived credentials:");
info!(" Expires at: {}", short_lived_credentials.expires_at);
info!(" Is expired: {}", short_lived_credentials.is_expired());
info!(" Expires within 10s: {}", short_lived_credentials.expires_within(10));
info!(
" Expires within 10s: {}",
short_lived_credentials.expires_within(10)
);
// Wait a moment and check again
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
info!("⏳ After 1 second:");
info!(" Is expired: {}", short_lived_credentials.is_expired());
info!(" Expires within 5s: {}", short_lived_credentials.expires_within(5));
info!(
" Expires within 5s: {}",
short_lived_credentials.expires_within(5)
);
}
info!("🎉 Authentication simulation completed successfully!");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_key_generation() {
#[cfg(feature = "crypto")]
@@ -214,13 +220,13 @@ mod tests {
let private_key = generate_private_key().unwrap();
assert!(private_key.starts_with("0x"));
assert_eq!(private_key.len(), 66); // 0x + 64 hex chars
let public_key = derive_public_key(&private_key).unwrap();
assert!(public_key.starts_with("04"));
assert_eq!(public_key.len(), 130); // 04 + 128 hex chars (uncompressed)
}
}
#[tokio::test]
async fn test_signature_flow() {
#[cfg(feature = "crypto")]
@@ -228,29 +234,29 @@ mod tests {
let private_key = generate_private_key().unwrap();
let public_key = derive_public_key(&private_key).unwrap();
let message = "test_nonce_12345";
let signature = sign_message(&private_key, message).unwrap();
let is_valid = verify_signature(&public_key, message, &signature).unwrap();
assert!(is_valid);
}
}
#[test]
fn test_credentials() {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
#[cfg(feature = "crypto")]
let credentials = AuthCredentials::new(
"04abcdef...".to_string(),
"0x123456...".to_string(),
"nonce_123".to_string(),
current_time + 300
current_time + 300,
);
#[cfg(feature = "crypto")]
{
assert!(!credentials.is_expired());
@@ -258,4 +264,4 @@ mod tests {
assert!(!credentials.expires_within(100));
}
}
}
}

View File

@@ -16,10 +16,10 @@
//! 4. The launcher will run until you stop it with Ctrl+C.
use launcher::{run_launcher, Args, CircleConfig};
use log::{error, info};
use std::error::Error as StdError;
use std::fs;
use std::path::PathBuf;
use std::error::Error as StdError;
use log::{error, info};
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
@@ -54,7 +54,11 @@ async fn main() -> Result<(), Box<dyn StdError>> {
let mut circle_configs: Vec<CircleConfig> = match serde_json::from_str(&config_content) {
Ok(configs) => configs,
Err(e) => {
error!("Failed to parse {}: {}. Ensure it's a valid JSON array of CircleConfig.", config_path.display(), e);
error!(
"Failed to parse {}: {}. Ensure it's a valid JSON array of CircleConfig.",
config_path.display(),
e
);
return Err(Box::new(e) as Box<dyn StdError>);
}
};
@@ -68,7 +72,10 @@ async fn main() -> Result<(), Box<dyn StdError>> {
}
if circle_configs.is_empty() {
info!("No circle configurations found in {}. Exiting.", config_path.display());
info!(
"No circle configurations found in {}. Exiting.",
config_path.display()
);
return Ok(());
}
@@ -80,4 +87,4 @@ async fn main() -> Result<(), Box<dyn StdError>> {
println!("--- OurWorld Example Finished ---");
Ok(())
}
}

View File

@@ -1,51 +1,51 @@
[
{
"name": "OurWorld",
"public_key": "02acbca22369b7f10584348056ae48779e04534cd34d37b7db0f4996f4d9d5e2a5",
"secret_key": "0c75df7425c799eb769049cf48891299761660396d772c687fa84cac5ec62570",
"worker_queue": "rhai_tasks:02acbca22369b7f10584348056ae48779e04534cd34d37b7db0f4996f4d9d5e2a5",
"public_key": "02b1ff38c18f66ffcfde1ff4931093484a96d378db55c1306a0760b39172d74099",
"secret_key": "86ed603c86f8938060575f7b1c7e4e4ddf72030ad2ea1699a8e9d1fb3a610869",
"worker_queue": "rhai_tasks:02b1ff38c18f66ffcfde1ff4931093484a96d378db55c1306a0760b39172d74099",
"ws_url": "ws://127.0.0.1:9000"
},
{
"name": "Dunia Cybercity",
"public_key": "03d97b1a357c3ceb2f0eb78f8e2c71beda9190db5cb7e5112150105132effb35e0",
"secret_key": "4fad664608e8de55f0e5e1712241e71dc0864be125bc8633e50601fca8040791",
"worker_queue": "rhai_tasks:03d97b1a357c3ceb2f0eb78f8e2c71beda9190db5cb7e5112150105132effb35e0",
"public_key": "020d8b1e3baab9991a82e9b55e117f45fda58b3f90b072dbbf10888f3195bfe6b9",
"secret_key": "b1ac20e4c6ace638f7f9e07918997fc35b2425de78152139c8b54629ca303b81",
"worker_queue": "rhai_tasks:020d8b1e3baab9991a82e9b55e117f45fda58b3f90b072dbbf10888f3195bfe6b9",
"ws_url": "ws://127.0.0.1:9001"
},
{
"name": "Sikana",
"public_key": "0389595b28cfa98b45fa3c222db79892f3face65e7ef06d44e35d642967e45ed6e",
"secret_key": "fd59ddbf0d0bada725c911dc7e3317754ac552aa1ac84cfcb899bdfe3591e1f4",
"worker_queue": "rhai_tasks:0389595b28cfa98b45fa3c222db79892f3face65e7ef06d44e35d642967e45ed6e",
"public_key": "0363dbff9f2b6dbaf58d3e8774db54dcccd10e23461ebf9a93cca63f8aa321d11d",
"secret_key": "9383663dcac577c14679c3487e6ffe7ff95040f422d391219ea530b892c1b0a0",
"worker_queue": "rhai_tasks:0363dbff9f2b6dbaf58d3e8774db54dcccd10e23461ebf9a93cca63f8aa321d11d",
"ws_url": "ws://127.0.0.1:9002"
},
{
"name": "Threefold",
"public_key": "03270f06ee4a7d42a9f6c22c9a7d6d0138cd15d4fa659026e2e6572fc6c6a6ea18",
"secret_key": "e204c0215bec80f74df49ea5b1592de3c6739cced339ace801bb7e158eb62231",
"worker_queue": "rhai_tasks:03270f06ee4a7d42a9f6c22c9a7d6d0138cd15d4fa659026e2e6572fc6c6a6ea18",
"public_key": "02c19cd347605dab98fb767b5e53c5fa5131d47a46b5f560b565fd4d79c1190994",
"secret_key": "0c4f5172724218650ea5806f5c9f8d4d4c8197c0c775f9d022fd8a192ad59048",
"worker_queue": "rhai_tasks:02c19cd347605dab98fb767b5e53c5fa5131d47a46b5f560b565fd4d79c1190994",
"ws_url": "ws://127.0.0.1:9003"
},
{
"name": "Mbweni",
"public_key": "02724cf23e4ac95d0f14984f55c6955b3ca5ab2275d7ac2a2e4baf3596caf8606c",
"secret_key": "3c013e2e5f64692f044d17233e5fabdb0577629f898359115e69c3e594d5f43e",
"worker_queue": "rhai_tasks:02724cf23e4ac95d0f14984f55c6955b3ca5ab2275d7ac2a2e4baf3596caf8606c",
"public_key": "0251808090b5b916e6187b63b6c97411f9d5406a9a6179408b90e3ff83042e7a9c",
"secret_key": "c824b3334350e2b267be2d4ceb1db53e98c9f386d2855aa7130227caa580805c",
"worker_queue": "rhai_tasks:0251808090b5b916e6187b63b6c97411f9d5406a9a6179408b90e3ff83042e7a9c",
"ws_url": "ws://127.0.0.1:9004"
},
{
"name": "Geomind",
"public_key": "030d8ceb47d445c92b7c3f13e9e134eebcb1d83beed424425f734164544eb58eed",
"secret_key": "dbd6dd383a6f56042710f72ce2ac68266650bbfb61432cdd139e98043b693e7c",
"worker_queue": "rhai_tasks:030d8ceb47d445c92b7c3f13e9e134eebcb1d83beed424425f734164544eb58eed",
"public_key": "037e2def151e7587b95519370e5d1023b9f24845e8e23a6535b0aad3cff20a859b",
"secret_key": "9c701a02ebba983d04ecbccee5072ed2cebd67ead4677c79a72d089d3ff29295",
"worker_queue": "rhai_tasks:037e2def151e7587b95519370e5d1023b9f24845e8e23a6535b0aad3cff20a859b",
"ws_url": "ws://127.0.0.1:9005"
},
{
"name": "Freezone",
"public_key": "02dd21025c1d47421eccc2264c87538d41126da772a9a3f0e7226807fed89c9971",
"secret_key": "0c0c6b02c20fcd4ccfb2afeae249979ddd623e6f6edd17af4a9a5a19bc1b15ae",
"worker_queue": "rhai_tasks:02dd21025c1d47421eccc2264c87538d41126da772a9a3f0e7226807fed89c9971",
"public_key": "02d4bf2713876cff2428f3f5e7e6191028374994d43a2c0f3d62c728a22d7f4aed",
"secret_key": "602c1bdd95489c7153676488976e9a24483cb353778332ec3b7644c3f05f5af2",
"worker_queue": "rhai_tasks:02d4bf2713876cff2428f3f5e7e6191028374994d43a2c0f3d62c728a22d7f4aed",
"ws_url": "ws://127.0.0.1:9006"
}
]

View File

@@ -1,6 +1,6 @@
use std::process::{Command, Child, Stdio};
use std::time::Duration;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::time::Duration;
use tokio::time::sleep;
// tokio_tungstenite and direct futures_util for ws stream are no longer needed here
// use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};
@@ -32,28 +32,54 @@ impl ChildProcessGuard {
impl Drop for ChildProcessGuard {
fn drop(&mut self) {
log::info!("Cleaning up {} process (PID: {})...", self.name, self.child.id());
log::info!(
"Cleaning up {} process (PID: {})...",
self.name,
self.child.id()
);
match self.child.kill() {
Ok(_) => {
log::info!("Successfully sent kill signal to {} (PID: {}).", self.name, self.child.id());
log::info!(
"Successfully sent kill signal to {} (PID: {}).",
self.name,
self.child.id()
);
// Optionally wait for a short period or check status
match self.child.wait() {
Ok(status) => log::info!("{} (PID: {}) exited with status: {}", self.name, self.child.id(), status),
Err(e) => log::warn!("Error waiting for {} (PID: {}): {}", self.name, self.child.id(), e),
Ok(status) => log::info!(
"{} (PID: {}) exited with status: {}",
self.name,
self.child.id(),
status
),
Err(e) => log::warn!(
"Error waiting for {} (PID: {}): {}",
self.name,
self.child.id(),
e
),
}
}
Err(e) => log::error!("Failed to kill {} (PID: {}): {}", self.name, self.child.id(), e),
Err(e) => log::error!(
"Failed to kill {} (PID: {}): {}",
self.name,
self.child.id(),
e
),
}
}
}
fn find_target_dir() -> Result<PathBuf, String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf();
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
let workspace_root = PathBuf::from(manifest_dir)
.parent()
.ok_or("Failed to get workspace root")?
.to_path_buf();
Ok(workspace_root.join("target").join("debug"))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
@@ -62,7 +88,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
log::error!("Could not determine target directory: {}", e);
e
})?;
let rhai_worker_path = target_dir.join(RHAI_WORKER_BIN_NAME);
let circle_server_ws_path = target_dir.join(CIRCLE_SERVER_WS_BIN_NAME);
@@ -79,26 +105,46 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.stdout(Stdio::piped()) // Capture stdout
.stderr(Stdio::piped()) // Capture stderr
.spawn()?;
let _rhai_worker_guard = ChildProcessGuard::new(rhai_worker_process, RHAI_WORKER_BIN_NAME.to_string());
log::info!("{} started with PID {}", RHAI_WORKER_BIN_NAME, _rhai_worker_guard.child.id());
let _rhai_worker_guard =
ChildProcessGuard::new(rhai_worker_process, RHAI_WORKER_BIN_NAME.to_string());
log::info!(
"{} started with PID {}",
RHAI_WORKER_BIN_NAME,
_rhai_worker_guard.child.id()
);
log::info!("Starting {} for circle '{}' on port {}...", CIRCLE_SERVER_WS_BIN_NAME, TEST_CIRCLE_NAME, TEST_SERVER_PORT);
log::info!(
"Starting {} for circle '{}' on port {}...",
CIRCLE_SERVER_WS_BIN_NAME,
TEST_CIRCLE_NAME,
TEST_SERVER_PORT
);
let circle_server_process = Command::new(&circle_server_ws_path)
.args(["--port", &TEST_SERVER_PORT.to_string(), "--circle-name", TEST_CIRCLE_NAME])
.args([
"--port",
&TEST_SERVER_PORT.to_string(),
"--circle-name",
TEST_CIRCLE_NAME,
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let _circle_server_guard = ChildProcessGuard::new(circle_server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
log::info!("{} started with PID {}", CIRCLE_SERVER_WS_BIN_NAME, _circle_server_guard.child.id());
let _circle_server_guard =
ChildProcessGuard::new(circle_server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
log::info!(
"{} started with PID {}",
CIRCLE_SERVER_WS_BIN_NAME,
_circle_server_guard.child.id()
);
// Give servers a moment to start
sleep(Duration::from_secs(3)).await; // Increased sleep
let ws_url_str = format!("ws://127.0.0.1:{}/ws", TEST_SERVER_PORT);
log::info!("Creating CircleWsClient for {}...", ws_url_str);
let mut client = CircleWsClientBuilder::new(ws_url_str.clone()).build();
log::info!("Connecting CircleWsClient...");
client.connect().await.map_err(|e| {
log::error!("CircleWsClient connection failed: {}", e);
@@ -108,8 +154,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let script_to_run = "let a = 5; let b = 10; print(\"E2E Rhai: \" + (a+b)); a + b";
log::info!("Sending 'play' request via CircleWsClient for script: '{}'", script_to_run);
log::info!(
"Sending 'play' request via CircleWsClient for script: '{}'",
script_to_run
);
match client.play(script_to_run.to_string()).await {
Ok(play_result) => {
log::info!("Received play result: {:?}", play_result);
@@ -121,12 +170,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
return Err(format!("CircleWsClient play request failed: {}", e).into());
}
}
log::info!("Disconnecting CircleWsClient...");
client.disconnect().await;
log::info!("CircleWsClient disconnected.");
log::info!("E2E Rhai flow example completed successfully.");
// Guards will automatically clean up child processes when they go out of scope here
Ok(())
}
}

View File

@@ -7,9 +7,9 @@
// Ensure circle_server_ws is compiled (cargo build --bin circle_server_ws).
use circle_client_ws::CircleWsClientBuilder;
use tokio::time::{sleep, Duration};
use std::process::{Command, Child, Stdio};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use tokio::time::{sleep, Duration};
const EXAMPLE_SERVER_PORT: u16 = 8089; // Using a specific port for this example
const WS_URL: &str = "ws://127.0.0.1:8089/ws";
@@ -32,26 +32,56 @@ impl ChildProcessGuard {
impl Drop for ChildProcessGuard {
fn drop(&mut self) {
log::info!("Cleaning up {} process (PID: {})...", self.name, self.child.id());
log::info!(
"Cleaning up {} process (PID: {})...",
self.name,
self.child.id()
);
match self.child.kill() {
Ok(_) => {
log::info!("Successfully sent kill signal to {} (PID: {}).", self.name, self.child.id());
log::info!(
"Successfully sent kill signal to {} (PID: {}).",
self.name,
self.child.id()
);
match self.child.wait() {
Ok(status) => log::info!("{} (PID: {}) exited with status: {}", self.name, self.child.id(), status),
Err(e) => log::warn!("Error waiting for {} (PID: {}): {}", self.name, self.child.id(), e),
Ok(status) => log::info!(
"{} (PID: {}) exited with status: {}",
self.name,
self.child.id(),
status
),
Err(e) => log::warn!(
"Error waiting for {} (PID: {}): {}",
self.name,
self.child.id(),
e
),
}
}
Err(e) => log::error!("Failed to kill {} (PID: {}): {}", self.name, self.child.id(), e),
Err(e) => log::error!(
"Failed to kill {} (PID: {}): {}",
self.name,
self.child.id(),
e
),
}
}
}
fn find_target_bin_path(bin_name: &str) -> Result<PathBuf, String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf();
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
let workspace_root = PathBuf::from(manifest_dir)
.parent()
.ok_or("Failed to get workspace root")?
.to_path_buf();
let bin_path = workspace_root.join("target").join("debug").join(bin_name);
if !bin_path.exists() {
return Err(format!("Binary '{}' not found at {:?}. Ensure it's built.", bin_name, bin_path));
return Err(format!(
"Binary '{}' not found at {:?}. Ensure it's built.",
bin_name, bin_path
));
}
Ok(bin_path)
}
@@ -63,18 +93,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let server_bin_path = find_target_bin_path(CIRCLE_SERVER_WS_BIN_NAME)?;
log::info!("Found server binary at: {:?}", server_bin_path);
log::info!("Starting {} for circle '{}' on port {}...", CIRCLE_SERVER_WS_BIN_NAME, CIRCLE_NAME_FOR_EXAMPLE, EXAMPLE_SERVER_PORT);
log::info!(
"Starting {} for circle '{}' on port {}...",
CIRCLE_SERVER_WS_BIN_NAME,
CIRCLE_NAME_FOR_EXAMPLE,
EXAMPLE_SERVER_PORT
);
let server_process = Command::new(&server_bin_path)
.args([
"--port", &EXAMPLE_SERVER_PORT.to_string(),
"--circle-name", CIRCLE_NAME_FOR_EXAMPLE
"--port",
&EXAMPLE_SERVER_PORT.to_string(),
"--circle-name",
CIRCLE_NAME_FOR_EXAMPLE,
])
.stdout(Stdio::piped()) // Pipe stdout to keep terminal clean, or Stdio::inherit() to see server logs
.stderr(Stdio::piped()) // Pipe stderr as well
.spawn()
.map_err(|e| format!("Failed to start {}: {}. Ensure it is built.", CIRCLE_SERVER_WS_BIN_NAME, e))?;
let _server_guard = ChildProcessGuard::new(server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
.map_err(|e| {
format!(
"Failed to start {}: {}. Ensure it is built.",
CIRCLE_SERVER_WS_BIN_NAME, e
)
})?;
let _server_guard =
ChildProcessGuard::new(server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
log::info!("Giving the server a moment to start up...");
sleep(Duration::from_secs(3)).await; // Wait for server to initialize
@@ -99,13 +142,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// This part should not be reached if timeout works correctly.
print(x);
x
".to_string();
"
.to_string();
log::info!("Sending long-running script (expected to time out on server after ~{}s)...", SCRIPT_TIMEOUT_SECONDS);
log::info!(
"Sending long-running script (expected to time out on server after ~{}s)...",
SCRIPT_TIMEOUT_SECONDS
);
match client.play(long_running_script).await {
Ok(play_result) => {
log::warn!("Received unexpected success from play request: {:?}", play_result);
log::warn!(
"Received unexpected success from play request: {:?}",
play_result
);
log::warn!("This might indicate the script finished faster than expected, or the timeout didn't trigger.");
}
Err(e) => {
@@ -116,7 +166,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
if e.to_string().contains("timed out") || e.to_string().contains("-32002") {
log::info!("Successfully received timeout error from the server!");
} else {
log::warn!("Received an error, but it might not be the expected timeout error: {}", e);
log::warn!(
"Received an error, but it might not be the expected timeout error: {}",
e
);
}
}
}
@@ -127,4 +180,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
log::info!("Timeout demonstration example finished.");
Ok(())
}
}