Compare commits
No commits in common. "08c87709423614d12acc66cd3e54ba5c50a92981" and "0d90c180f84e0bc2b5ef12963de2b45a83c00931" have entirely different histories.
08c8770942
...
0d90c180f8
39
.gitignore
vendored
39
.gitignore
vendored
@ -23,42 +23,3 @@ Cargo.lock
|
|||||||
/rhai_test_download
|
/rhai_test_download
|
||||||
/rhai_test_fs
|
/rhai_test_fs
|
||||||
run_rhai_tests.log
|
run_rhai_tests.log
|
||||||
new_location
|
|
||||||
log.txt
|
|
||||||
file.txt
|
|
||||||
fix_doc*
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# Production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.docusaurus
|
|
||||||
.cache-loader
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
bun.lockb
|
|
||||||
bun.lock
|
|
||||||
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
build.sh
|
|
||||||
build_dev.sh
|
|
||||||
develop.sh
|
|
||||||
|
|
||||||
docusaurus.config.ts
|
|
||||||
|
|
||||||
sidebars.ts
|
|
||||||
|
|
||||||
tsconfig.json
|
|
||||||
|
75
Cargo.toml
75
Cargo.toml
@ -11,38 +11,67 @@ categories = ["os", "filesystem", "api-bindings"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
tera = "1.19.0" # Template engine for text rendering
|
||||||
base64 = "0.21.0" # Base64 encoding/decoding
|
# Cross-platform functionality
|
||||||
cfg-if = "1.0"
|
|
||||||
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
|
|
||||||
clap = "2.33" # Command-line argument parsing
|
|
||||||
dirs = "5.0.1" # Directory paths
|
|
||||||
env_logger = "0.10.0" # Logger implementation
|
|
||||||
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
|
|
||||||
glob = "0.3.1" # For file pattern matching
|
|
||||||
jsonrpsee = "0.25.1"
|
|
||||||
k256 = { version = "0.13.1", features = ["ecdsa", "ecdh"] } # Elliptic curve cryptography
|
|
||||||
lazy_static = "1.4.0" # For lazy initialization of static variables
|
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4" # Logging facade
|
cfg-if = "1.0"
|
||||||
once_cell = "1.18.0" # Lazy static initialization
|
|
||||||
postgres = "0.19.4" # PostgreSQL client
|
|
||||||
postgres-types = "0.2.5" # PostgreSQL type conversions
|
thiserror = "1.0" # For error handling
|
||||||
r2d2 = "0.8.10"
|
|
||||||
r2d2_postgres = "0.18.2"
|
|
||||||
rand = "0.8.5" # Random number generation
|
|
||||||
redis = "0.22.0" # Redis client
|
redis = "0.22.0" # Redis client
|
||||||
|
postgres = "0.19.4" # PostgreSQL client
|
||||||
|
tokio-postgres = "0.7.8" # Async PostgreSQL client
|
||||||
|
postgres-types = "0.2.5" # PostgreSQL type conversions
|
||||||
|
lazy_static = "1.4.0" # For lazy initialization of static variables
|
||||||
regex = "1.8.1" # For regex pattern matching
|
regex = "1.8.1" # For regex pattern matching
|
||||||
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
|
||||||
serde = { version = "1.0", features = [
|
serde = { version = "1.0", features = [
|
||||||
"derive",
|
"derive",
|
||||||
] } # For serialization/deserialization
|
] } # For serialization/deserialization
|
||||||
serde_json = "1.0" # For JSON handling
|
serde_json = "1.0" # For JSON handling
|
||||||
sha2 = "0.10.7" # SHA-2 hash functions
|
glob = "0.3.1" # For file pattern matching
|
||||||
tempfile = "3.5" # For temporary file operations
|
tempfile = "3.5" # For temporary file operations
|
||||||
|
log = "0.4" # Logging facade
|
||||||
|
env_logger = "0.10.0" # Logger implementation
|
||||||
|
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
||||||
|
rand = "0.8.5" # Random number generation
|
||||||
|
clap = "2.33" # Command-line argument parsing
|
||||||
|
r2d2 = "0.8.10"
|
||||||
|
r2d2_postgres = "0.18.2"
|
||||||
|
slatedb = "0.6.1" # Embedded key-value store
|
||||||
|
object_store = "0.5.0" # Object store implementations used by SlateDB
|
||||||
|
bytes = "1.4.0" # Used for byte operations
|
||||||
|
|
||||||
|
# Crypto dependencies
|
||||||
|
base64 = "0.21.0" # Base64 encoding/decoding
|
||||||
|
k256 = { version = "0.13.1", features = ["ecdsa"] } # Elliptic curve cryptography
|
||||||
|
once_cell = "1.18.0" # Lazy static initialization
|
||||||
|
sha2 = "0.10.7" # SHA-2 hash functions
|
||||||
|
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
|
||||||
|
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
|
||||||
|
dirs = "5.0.1" # Directory paths
|
||||||
tokio = { version = "1.28", features = ["full"] }
|
tokio = { version = "1.28", features = ["full"] }
|
||||||
tokio-test = "0.4.4"
|
|
||||||
uuid = { version = "1.16.0", features = ["v4"] }
|
uuid = { version = "1.16.0", features = ["v4"] }
|
||||||
|
tokio-test = "0.4.4"
|
||||||
|
|
||||||
|
# WebAssembly dependencies
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
wasm-bindgen = "0.2.87"
|
||||||
|
js-sys = "0.3.64"
|
||||||
|
wasm-bindgen-futures = "0.4.39"
|
||||||
|
web-sys = { version = "0.3.64", features = [
|
||||||
|
"Window",
|
||||||
|
"Storage",
|
||||||
|
"IdbDatabase",
|
||||||
|
"IdbOpenDbRequest",
|
||||||
|
"IdbFactory",
|
||||||
|
"IdbTransaction",
|
||||||
|
"IdbObjectStore",
|
||||||
|
"IdbRequest",
|
||||||
|
"IdbKeyRange",
|
||||||
|
"IdbCursorWithValue",
|
||||||
|
"Event",
|
||||||
|
"console"
|
||||||
|
] }
|
||||||
|
|
||||||
# Optional features for specific OS functionality
|
# Optional features for specific OS functionality
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
@ -56,9 +85,9 @@ windows = { version = "0.48", features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mockall = "0.11.4" # For mocking in tests
|
|
||||||
tempfile = "3.5" # For tests that need temporary files/directories
|
tempfile = "3.5" # For tests that need temporary files/directories
|
||||||
tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing
|
tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing
|
||||||
|
mockall = "0.11.4" # For mocking in tests
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "herodo"
|
name = "herodo"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Demonstrates file system operations using SAL
|
// Demonstrates file system operations using SAL
|
||||||
|
|
||||||
// Create a test directory
|
// Create a test directory
|
||||||
let test_dir = "/tmp/rhai_test_dir";
|
let test_dir = "rhai_test_dir";
|
||||||
println(`Creating directory: ${test_dir}`);
|
println(`Creating directory: ${test_dir}`);
|
||||||
let mkdir_result = mkdir(test_dir);
|
let mkdir_result = mkdir(test_dir);
|
||||||
println(`Directory creation result: ${mkdir_result}`);
|
println(`Directory creation result: ${mkdir_result}`);
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
// Basic example of using the Zinit client in Rhai
|
|
||||||
|
|
||||||
// Socket path for Zinit
|
|
||||||
let socket_path = "/var/run/zinit.sock";
|
|
||||||
|
|
||||||
// List all services
|
|
||||||
print("Listing all services:");
|
|
||||||
let services = zinit_list(socket_path);
|
|
||||||
|
|
||||||
if services.is_empty() {
|
|
||||||
print("No services found.");
|
|
||||||
} else {
|
|
||||||
// Iterate over the keys of the map
|
|
||||||
for name in services.keys() {
|
|
||||||
let state = services[name];
|
|
||||||
print(`${name}: ${state}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get status of a specific service
|
|
||||||
let service_name = "test";
|
|
||||||
print(`Getting status for ${service_name}:`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let status = zinit_status(socket_path, service_name);
|
|
||||||
print(`Service: ${status.name}`);
|
|
||||||
print(`PID: ${status.pid}`);
|
|
||||||
print(`State: ${status.state}`);
|
|
||||||
print(`Target: ${status.target}`);
|
|
||||||
print("Dependencies:");
|
|
||||||
|
|
||||||
for (dep, state) in status.after.keys() {
|
|
||||||
print(` ${dep}: ${state}`);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
print(`Error getting status: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new service
|
|
||||||
print("\nCreating a new service:");
|
|
||||||
let new_service = "rhai-test-service";
|
|
||||||
let exec_command = "echo 'Hello from Rhai'";
|
|
||||||
let oneshot = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = zinit_create_service(socket_path, new_service, exec_command, oneshot);
|
|
||||||
print(`Service created: ${result}`);
|
|
||||||
|
|
||||||
// Monitor the service
|
|
||||||
print("\nMonitoring the service:");
|
|
||||||
let monitor_result = zinit_monitor(socket_path, new_service);
|
|
||||||
print(`Service monitored: ${monitor_result}`);
|
|
||||||
|
|
||||||
// Start the service
|
|
||||||
print("\nStarting the service:");
|
|
||||||
let start_result = zinit_start(socket_path, new_service);
|
|
||||||
print(`Service started: ${start_result}`);
|
|
||||||
|
|
||||||
// Get logs for a specific service
|
|
||||||
print("\nGetting logs:");
|
|
||||||
let logs = zinit_logs(socket_path, new_service);
|
|
||||||
|
|
||||||
for log in logs {
|
|
||||||
print(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or to get all logs (uncomment if needed)
|
|
||||||
// print("\nGetting all logs:");
|
|
||||||
// let all_logs = zinit_logs_all(socket_path);
|
|
||||||
//
|
|
||||||
// for log in all_logs {
|
|
||||||
// print(log);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
print("\nCleaning up:");
|
|
||||||
let stop_result = zinit_stop(socket_path, new_service);
|
|
||||||
print(`Service stopped: ${stop_result}`);
|
|
||||||
|
|
||||||
let forget_result = zinit_forget(socket_path, new_service);
|
|
||||||
print(`Service forgotten: ${forget_result}`);
|
|
||||||
|
|
||||||
let delete_result = zinit_delete_service(socket_path, new_service);
|
|
||||||
print(`Service deleted: ${delete_result}`);
|
|
||||||
} catch(err) {
|
|
||||||
print(`Error: ${err}`);
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
// 01_keypair_operations.rhai
|
|
||||||
// Tests for basic keypair operations in the Keypair module
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== Testing Basic Keypair Operations ===");
|
|
||||||
|
|
||||||
// Test creating a new keypair
|
|
||||||
print("Testing keypair creation...");
|
|
||||||
let keypair_name = "test_keypair";
|
|
||||||
if create_key_space("test_space", "password") {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
if create_keypair(keypair_name, "password") {
|
|
||||||
print("✓ Keypair created successfully");
|
|
||||||
|
|
||||||
// Test getting the public key
|
|
||||||
print("Testing public key retrieval...");
|
|
||||||
if select_keypair(keypair_name) {
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
print(`✓ Public key retrieved: ${pub_key.len()} bytes`);
|
|
||||||
|
|
||||||
// Test signing a message
|
|
||||||
print("Testing message signing...");
|
|
||||||
let message = "This is a test message to sign";
|
|
||||||
let signature = keypair_sign(message);
|
|
||||||
assert_true(signature.len() > 0, "Signature should not be empty");
|
|
||||||
print(`✓ Message signed successfully: ${signature.len()} bytes`);
|
|
||||||
|
|
||||||
// Test verifying a signature
|
|
||||||
print("Testing signature verification...");
|
|
||||||
let is_valid = keypair_verify(message, signature);
|
|
||||||
assert_true(is_valid, "Signature should be valid");
|
|
||||||
print("✓ Signature verified successfully");
|
|
||||||
|
|
||||||
// Test verifying with just a public key
|
|
||||||
print("Testing verification with public key only...");
|
|
||||||
let is_valid_pub = verify_with_public_key(pub_key, message, signature);
|
|
||||||
assert_true(is_valid_pub, "Signature should be valid with public key only");
|
|
||||||
print("✓ Signature verified with public key only");
|
|
||||||
|
|
||||||
// Edge case: Empty message
|
|
||||||
print("Testing with empty message...");
|
|
||||||
let empty_message = "";
|
|
||||||
let empty_signature = keypair_sign(empty_message);
|
|
||||||
assert_true(empty_signature.len() > 0, "Signature for empty message should not be empty");
|
|
||||||
let is_valid_empty = keypair_verify(empty_message, empty_signature);
|
|
||||||
assert_true(is_valid_empty, "Empty message signature should be valid");
|
|
||||||
print("✓ Empty message signed and verified successfully");
|
|
||||||
|
|
||||||
// Edge case: Large message
|
|
||||||
print("Testing with large message...");
|
|
||||||
let large_message = "A" * 10000; // 10KB message
|
|
||||||
let large_signature = keypair_sign(large_message);
|
|
||||||
assert_true(large_signature.len() > 0, "Signature for large message should not be empty");
|
|
||||||
let is_valid_large = keypair_verify(large_message, large_signature);
|
|
||||||
assert_true(is_valid_large, "Large message signature should be valid");
|
|
||||||
print("✓ Large message signed and verified successfully");
|
|
||||||
|
|
||||||
// Error case: Invalid signature format
|
|
||||||
print("Testing with invalid signature format...");
|
|
||||||
let invalid_signature = [0, 1, 2, 3]; // Invalid signature bytes
|
|
||||||
let is_valid_invalid = false;
|
|
||||||
try {
|
|
||||||
is_valid_invalid = keypair_verify(message, invalid_signature);
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for invalid signature: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!is_valid_invalid, "Invalid signature should not verify");
|
|
||||||
|
|
||||||
// Error case: Tampered message
|
|
||||||
print("Testing with tampered message...");
|
|
||||||
let tampered_message = message + " (tampered)";
|
|
||||||
let is_valid_tampered = keypair_verify(tampered_message, signature);
|
|
||||||
assert_true(!is_valid_tampered, "Tampered message should not verify");
|
|
||||||
print("✓ Tampered message correctly failed verification");
|
|
||||||
|
|
||||||
// Error case: Malformed public key
|
|
||||||
print("Testing with malformed public key...");
|
|
||||||
let malformed_pub_key = [0, 1, 2, 3]; // Invalid public key bytes
|
|
||||||
let is_valid_malformed = false;
|
|
||||||
try {
|
|
||||||
is_valid_malformed = verify_with_public_key(malformed_pub_key, message, signature);
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for malformed public key: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!is_valid_malformed, "Malformed public key should not verify");
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to select keypair");
|
|
||||||
throw "Failed to select keypair";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create keypair");
|
|
||||||
throw "Failed to create keypair";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create key space");
|
|
||||||
throw "Failed to create key space";
|
|
||||||
}
|
|
||||||
|
|
||||||
print("All keypair operations tests completed successfully!");
|
|
@ -1,162 +0,0 @@
|
|||||||
// 02_keyspace_operations.rhai
|
|
||||||
// Tests for key space operations in the Keypair module
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== Testing Key Space Operations ===");
|
|
||||||
|
|
||||||
// Test creating a new key space
|
|
||||||
print("Testing key space creation...");
|
|
||||||
let space_name = "test_keyspace";
|
|
||||||
let password = "secure_password";
|
|
||||||
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print(`✓ Key space "${space_name}" created successfully`);
|
|
||||||
|
|
||||||
// Test adding keypairs to a key space
|
|
||||||
print("Testing adding keypairs to key space...");
|
|
||||||
let keypair1_name = "keypair1";
|
|
||||||
let keypair2_name = "keypair2";
|
|
||||||
|
|
||||||
if create_keypair(keypair1_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair1_name}" created successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair "${keypair1_name}"`);
|
|
||||||
throw `Failed to create keypair "${keypair1_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_keypair(keypair2_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair2_name}" created successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair "${keypair2_name}"`);
|
|
||||||
throw `Failed to create keypair "${keypair2_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test listing keypairs in a key space
|
|
||||||
print("Testing listing keypairs in key space...");
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
assert_true(keypairs.len() == 2, `Expected 2 keypairs, got ${keypairs.len()}`);
|
|
||||||
assert_true(keypairs.contains(keypair1_name), `Keypair list should contain "${keypair1_name}"`);
|
|
||||||
assert_true(keypairs.contains(keypair2_name), `Keypair list should contain "${keypair2_name}"`);
|
|
||||||
print(`✓ Listed keypairs successfully: ${keypairs}`);
|
|
||||||
|
|
||||||
// Test getting a keypair by name
|
|
||||||
print("Testing getting a keypair by name...");
|
|
||||||
if select_keypair(keypair1_name) {
|
|
||||||
print(`✓ Selected keypair "${keypair1_name}" successfully`);
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
print(`✓ Retrieved public key for "${keypair1_name}": ${pub_key.len()} bytes`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select keypair "${keypair1_name}"`);
|
|
||||||
throw `Failed to select keypair "${keypair1_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge case: Attempt to add a keypair with a duplicate name
|
|
||||||
print("Testing adding a keypair with a duplicate name...");
|
|
||||||
let duplicate_success = false;
|
|
||||||
try {
|
|
||||||
duplicate_success = create_keypair(keypair1_name, password);
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for duplicate keypair: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!duplicate_success, "Creating a duplicate keypair should fail");
|
|
||||||
|
|
||||||
// Edge case: Attempt to get a non-existent keypair
|
|
||||||
print("Testing getting a non-existent keypair...");
|
|
||||||
let nonexistent_success = false;
|
|
||||||
try {
|
|
||||||
nonexistent_success = select_keypair("nonexistent_keypair");
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for non-existent keypair: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!nonexistent_success, "Selecting a non-existent keypair should fail");
|
|
||||||
|
|
||||||
// Edge case: Test with special characters in keypair names
|
|
||||||
print("Testing with special characters in keypair name...");
|
|
||||||
let special_name = "special!@#$%^&*()_+";
|
|
||||||
if create_keypair(special_name, password) {
|
|
||||||
print(`✓ Created keypair with special characters: "${special_name}"`);
|
|
||||||
|
|
||||||
// Verify we can select and use it
|
|
||||||
if select_keypair(special_name) {
|
|
||||||
print(`✓ Selected keypair with special characters`);
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select keypair with special characters`);
|
|
||||||
throw `Failed to select keypair with special characters`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair with special characters`);
|
|
||||||
throw `Failed to create keypair with special characters`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge case: Test with very long keypair name
|
|
||||||
print("Testing with very long keypair name...");
|
|
||||||
let long_name = "a" * 100; // 100 character name
|
|
||||||
if create_keypair(long_name, password) {
|
|
||||||
print(`✓ Created keypair with long name (${long_name.len()} characters)`);
|
|
||||||
|
|
||||||
// Verify we can select and use it
|
|
||||||
if select_keypair(long_name) {
|
|
||||||
print(`✓ Selected keypair with long name`);
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select keypair with long name`);
|
|
||||||
throw `Failed to select keypair with long name`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair with long name`);
|
|
||||||
throw `Failed to create keypair with long name`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge case: Test with empty keypair name (should fail)
|
|
||||||
print("Testing with empty keypair name...");
|
|
||||||
let empty_name = "";
|
|
||||||
let empty_name_success = false;
|
|
||||||
try {
|
|
||||||
empty_name_success = create_keypair(empty_name, password);
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for empty keypair name: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!empty_name_success, "Creating a keypair with empty name should fail");
|
|
||||||
|
|
||||||
// Stress test: Add multiple keypairs
|
|
||||||
print("Stress testing: Adding multiple keypairs...");
|
|
||||||
let num_keypairs = 10; // Add 10 more keypairs
|
|
||||||
let stress_keypairs = [];
|
|
||||||
|
|
||||||
for i in 0..num_keypairs {
|
|
||||||
let name = `stress_keypair_${i}`;
|
|
||||||
stress_keypairs.push(name);
|
|
||||||
|
|
||||||
if create_keypair(name, password) {
|
|
||||||
print(`✓ Created stress test keypair ${i+1}/${num_keypairs}`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create stress test keypair ${i+1}/${num_keypairs}`);
|
|
||||||
throw `Failed to create stress test keypair ${i+1}/${num_keypairs}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify all keypairs were created
|
|
||||||
print("Verifying all stress test keypairs...");
|
|
||||||
let all_keypairs = list_keypairs();
|
|
||||||
for name in stress_keypairs {
|
|
||||||
assert_true(all_keypairs.contains(name), `Keypair list should contain "${name}"`);
|
|
||||||
}
|
|
||||||
print(`✓ All ${num_keypairs} stress test keypairs verified`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create key space "${space_name}"`);
|
|
||||||
throw `Failed to create key space "${space_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("All key space operations tests completed successfully!");
|
|
@ -1,167 +0,0 @@
|
|||||||
// 03_session_management.rhai
|
|
||||||
// Tests for session management in the Keypair module
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== Testing Session Management ===");
|
|
||||||
|
|
||||||
// Test creating a key space and setting it as current
|
|
||||||
print("Testing key space creation and activation...");
|
|
||||||
let space_name1 = "session_test_space1";
|
|
||||||
let space_name2 = "session_test_space2";
|
|
||||||
let password = "secure_password";
|
|
||||||
|
|
||||||
// Create first key space
|
|
||||||
if create_key_space(space_name1, password) {
|
|
||||||
print(`✓ Key space "${space_name1}" created successfully`);
|
|
||||||
|
|
||||||
// Test creating keypairs in the current space
|
|
||||||
print("Testing creating keypairs in current space...");
|
|
||||||
let keypair1_name = "session_keypair1";
|
|
||||||
|
|
||||||
if create_keypair(keypair1_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair1_name}" created successfully in space "${space_name1}"`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair "${keypair1_name}" in space "${space_name1}"`);
|
|
||||||
throw `Failed to create keypair "${keypair1_name}" in space "${space_name1}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test selecting a keypair
|
|
||||||
print("Testing selecting a keypair...");
|
|
||||||
if select_keypair(keypair1_name) {
|
|
||||||
print(`✓ Selected keypair "${keypair1_name}" successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select keypair "${keypair1_name}"`);
|
|
||||||
throw `Failed to select keypair "${keypair1_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getting the selected keypair
|
|
||||||
print("Testing getting the selected keypair...");
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
print(`✓ Retrieved public key for selected keypair: ${pub_key.len()} bytes`);
|
|
||||||
|
|
||||||
// Create second key space
|
|
||||||
print("\nTesting creating and switching to a second key space...");
|
|
||||||
if create_key_space(space_name2, password) {
|
|
||||||
print(`✓ Key space "${space_name2}" created successfully`);
|
|
||||||
|
|
||||||
// Verify we're now in the second space
|
|
||||||
print("Verifying current space changed...");
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
assert_true(keypairs.len() == 0, `Expected 0 keypairs in new space, got ${keypairs.len()}`);
|
|
||||||
print("✓ Current space verified as the new space (empty keypair list)");
|
|
||||||
|
|
||||||
// Create a keypair in the second space
|
|
||||||
let keypair2_name = "session_keypair2";
|
|
||||||
if create_keypair(keypair2_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair2_name}" created successfully in space "${space_name2}"`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create keypair "${keypair2_name}" in space "${space_name2}"`);
|
|
||||||
throw `Failed to create keypair "${keypair2_name}" in space "${space_name2}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch back to first space
|
|
||||||
print("\nTesting switching back to first key space...");
|
|
||||||
if load_key_space(space_name1, password) {
|
|
||||||
print(`✓ Switched back to key space "${space_name1}" successfully`);
|
|
||||||
|
|
||||||
// Verify we're now in the first space
|
|
||||||
print("Verifying current space changed back...");
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
assert_true(keypairs.len() == 1, `Expected 1 keypair in original space, got ${keypairs.len()}`);
|
|
||||||
assert_true(keypairs.contains(keypair1_name), `Keypair list should contain "${keypair1_name}"`);
|
|
||||||
print("✓ Current space verified as the original space");
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch back to key space "${space_name1}"`);
|
|
||||||
throw `Failed to switch back to key space "${space_name1}"`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create second key space "${space_name2}"`);
|
|
||||||
throw `Failed to create second key space "${space_name2}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test clearing the session
|
|
||||||
print("\nTesting clearing the session...");
|
|
||||||
clear_session();
|
|
||||||
print("✓ Session cleared");
|
|
||||||
|
|
||||||
// Verify operations fail after clearing session
|
|
||||||
print("Verifying operations fail after clearing session...");
|
|
||||||
let list_success = false;
|
|
||||||
try {
|
|
||||||
list_keypairs();
|
|
||||||
list_success = true;
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error after clearing session: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!list_success, "Listing keypairs should fail after clearing session");
|
|
||||||
|
|
||||||
// Error case: Attempt operations without an active key space
|
|
||||||
print("\nTesting operations without an active key space...");
|
|
||||||
|
|
||||||
// Attempt to create a keypair
|
|
||||||
let create_success = false;
|
|
||||||
try {
|
|
||||||
create_success = create_keypair("no_space_keypair", password);
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for creating keypair without active space: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!create_success, "Creating a keypair without active space should fail");
|
|
||||||
|
|
||||||
// Attempt to select a keypair
|
|
||||||
let select_success = false;
|
|
||||||
try {
|
|
||||||
select_success = select_keypair("no_space_keypair");
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for selecting keypair without active space: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!select_success, "Selecting a keypair without active space should fail");
|
|
||||||
|
|
||||||
// Reload a key space
|
|
||||||
print("\nTesting reloading a key space after clearing session...");
|
|
||||||
if load_key_space(space_name1, password) {
|
|
||||||
print(`✓ Reloaded key space "${space_name1}" successfully`);
|
|
||||||
|
|
||||||
// Verify the keypair is still there
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
assert_true(keypairs.contains(keypair1_name), `Keypair list should contain "${keypair1_name}"`);
|
|
||||||
print("✓ Keypair still exists in reloaded space");
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to reload key space "${space_name1}"`);
|
|
||||||
throw `Failed to reload key space "${space_name1}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case: Attempt to get selected keypair when none is selected
|
|
||||||
print("\nTesting getting selected keypair when none is selected...");
|
|
||||||
let get_selected_success = false;
|
|
||||||
try {
|
|
||||||
keypair_pub_key();
|
|
||||||
get_selected_success = true;
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for getting selected keypair when none selected: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!get_selected_success, "Getting selected keypair when none is selected should fail");
|
|
||||||
|
|
||||||
// Error case: Attempt to select non-existent keypair
|
|
||||||
print("\nTesting selecting a non-existent keypair...");
|
|
||||||
let select_nonexistent_success = false;
|
|
||||||
try {
|
|
||||||
select_nonexistent_success = select_keypair("nonexistent_keypair");
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for selecting non-existent keypair: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!select_nonexistent_success, "Selecting a non-existent keypair should fail");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create key space "${space_name1}"`);
|
|
||||||
throw `Failed to create key space "${space_name1}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("All session management tests completed successfully!");
|
|
@ -1,192 +0,0 @@
|
|||||||
// 04_encryption_decryption.rhai
|
|
||||||
// Tests for asymmetric encryption and decryption in the Keypair module
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== Testing Asymmetric Encryption and Decryption ===");
|
|
||||||
|
|
||||||
// Test creating keypairs for sender and recipient
|
|
||||||
print("Setting up sender and recipient keypairs...");
|
|
||||||
let space_name = "encryption_test_space";
|
|
||||||
let password = "secure_password";
|
|
||||||
let sender_name = "sender_keypair";
|
|
||||||
let recipient_name = "recipient_keypair";
|
|
||||||
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print(`✓ Key space "${space_name}" created successfully`);
|
|
||||||
|
|
||||||
// Create sender keypair
|
|
||||||
if create_keypair(sender_name, password) {
|
|
||||||
print(`✓ Sender keypair "${sender_name}" created successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create sender keypair "${sender_name}"`);
|
|
||||||
throw `Failed to create sender keypair "${sender_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create recipient keypair
|
|
||||||
if create_keypair(recipient_name, password) {
|
|
||||||
print(`✓ Recipient keypair "${recipient_name}" created successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create recipient keypair "${recipient_name}"`);
|
|
||||||
throw `Failed to create recipient keypair "${recipient_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get recipient's public key
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Selected recipient keypair "${recipient_name}" successfully`);
|
|
||||||
let recipient_pub_key = keypair_pub_key();
|
|
||||||
assert_true(recipient_pub_key.len() > 0, "Recipient public key should not be empty");
|
|
||||||
print(`✓ Retrieved recipient public key: ${recipient_pub_key.len()} bytes`);
|
|
||||||
|
|
||||||
// Switch to sender keypair
|
|
||||||
if select_keypair(sender_name) {
|
|
||||||
print(`✓ Selected sender keypair "${sender_name}" successfully`);
|
|
||||||
|
|
||||||
// Test encrypting a message with recipient's public key
|
|
||||||
print("\nTesting encrypting a message...");
|
|
||||||
let message = "This is a secret message for the recipient";
|
|
||||||
let ciphertext = encrypt_asymmetric(recipient_pub_key, message);
|
|
||||||
assert_true(ciphertext.len() > 0, "Ciphertext should not be empty");
|
|
||||||
print(`✓ Message encrypted successfully: ${ciphertext.len()} bytes`);
|
|
||||||
|
|
||||||
// Switch back to recipient keypair to decrypt
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Switched back to recipient keypair "${recipient_name}" successfully`);
|
|
||||||
|
|
||||||
// Test decrypting the message
|
|
||||||
print("Testing decrypting the message...");
|
|
||||||
let decrypted = decrypt_asymmetric(ciphertext);
|
|
||||||
assert_true(decrypted == message, "Decrypted message should match original");
|
|
||||||
print(`✓ Message decrypted successfully: "${decrypted}"`);
|
|
||||||
|
|
||||||
// Edge case: Test with empty message
|
|
||||||
print("\nTesting with empty message...");
|
|
||||||
let empty_message = "";
|
|
||||||
let empty_ciphertext = encrypt_asymmetric(recipient_pub_key, empty_message);
|
|
||||||
assert_true(empty_ciphertext.len() > 0, "Ciphertext for empty message should not be empty");
|
|
||||||
|
|
||||||
let empty_decrypted = decrypt_asymmetric(empty_ciphertext);
|
|
||||||
assert_true(empty_decrypted == empty_message, "Decrypted empty message should be empty");
|
|
||||||
print("✓ Empty message encrypted and decrypted successfully");
|
|
||||||
|
|
||||||
// Edge case: Test with large message
|
|
||||||
print("\nTesting with large message...");
|
|
||||||
let large_message = "A" * 10000; // 10KB message
|
|
||||||
let large_ciphertext = encrypt_asymmetric(recipient_pub_key, large_message);
|
|
||||||
assert_true(large_ciphertext.len() > 0, "Ciphertext for large message should not be empty");
|
|
||||||
|
|
||||||
let large_decrypted = decrypt_asymmetric(large_ciphertext);
|
|
||||||
assert_true(large_decrypted == large_message, "Decrypted large message should match original");
|
|
||||||
print("✓ Large message encrypted and decrypted successfully");
|
|
||||||
|
|
||||||
// Error case: Attempt to decrypt with the wrong keypair
|
|
||||||
print("\nTesting decryption with wrong keypair...");
|
|
||||||
if select_keypair(sender_name) {
|
|
||||||
print(`✓ Switched to sender keypair "${sender_name}" successfully`);
|
|
||||||
|
|
||||||
let wrong_keypair_success = true;
|
|
||||||
try {
|
|
||||||
let wrong_decrypted = decrypt_asymmetric(ciphertext);
|
|
||||||
// If we get here, the decryption didn't fail as expected
|
|
||||||
assert_true(wrong_decrypted != message, "Decryption with wrong keypair should not match original message");
|
|
||||||
} catch(err) {
|
|
||||||
wrong_keypair_success = false;
|
|
||||||
print(`✓ Caught expected error for decryption with wrong keypair: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Some implementations might not throw an error but return garbage data
|
|
||||||
// So we don't assert on wrong_keypair_success
|
|
||||||
|
|
||||||
// Switch back to recipient for further tests
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Switched back to recipient keypair "${recipient_name}" successfully`);
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch back to recipient keypair "${recipient_name}"`);
|
|
||||||
throw `Failed to switch back to recipient keypair "${recipient_name}"`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch to sender keypair "${sender_name}"`);
|
|
||||||
throw `Failed to switch to sender keypair "${sender_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case: Test with malformed ciphertext
|
|
||||||
print("\nTesting with malformed ciphertext...");
|
|
||||||
let malformed_ciphertext = [0, 1, 2, 3]; // Invalid ciphertext bytes
|
|
||||||
let malformed_success = false;
|
|
||||||
try {
|
|
||||||
decrypt_asymmetric(malformed_ciphertext);
|
|
||||||
malformed_success = true;
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for malformed ciphertext: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!malformed_success, "Decrypting malformed ciphertext should fail");
|
|
||||||
|
|
||||||
// Error case: Test with invalid public key for encryption
|
|
||||||
print("\nTesting encryption with invalid public key...");
|
|
||||||
if select_keypair(sender_name) {
|
|
||||||
print(`✓ Switched to sender keypair "${sender_name}" successfully`);
|
|
||||||
|
|
||||||
let invalid_pub_key = [0, 1, 2, 3]; // Invalid public key bytes
|
|
||||||
let invalid_key_success = false;
|
|
||||||
try {
|
|
||||||
encrypt_asymmetric(invalid_pub_key, message);
|
|
||||||
invalid_key_success = true;
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for invalid public key: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!invalid_key_success, "Encrypting with invalid public key should fail");
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch to sender keypair "${sender_name}"`);
|
|
||||||
throw `Failed to switch to sender keypair "${sender_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case: Test with tampered ciphertext
|
|
||||||
print("\nTesting with tampered ciphertext...");
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Switched to recipient keypair "${recipient_name}" successfully`);
|
|
||||||
|
|
||||||
// Tamper with the ciphertext (change a byte in the middle)
|
|
||||||
let tampered_ciphertext = ciphertext.clone();
|
|
||||||
if tampered_ciphertext.len() > 100 {
|
|
||||||
tampered_ciphertext[100] = (tampered_ciphertext[100] + 1) % 256;
|
|
||||||
|
|
||||||
let tampered_success = false;
|
|
||||||
try {
|
|
||||||
let tampered_decrypted = decrypt_asymmetric(tampered_ciphertext);
|
|
||||||
tampered_success = tampered_decrypted == message;
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for tampered ciphertext: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(!tampered_success, "Decrypting tampered ciphertext should fail or produce incorrect result");
|
|
||||||
} else {
|
|
||||||
print("Note: Ciphertext too short to test tampering");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch to recipient keypair "${recipient_name}"`);
|
|
||||||
throw `Failed to switch to recipient keypair "${recipient_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to switch back to recipient keypair "${recipient_name}"`);
|
|
||||||
throw `Failed to switch back to recipient keypair "${recipient_name}"`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select sender keypair "${sender_name}"`);
|
|
||||||
throw `Failed to select sender keypair "${sender_name}"`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to select recipient keypair "${recipient_name}"`);
|
|
||||||
throw `Failed to select recipient keypair "${recipient_name}"`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print(`✗ Failed to create key space "${space_name}"`);
|
|
||||||
throw `Failed to create key space "${space_name}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("All asymmetric encryption and decryption tests completed successfully!");
|
|
@ -1,231 +0,0 @@
|
|||||||
// 05_error_handling.rhai
|
|
||||||
// Comprehensive error handling tests for the Keypair module
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test for expected errors
|
|
||||||
fn expect_error(fn_to_test, expected_error_substring) {
|
|
||||||
let error_caught = false;
|
|
||||||
let error_message = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
fn_to_test();
|
|
||||||
} catch(err) {
|
|
||||||
error_caught = true;
|
|
||||||
error_message = err.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !error_caught {
|
|
||||||
print(`ASSERTION FAILED: Expected error containing "${expected_error_substring}" but no error was thrown`);
|
|
||||||
throw `Expected error containing "${expected_error_substring}" but no error was thrown`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !error_message.contains(expected_error_substring) {
|
|
||||||
print(`ASSERTION FAILED: Expected error containing "${expected_error_substring}" but got "${error_message}"`);
|
|
||||||
throw `Expected error containing "${expected_error_substring}" but got "${error_message}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
print(`✓ Caught expected error: ${error_message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== Testing Error Handling ===");
|
|
||||||
|
|
||||||
// Test all error types defined in CryptoError
|
|
||||||
|
|
||||||
// 1. Test InvalidKeyLength error
|
|
||||||
print("\n--- Testing InvalidKeyLength error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
select_keypair("test_keypair");
|
|
||||||
|
|
||||||
// Try to verify with an invalid public key (wrong length)
|
|
||||||
verify_with_public_key([1, 2, 3], "test message", [1, 2, 3, 4]);
|
|
||||||
}, "InvalidKeyLength");
|
|
||||||
|
|
||||||
// 2. Test EncryptionFailed error
|
|
||||||
print("\n--- Testing EncryptionFailed error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
select_keypair("test_keypair");
|
|
||||||
|
|
||||||
// Try to encrypt with an invalid public key
|
|
||||||
encrypt_asymmetric([1, 2, 3], "test message");
|
|
||||||
}, "EncryptionFailed");
|
|
||||||
|
|
||||||
// 3. Test DecryptionFailed error
|
|
||||||
print("\n--- Testing DecryptionFailed error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
select_keypair("test_keypair");
|
|
||||||
|
|
||||||
// Try to decrypt invalid ciphertext
|
|
||||||
decrypt_asymmetric([1, 2, 3, 4]);
|
|
||||||
}, "DecryptionFailed");
|
|
||||||
|
|
||||||
// 4. Test SignatureFormatError error
|
|
||||||
print("\n--- Testing SignatureFormatError error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
select_keypair("test_keypair");
|
|
||||||
|
|
||||||
// Try to verify with an invalid signature format
|
|
||||||
keypair_verify("test message", [1, 2, 3]);
|
|
||||||
}, "SignatureFormatError");
|
|
||||||
|
|
||||||
// 5. Test KeypairAlreadyExists error
|
|
||||||
print("\n--- Testing KeypairAlreadyExists error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
create_keypair("duplicate_keypair", "password");
|
|
||||||
|
|
||||||
// Try to create a keypair with the same name
|
|
||||||
create_keypair("duplicate_keypair", "password");
|
|
||||||
}, "KeypairAlreadyExists");
|
|
||||||
|
|
||||||
// 6. Test KeypairNotFound error
|
|
||||||
print("\n--- Testing KeypairNotFound error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
|
|
||||||
// Try to select a non-existent keypair
|
|
||||||
select_keypair("nonexistent_keypair");
|
|
||||||
}, "KeypairNotFound");
|
|
||||||
|
|
||||||
// 7. Test NoActiveSpace error
|
|
||||||
print("\n--- Testing NoActiveSpace error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Clear the session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Try to create a keypair without an active space
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
}, "NoActiveSpace");
|
|
||||||
|
|
||||||
// 8. Test NoKeypairSelected error
|
|
||||||
print("\n--- Testing NoKeypairSelected error ---");
|
|
||||||
expect_error(|| {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
|
|
||||||
// Try to get the public key without selecting a keypair
|
|
||||||
keypair_pub_key();
|
|
||||||
}, "NoKeypairSelected");
|
|
||||||
|
|
||||||
// Test error propagation through the API
|
|
||||||
print("\n--- Testing error propagation ---");
|
|
||||||
let propagation_test = || {
|
|
||||||
// Create a key space for testing
|
|
||||||
create_key_space("error_test_space", "password");
|
|
||||||
|
|
||||||
// Create a keypair
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
|
|
||||||
// Clear the session to force an error
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// This should fail with NoActiveSpace
|
|
||||||
select_keypair("test_keypair");
|
|
||||||
|
|
||||||
// This line should never be reached
|
|
||||||
print("ERROR: Code execution continued after error");
|
|
||||||
};
|
|
||||||
|
|
||||||
expect_error(propagation_test, "NoActiveSpace");
|
|
||||||
|
|
||||||
// Test recovery from errors
|
|
||||||
print("\n--- Testing recovery from errors ---");
|
|
||||||
let recovery_success = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try an operation that will fail
|
|
||||||
clear_session();
|
|
||||||
list_keypairs(); // This should fail with NoActiveSpace
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error: ${err}`);
|
|
||||||
|
|
||||||
// Now recover by creating a new key space
|
|
||||||
if create_key_space("recovery_space", "password") {
|
|
||||||
// Create a keypair to verify recovery
|
|
||||||
if create_keypair("recovery_keypair", "password") {
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
if keypairs.contains("recovery_keypair") {
|
|
||||||
recovery_success = true;
|
|
||||||
print("✓ Successfully recovered from error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_true(recovery_success, "Should be able to recover from errors");
|
|
||||||
|
|
||||||
// Test behavior when multiple errors occur in sequence
|
|
||||||
print("\n--- Testing sequential errors ---");
|
|
||||||
let sequential_errors_count = 0;
|
|
||||||
|
|
||||||
// First error: No active space
|
|
||||||
try {
|
|
||||||
clear_session();
|
|
||||||
list_keypairs();
|
|
||||||
} catch(err) {
|
|
||||||
sequential_errors_count += 1;
|
|
||||||
print(`✓ Caught first sequential error: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second error: Keypair not found
|
|
||||||
try {
|
|
||||||
create_key_space("sequential_space", "password");
|
|
||||||
select_keypair("nonexistent_keypair");
|
|
||||||
} catch(err) {
|
|
||||||
sequential_errors_count += 1;
|
|
||||||
print(`✓ Caught second sequential error: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Third error: Keypair already exists
|
|
||||||
try {
|
|
||||||
create_keypair("sequential_keypair", "password");
|
|
||||||
create_keypair("sequential_keypair", "password");
|
|
||||||
} catch(err) {
|
|
||||||
sequential_errors_count += 1;
|
|
||||||
print(`✓ Caught third sequential error: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_true(sequential_errors_count == 3, `Expected 3 sequential errors, got ${sequential_errors_count}`);
|
|
||||||
|
|
||||||
// Test error handling with invalid parameters
|
|
||||||
print("\n--- Testing error handling with invalid parameters ---");
|
|
||||||
|
|
||||||
// Test with null/undefined parameters
|
|
||||||
try {
|
|
||||||
// Note: In Rhai, we can't directly pass null/undefined, but we can test with empty arrays
|
|
||||||
verify_with_public_key([], "message", []);
|
|
||||||
print("ERROR: verify_with_public_key with empty arrays didn't throw an error");
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for invalid parameters: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with wrong parameter types
|
|
||||||
try {
|
|
||||||
// Note: In Rhai, we can't easily pass wrong types, but we can test with strings instead of arrays
|
|
||||||
verify_with_public_key("not an array", "message", "not an array");
|
|
||||||
print("ERROR: verify_with_public_key with wrong types didn't throw an error");
|
|
||||||
} catch(err) {
|
|
||||||
print(`✓ Caught expected error for wrong parameter types: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("All error handling tests completed successfully!");
|
|
@ -1,293 +0,0 @@
|
|||||||
// run_all_tests.rhai
|
|
||||||
// Runs all Keypair module tests
|
|
||||||
|
|
||||||
print("=== Running Keypair Module Tests ===");
|
|
||||||
|
|
||||||
// Custom assert function
|
|
||||||
fn assert_true(condition, message) {
|
|
||||||
if !condition {
|
|
||||||
print(`ASSERTION FAILED: ${message}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run each test directly
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
let test_results = #{};
|
|
||||||
|
|
||||||
// Test 1: Keypair Operations
|
|
||||||
print("\n--- Running Keypair Operations Tests ---");
|
|
||||||
try {
|
|
||||||
// Clear any existing session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Test creating a new keypair
|
|
||||||
print("Testing keypair creation...");
|
|
||||||
let keypair_name = "test_keypair";
|
|
||||||
if create_key_space("test_space", "password") {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
if create_keypair(keypair_name, "password") {
|
|
||||||
print("✓ Keypair created successfully");
|
|
||||||
|
|
||||||
// Test getting the public key
|
|
||||||
print("Testing public key retrieval...");
|
|
||||||
if select_keypair(keypair_name) {
|
|
||||||
let pub_key = keypair_pub_key();
|
|
||||||
assert_true(pub_key.len() > 0, "Public key should not be empty");
|
|
||||||
print(`✓ Public key retrieved: ${pub_key.len()} bytes`);
|
|
||||||
|
|
||||||
// Test signing a message
|
|
||||||
print("Testing message signing...");
|
|
||||||
let message = "This is a test message to sign";
|
|
||||||
let signature = keypair_sign(message);
|
|
||||||
assert_true(signature.len() > 0, "Signature should not be empty");
|
|
||||||
print(`✓ Message signed successfully: ${signature.len()} bytes`);
|
|
||||||
|
|
||||||
// Test verifying a signature
|
|
||||||
print("Testing signature verification...");
|
|
||||||
let is_valid = keypair_verify(message, signature);
|
|
||||||
assert_true(is_valid, "Signature should be valid");
|
|
||||||
print("✓ Signature verified successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("--- Keypair Operations Tests completed successfully ---");
|
|
||||||
passed += 1;
|
|
||||||
test_results["01_keypair_operations"] = "PASSED";
|
|
||||||
} catch(err) {
|
|
||||||
print(`!!! Error in Keypair Operations Tests: ${err}`);
|
|
||||||
failed += 1;
|
|
||||||
test_results["01_keypair_operations"] = `FAILED: ${err}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: Key Space Operations
|
|
||||||
print("\n--- Running Key Space Operations Tests ---");
|
|
||||||
try {
|
|
||||||
// Clear any existing session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Test creating a new key space
|
|
||||||
print("Testing key space creation...");
|
|
||||||
let space_name = "test_keyspace";
|
|
||||||
let password = "secure_password";
|
|
||||||
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print(`✓ Key space "${space_name}" created successfully`);
|
|
||||||
|
|
||||||
// Test adding keypairs to a key space
|
|
||||||
print("Testing adding keypairs to key space...");
|
|
||||||
let keypair1_name = "keypair1";
|
|
||||||
let keypair2_name = "keypair2";
|
|
||||||
|
|
||||||
if create_keypair(keypair1_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair1_name}" created successfully`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_keypair(keypair2_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair2_name}" created successfully`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test listing keypairs in a key space
|
|
||||||
print("Testing listing keypairs in key space...");
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
assert_true(keypairs.len() == 2, `Expected 2 keypairs, got ${keypairs.len()}`);
|
|
||||||
assert_true(keypairs.contains(keypair1_name), `Keypair list should contain "${keypair1_name}"`);
|
|
||||||
assert_true(keypairs.contains(keypair2_name), `Keypair list should contain "${keypair2_name}"`);
|
|
||||||
print(`✓ Listed keypairs successfully: ${keypairs}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("--- Key Space Operations Tests completed successfully ---");
|
|
||||||
passed += 1;
|
|
||||||
test_results["02_keyspace_operations"] = "PASSED";
|
|
||||||
} catch(err) {
|
|
||||||
print(`!!! Error in Key Space Operations Tests: ${err}`);
|
|
||||||
failed += 1;
|
|
||||||
test_results["02_keyspace_operations"] = `FAILED: ${err}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 3: Session Management
|
|
||||||
print("\n--- Running Session Management Tests ---");
|
|
||||||
try {
|
|
||||||
// Clear any existing session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Test creating a key space and setting it as current
|
|
||||||
print("Testing key space creation and activation...");
|
|
||||||
let space_name1 = "session_test_space1";
|
|
||||||
let space_name2 = "session_test_space2";
|
|
||||||
let password = "secure_password";
|
|
||||||
|
|
||||||
// Create first key space
|
|
||||||
if create_key_space(space_name1, password) {
|
|
||||||
print(`✓ Key space "${space_name1}" created successfully`);
|
|
||||||
|
|
||||||
// Test creating keypairs in the current space
|
|
||||||
print("Testing creating keypairs in current space...");
|
|
||||||
let keypair1_name = "session_keypair1";
|
|
||||||
|
|
||||||
if create_keypair(keypair1_name, password) {
|
|
||||||
print(`✓ Keypair "${keypair1_name}" created successfully in space "${space_name1}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test selecting a keypair
|
|
||||||
print("Testing selecting a keypair...");
|
|
||||||
if select_keypair(keypair1_name) {
|
|
||||||
print(`✓ Selected keypair "${keypair1_name}" successfully`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("--- Session Management Tests completed successfully ---");
|
|
||||||
passed += 1;
|
|
||||||
test_results["03_session_management"] = "PASSED";
|
|
||||||
} catch(err) {
|
|
||||||
print(`!!! Error in Session Management Tests: ${err}`);
|
|
||||||
failed += 1;
|
|
||||||
test_results["03_session_management"] = `FAILED: ${err}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 4: Encryption and Decryption
|
|
||||||
print("\n--- Running Encryption and Decryption Tests ---");
|
|
||||||
try {
|
|
||||||
// Clear any existing session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Test creating keypairs for sender and recipient
|
|
||||||
print("Setting up sender and recipient keypairs...");
|
|
||||||
let space_name = "encryption_test_space";
|
|
||||||
let password = "secure_password";
|
|
||||||
let sender_name = "sender_keypair";
|
|
||||||
let recipient_name = "recipient_keypair";
|
|
||||||
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print(`✓ Key space "${space_name}" created successfully`);
|
|
||||||
|
|
||||||
// Create sender keypair
|
|
||||||
if create_keypair(sender_name, password) {
|
|
||||||
print(`✓ Sender keypair "${sender_name}" created successfully`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create recipient keypair
|
|
||||||
if create_keypair(recipient_name, password) {
|
|
||||||
print(`✓ Recipient keypair "${recipient_name}" created successfully`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get recipient's public key
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Selected recipient keypair "${recipient_name}" successfully`);
|
|
||||||
let recipient_pub_key = keypair_pub_key();
|
|
||||||
|
|
||||||
// Switch to sender keypair
|
|
||||||
if select_keypair(sender_name) {
|
|
||||||
print(`✓ Selected sender keypair "${sender_name}" successfully`);
|
|
||||||
|
|
||||||
// Test encrypting a message with recipient's public key
|
|
||||||
print("\nTesting encrypting a message...");
|
|
||||||
let message = "This is a secret message for the recipient";
|
|
||||||
let ciphertext = encrypt_asymmetric(recipient_pub_key, message);
|
|
||||||
|
|
||||||
// Switch back to recipient keypair to decrypt
|
|
||||||
if select_keypair(recipient_name) {
|
|
||||||
print(`✓ Switched back to recipient keypair "${recipient_name}" successfully`);
|
|
||||||
|
|
||||||
// Test decrypting the message
|
|
||||||
print("Testing decrypting the message...");
|
|
||||||
let decrypted = decrypt_asymmetric(ciphertext);
|
|
||||||
assert_true(decrypted == message, "Decrypted message should match original");
|
|
||||||
print(`✓ Message decrypted successfully: "${decrypted}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("--- Encryption and Decryption Tests completed successfully ---");
|
|
||||||
passed += 1;
|
|
||||||
test_results["04_encryption_decryption"] = "PASSED";
|
|
||||||
} catch(err) {
|
|
||||||
print(`!!! Error in Encryption and Decryption Tests: ${err}`);
|
|
||||||
failed += 1;
|
|
||||||
test_results["04_encryption_decryption"] = `FAILED: ${err}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 5: Error Handling
|
|
||||||
print("\n--- Running Error Handling Tests ---");
|
|
||||||
try {
|
|
||||||
// Clear any existing session
|
|
||||||
clear_session();
|
|
||||||
|
|
||||||
// Test NoActiveSpace error
|
|
||||||
print("Testing NoActiveSpace error...");
|
|
||||||
let no_active_space_error_caught = false;
|
|
||||||
try {
|
|
||||||
// Try to create a keypair without an active space
|
|
||||||
create_keypair("test_keypair", "password");
|
|
||||||
} catch(err) {
|
|
||||||
no_active_space_error_caught = true;
|
|
||||||
print(`✓ Caught expected error: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(no_active_space_error_caught, "NoActiveSpace error should be caught");
|
|
||||||
|
|
||||||
// Create a key space for further tests
|
|
||||||
if create_key_space("error_test_space", "password") {
|
|
||||||
print(`✓ Key space created successfully`);
|
|
||||||
|
|
||||||
// Test KeypairNotFound error
|
|
||||||
print("Testing KeypairNotFound error...");
|
|
||||||
let keypair_not_found_error_caught = false;
|
|
||||||
try {
|
|
||||||
// Try to select a non-existent keypair
|
|
||||||
select_keypair("nonexistent_keypair");
|
|
||||||
} catch(err) {
|
|
||||||
keypair_not_found_error_caught = true;
|
|
||||||
print(`✓ Caught expected error: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(keypair_not_found_error_caught, "KeypairNotFound error should be caught");
|
|
||||||
|
|
||||||
// Test NoKeypairSelected error
|
|
||||||
print("Testing NoKeypairSelected error...");
|
|
||||||
let no_keypair_selected_error_caught = false;
|
|
||||||
try {
|
|
||||||
// Try to get the public key without selecting a keypair
|
|
||||||
keypair_pub_key();
|
|
||||||
} catch(err) {
|
|
||||||
no_keypair_selected_error_caught = true;
|
|
||||||
print(`✓ Caught expected error: ${err}`);
|
|
||||||
}
|
|
||||||
assert_true(no_keypair_selected_error_caught, "NoKeypairSelected error should be caught");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("--- Error Handling Tests completed successfully ---");
|
|
||||||
passed += 1;
|
|
||||||
test_results["05_error_handling"] = "PASSED";
|
|
||||||
} catch(err) {
|
|
||||||
print(`!!! Error in Error Handling Tests: ${err}`);
|
|
||||||
failed += 1;
|
|
||||||
test_results["05_error_handling"] = `FAILED: ${err}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Test Summary ===");
|
|
||||||
print(`Passed: ${passed}`);
|
|
||||||
print(`Failed: ${failed}`);
|
|
||||||
print(`Total: ${passed + failed}`);
|
|
||||||
|
|
||||||
// Print detailed results
|
|
||||||
print("\n=== Detailed Test Results ===");
|
|
||||||
for key in test_results.keys() {
|
|
||||||
let result = test_results[key];
|
|
||||||
if result.starts_with("PASSED") {
|
|
||||||
print(`✓ ${key}: ${result}`);
|
|
||||||
} else {
|
|
||||||
print(`✗ ${key}: ${result}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failed == 0 {
|
|
||||||
print("\n✅ All tests passed!");
|
|
||||||
} else {
|
|
||||||
print("\n❌ Some tests failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the number of failed tests (0 means success)
|
|
||||||
failed;
|
|
@ -24,7 +24,7 @@ log "${BLUE} Running All Rhai Tests ${NC}"
|
|||||||
log "${BLUE}=======================================${NC}"
|
log "${BLUE}=======================================${NC}"
|
||||||
|
|
||||||
# Find all test runner scripts
|
# Find all test runner scripts
|
||||||
RUNNERS=$(find rhai_tests -name "run_all_tests.rhai")
|
RUNNERS=$(find src/rhai_tests -name "run_all_tests.rhai")
|
||||||
|
|
||||||
# Initialize counters
|
# Initialize counters
|
||||||
TOTAL_MODULES=0
|
TOTAL_MODULES=0
|
||||||
|
34
src/docs/.gitignore
vendored
Normal file
34
src/docs/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.docusaurus
|
||||||
|
.cache-loader
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
bun.lockb
|
||||||
|
bun.lock
|
||||||
|
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
build.sh
|
||||||
|
build_dev.sh
|
||||||
|
develop.sh
|
||||||
|
|
||||||
|
docusaurus.config.ts
|
||||||
|
|
||||||
|
sidebars.ts
|
||||||
|
|
||||||
|
tsconfig.json
|
@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use crate::vault::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use super::wallet::EthereumWallet;
|
use super::wallet::EthereumWallet;
|
||||||
use super::networks::NetworkConfig;
|
use super::networks::NetworkConfig;
|
||||||
|
|
@ -16,8 +16,11 @@ mod provider;
|
|||||||
mod transaction;
|
mod transaction;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod contract;
|
mod contract;
|
||||||
pub mod contract_utils;
|
mod contract_utils;
|
||||||
pub mod networks;
|
pub mod networks;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests;
|
||||||
|
|
||||||
// Re-export public types and functions
|
// Re-export public types and functions
|
||||||
pub use wallet::EthereumWallet;
|
pub use wallet::EthereumWallet;
|
||||||
pub use networks::NetworkConfig;
|
pub use networks::NetworkConfig;
|
@ -3,7 +3,7 @@
|
|||||||
use ethers::types::Address;
|
use ethers::types::Address;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::vault::ethereum::*;
|
use crate::hero_vault::ethereum::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contract_creation() {
|
fn test_contract_creation() {
|
@ -3,7 +3,7 @@
|
|||||||
use ethers::types::Address;
|
use ethers::types::Address;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::vault::ethereum::*;
|
use crate::hero_vault::ethereum::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contract_creation() {
|
fn test_contract_creation() {
|
@ -1,6 +1,6 @@
|
|||||||
//! Tests for Ethereum network functionality.
|
//! Tests for Ethereum network functionality.
|
||||||
|
|
||||||
use crate::vault::ethereum::*;
|
use crate::hero_vault::ethereum::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_network_config() {
|
fn test_network_config() {
|
@ -1,9 +1,9 @@
|
|||||||
//! Tests for Ethereum transaction functionality.
|
//! Tests for Ethereum transaction functionality.
|
||||||
|
|
||||||
use crate::vault::ethereum::*;
|
use crate::hero_vault::ethereum::*;
|
||||||
use crate::vault::keypair::implementation::KeyPair;
|
use crate::hero_vault::keypair::KeyPair;
|
||||||
use ethers::types::U256;
|
use ethers::types::U256;
|
||||||
// use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_balance() {
|
fn test_format_balance() {
|
@ -1,7 +1,7 @@
|
|||||||
//! Tests for Ethereum wallet functionality.
|
//! Tests for Ethereum wallet functionality.
|
||||||
|
|
||||||
use crate::vault::ethereum::*;
|
use crate::hero_vault::ethereum::*;
|
||||||
use crate::vault::keypair::implementation::KeyPair;
|
use crate::hero_vault::keypair::KeyPair;
|
||||||
use ethers::utils::hex;
|
use ethers::utils::hex;
|
||||||
use ethers::prelude::Signer;
|
use ethers::prelude::Signer;
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ fn test_wallet_management() {
|
|||||||
clear_ethereum_wallets();
|
clear_ethereum_wallets();
|
||||||
|
|
||||||
// Create a key space and keypair
|
// Create a key space and keypair
|
||||||
crate::vault::keypair::session_manager::create_space("test_space").unwrap();
|
crate::hero_vault::keypair::create_space("test_space").unwrap();
|
||||||
crate::vault::keypair::create_keypair("test_keypair3").unwrap();
|
crate::hero_vault::keypair::create_keypair("test_keypair3").unwrap();
|
||||||
|
|
||||||
// Create a wallet
|
// Create a wallet
|
||||||
let wallet = create_ethereum_wallet().unwrap();
|
let wallet = create_ethereum_wallet().unwrap();
|
@ -3,7 +3,7 @@
|
|||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
use ethers::types::transaction::eip2718::TypedTransaction;
|
use ethers::types::transaction::eip2718::TypedTransaction;
|
||||||
|
|
||||||
use crate::vault::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use super::wallet::EthereumWallet;
|
use super::wallet::EthereumWallet;
|
||||||
use super::networks::NetworkConfig;
|
use super::networks::NetworkConfig;
|
||||||
use super::provider;
|
use super::provider;
|
@ -7,8 +7,8 @@ use k256::ecdsa::SigningKey;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::vault::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use crate::vault::keypair::KeyPair;
|
use crate::hero_vault::keypair::KeyPair;
|
||||||
use super::networks::NetworkConfig;
|
use super::networks::NetworkConfig;
|
||||||
|
|
||||||
/// An Ethereum wallet derived from a keypair.
|
/// An Ethereum wallet derived from a keypair.
|
@ -6,9 +6,8 @@ The Keypair module provides functionality for creating, managing, and using ECDS
|
|||||||
|
|
||||||
The Keypair module is organized into:
|
The Keypair module is organized into:
|
||||||
|
|
||||||
- `keypair_types.rs` - Defines the KeyPair and related types.
|
- `implementation.rs` - Core implementation of the KeyPair and KeySpace types
|
||||||
- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces.
|
- `mod.rs` - Module exports and public interface
|
||||||
- `mod.rs` - Module exports and public interface.
|
|
||||||
|
|
||||||
## Key Types
|
## Key Types
|
||||||
|
|
||||||
@ -114,44 +113,26 @@ let mut loaded_space = KeySpace::load("my_space", "secure_password")?;
|
|||||||
The module provides functionality for creating, selecting, and using keypairs:
|
The module provides functionality for creating, selecting, and using keypairs:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use crate::vault::keypair::{KeySpace, KeyPair};
|
// Create a new keypair in the key space
|
||||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
let keypair = space.create_keypair("my_keypair", "secure_password")?;
|
||||||
|
|
||||||
fn demonstrate_keypair_management() -> Result<(), CryptoError> {
|
// Select a keypair for use
|
||||||
// Create a new key space
|
space.select_keypair("my_keypair")?;
|
||||||
let mut space = KeySpace::new("my_space", "secure_password")?;
|
|
||||||
|
|
||||||
// Create a new keypair in the key space
|
// Get the currently selected keypair
|
||||||
let keypair = space.create_keypair("my_keypair", "secure_password")?;
|
let current = space.current_keypair()?;
|
||||||
println!("Created keypair: {}", keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
|
||||||
|
|
||||||
// Select a keypair for use
|
// List all keypairs in the key space
|
||||||
space.select_keypair("my_keypair")?;
|
let keypairs = space.list_keypairs()?;
|
||||||
println!("Selected keypair: {}", space.current_keypair()?.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
|
||||||
|
|
||||||
// List all keypairs in the key space
|
// Get a keypair by name
|
||||||
let keypairs = space.list_keypairs()?;
|
let keypair = space.get_keypair("my_keypair")?;
|
||||||
println!("Keypairs in space: {:?}", keypairs);
|
|
||||||
|
|
||||||
// Get a keypair by name
|
// Remove a keypair from the key space
|
||||||
let retrieved_keypair = space.get_keypair("my_keypair")?;
|
space.remove_keypair("my_keypair")?;
|
||||||
println!("Retrieved keypair: {}", retrieved_keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
|
||||||
|
|
||||||
// Rename a keypair
|
// Rename a keypair
|
||||||
space.rename_keypair("my_keypair", "new_name")?;
|
space.rename_keypair("my_keypair", "new_name")?;
|
||||||
println!("Renamed keypair to new_name");
|
|
||||||
let keypairs_after_rename = space.list_keypairs()?;
|
|
||||||
println!("Keypairs in space after rename: {:?}", keypairs_after_rename);
|
|
||||||
|
|
||||||
|
|
||||||
// Remove a keypair from the key space
|
|
||||||
space.remove_keypair("new_name")?;
|
|
||||||
println!("Removed keypair new_name");
|
|
||||||
let keypairs_after_remove = space.list_keypairs()?;
|
|
||||||
println!("Keypairs in space after removal: {:?}", keypairs_after_remove);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Digital Signatures
|
### Digital Signatures
|
||||||
@ -159,35 +140,12 @@ fn demonstrate_keypair_management() -> Result<(), CryptoError> {
|
|||||||
The module provides functionality for signing and verifying messages using ECDSA:
|
The module provides functionality for signing and verifying messages using ECDSA:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use crate::vault::keypair::KeySpace;
|
// Sign a message using the selected keypair
|
||||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
let keypair = space.current_keypair()?;
|
||||||
|
let signature = keypair.sign("This is a message to sign".as_bytes())?;
|
||||||
|
|
||||||
fn demonstrate_digital_signatures() -> Result<(), CryptoError> {
|
// Verify a signature
|
||||||
// Assuming a key space and selected keypair exist
|
let is_valid = keypair.verify("This is a message to sign".as_bytes(), &signature)?;
|
||||||
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
|
|
||||||
let mut space = KeySpace::new("temp_space_for_demo", "password")?; // Or create a new one for demo
|
|
||||||
space.create_keypair("my_signing_key", "key_password")?;
|
|
||||||
space.select_keypair("my_signing_key")?;
|
|
||||||
|
|
||||||
|
|
||||||
// Sign a message using the selected keypair
|
|
||||||
let keypair = space.current_keypair()?;
|
|
||||||
let message = "This is a message to sign".as_bytes();
|
|
||||||
let signature = keypair.sign(message)?;
|
|
||||||
println!("Message signed. Signature: {:?}", signature);
|
|
||||||
|
|
||||||
// Verify a signature
|
|
||||||
let is_valid = keypair.verify(message, &signature)?;
|
|
||||||
println!("Signature valid: {}", is_valid);
|
|
||||||
|
|
||||||
// Example of invalid signature verification
|
|
||||||
let invalid_signature = vec![0u8; signature.len()]; // A dummy invalid signature
|
|
||||||
let is_valid_invalid = keypair.verify(message, &invalid_signature)?;
|
|
||||||
println!("Invalid signature valid: {}", is_valid_invalid);
|
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ethereum Address Derivation
|
### Ethereum Address Derivation
|
||||||
@ -195,53 +153,11 @@ fn demonstrate_digital_signatures() -> Result<(), CryptoError> {
|
|||||||
The module provides functionality for deriving Ethereum addresses from keypairs:
|
The module provides functionality for deriving Ethereum addresses from keypairs:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use crate::vault::keypair::KeySpace;
|
// Derive an Ethereum address from a keypair
|
||||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
let keypair = space.current_keypair()?;
|
||||||
|
let address = keypair.to_ethereum_address()?;
|
||||||
fn demonstrate_ethereum_address_derivation() -> Result<(), CryptoError> {
|
|
||||||
// Assuming a key space and selected keypair exist
|
|
||||||
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
|
|
||||||
let mut space = KeySpace::new("temp_space_for_eth_demo", "password")?; // Or create a new one for demo
|
|
||||||
space.create_keypair("my_eth_key", "key_password")?;
|
|
||||||
space.select_keypair("my_eth_key")?;
|
|
||||||
|
|
||||||
// Derive an Ethereum address from a keypair
|
|
||||||
let keypair = space.current_keypair()?;
|
|
||||||
let address = keypair.to_ethereum_address()?;
|
|
||||||
println!("Derived Ethereum address: {}", address);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Including in Your Project
|
|
||||||
|
|
||||||
To include the Hero Vault Keypair module in your Rust project, add the following to your `Cargo.toml` file:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
hero_vault = "0.1.0" # Replace with the actual version
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, you can import and use the module in your Rust code:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use hero_vault::vault::keypair::{KeySpace, KeyPair};
|
|
||||||
use hero_vault::vault::error::CryptoError;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Tests for the Keypair module are included within the source files, likely in `session_manager.rs` or `mod.rs` as inline tests.
|
|
||||||
|
|
||||||
To run the tests, navigate to the root directory of the project in your terminal and execute the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --lib vault::keypair
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will run all tests specifically within the `vault::keypair` module.
|
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
|
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
|
@ -1,13 +1,14 @@
|
|||||||
/// Implementation of keypair functionality.
|
//! Implementation of keypair functionality.
|
||||||
|
|
||||||
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
|
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::sync::Mutex;
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::vault::symmetric::implementation;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use crate::vault::error::CryptoError;
|
|
||||||
|
|
||||||
/// A keypair for signing and verifying messages.
|
/// A keypair for signing and verifying messages.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -225,7 +226,7 @@ impl KeyPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt the message using the derived key
|
// Encrypt the message using the derived key
|
||||||
let ciphertext = implementation::encrypt_with_key(&shared_secret, message)
|
let ciphertext = crate::hero_vault::symmetric::encrypt_with_key(&shared_secret, message)
|
||||||
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
||||||
|
|
||||||
// Format: ephemeral_public_key || ciphertext
|
// Format: ephemeral_public_key || ciphertext
|
||||||
@ -262,7 +263,7 @@ impl KeyPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt the message using the derived key
|
// Decrypt the message using the derived key
|
||||||
implementation::decrypt_with_key(&shared_secret, actual_ciphertext)
|
crate::hero_vault::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext)
|
||||||
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
|
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,3 +306,162 @@ impl KeySpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Session state for the current key space and selected keypair.
|
||||||
|
pub struct Session {
|
||||||
|
pub current_space: Option<KeySpace>,
|
||||||
|
pub selected_keypair: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Session {
|
||||||
|
fn default() -> Self {
|
||||||
|
Session {
|
||||||
|
current_space: None,
|
||||||
|
selected_keypair: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global session state.
|
||||||
|
static SESSION: Lazy<Mutex<Session>> = Lazy::new(|| {
|
||||||
|
Mutex::new(Session::default())
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Creates a new key space with the given name.
|
||||||
|
pub fn create_space(name: &str) -> Result<(), CryptoError> {
|
||||||
|
let mut session = SESSION.lock().unwrap();
|
||||||
|
|
||||||
|
// Create a new space
|
||||||
|
let space = KeySpace::new(name);
|
||||||
|
|
||||||
|
// Set as current space
|
||||||
|
session.current_space = Some(space);
|
||||||
|
session.selected_keypair = None;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the current key space.
|
||||||
|
pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> {
|
||||||
|
let mut session = SESSION.lock().unwrap();
|
||||||
|
session.current_space = Some(space);
|
||||||
|
session.selected_keypair = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current key space.
|
||||||
|
pub fn get_current_space() -> Result<KeySpace, CryptoError> {
|
||||||
|
let session = SESSION.lock().unwrap();
|
||||||
|
session.current_space.clone().ok_or(CryptoError::NoActiveSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the current session (logout).
|
||||||
|
pub fn clear_session() {
|
||||||
|
let mut session = SESSION.lock().unwrap();
|
||||||
|
session.current_space = None;
|
||||||
|
session.selected_keypair = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new keypair in the current space.
|
||||||
|
pub fn create_keypair(name: &str) -> Result<(), CryptoError> {
|
||||||
|
let mut session = SESSION.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(ref mut space) = session.current_space {
|
||||||
|
if space.keypairs.contains_key(name) {
|
||||||
|
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let keypair = KeyPair::new(name);
|
||||||
|
space.keypairs.insert(name.to_string(), keypair);
|
||||||
|
|
||||||
|
// Automatically select the new keypair
|
||||||
|
session.selected_keypair = Some(name.to_string());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CryptoError::NoActiveSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selects a keypair for use.
|
||||||
|
pub fn select_keypair(name: &str) -> Result<(), CryptoError> {
|
||||||
|
let mut session = SESSION.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(ref space) = session.current_space {
|
||||||
|
if !space.keypairs.contains_key(name) {
|
||||||
|
return Err(CryptoError::KeypairNotFound(name.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
session.selected_keypair = Some(name.to_string());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CryptoError::NoActiveSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the currently selected keypair.
|
||||||
|
pub fn get_selected_keypair() -> Result<KeyPair, CryptoError> {
|
||||||
|
let session = SESSION.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(ref space) = session.current_space {
|
||||||
|
if let Some(ref keypair_name) = session.selected_keypair {
|
||||||
|
if let Some(keypair) = space.keypairs.get(keypair_name) {
|
||||||
|
return Ok(keypair.clone());
|
||||||
|
}
|
||||||
|
return Err(CryptoError::KeypairNotFound(keypair_name.clone()));
|
||||||
|
}
|
||||||
|
return Err(CryptoError::NoKeypairSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(CryptoError::NoActiveSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all keypair names in the current space.
|
||||||
|
pub fn list_keypairs() -> Result<Vec<String>, CryptoError> {
|
||||||
|
let session = SESSION.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(ref space) = session.current_space {
|
||||||
|
Ok(space.keypairs.keys().cloned().collect())
|
||||||
|
} else {
|
||||||
|
Err(CryptoError::NoActiveSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the public key of the selected keypair.
|
||||||
|
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let keypair = get_selected_keypair()?;
|
||||||
|
Ok(keypair.pub_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives a public key from a private key.
|
||||||
|
pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
KeyPair::pub_key_from_private(private_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs a message with the selected keypair.
|
||||||
|
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let keypair = get_selected_keypair()?;
|
||||||
|
Ok(keypair.sign(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies a message signature with the selected keypair.
|
||||||
|
pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
||||||
|
let keypair = get_selected_keypair()?;
|
||||||
|
keypair.verify(message, signature_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies a message signature with a public key.
|
||||||
|
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
||||||
|
KeyPair::verify_with_public_key(public_key, message, signature_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypts a message for a recipient using their public key.
|
||||||
|
pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let keypair = get_selected_keypair()?;
|
||||||
|
keypair.encrypt_asymmetric(recipient_public_key, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a message that was encrypted with the current keypair's public key.
|
||||||
|
pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let keypair = get_selected_keypair()?;
|
||||||
|
keypair.decrypt_asymmetric(ciphertext)
|
||||||
|
}
|
@ -2,17 +2,13 @@
|
|||||||
//!
|
//!
|
||||||
//! This module provides functionality for creating and managing ECDSA key pairs.
|
//! This module provides functionality for creating and managing ECDSA key pairs.
|
||||||
|
|
||||||
pub mod keypair_types;
|
mod implementation;
|
||||||
pub mod session_manager;
|
|
||||||
|
|
||||||
// Re-export public types and functions
|
// Re-export public types and functions
|
||||||
pub use keypair_types::{KeyPair, KeySpace};
|
pub use implementation::{
|
||||||
pub use session_manager::{
|
KeyPair, KeySpace,
|
||||||
create_space, set_current_space, get_current_space, clear_session,
|
create_space, set_current_space, get_current_space, clear_session,
|
||||||
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
|
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
|
||||||
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
|
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
|
||||||
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
|
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
@ -165,9 +165,3 @@ let loaded_store = KvStore::load("my_store", "secure_password")?;
|
|||||||
let api_key = loaded_store.get("api_key")?;
|
let api_key = loaded_store.get("api_key")?;
|
||||||
println!("API Key: {}", api_key.unwrap_or_default());
|
println!("API Key: {}", api_key.unwrap_or_default());
|
||||||
```
|
```
|
||||||
|
|
||||||
## to test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --lib vault::keypair
|
|
||||||
```
|
|
@ -1,5 +1,6 @@
|
|||||||
//! Error types for the key-value store.
|
//! Error types for the key-value store.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Errors that can occur when using the key-value store.
|
/// Errors that can occur when using the key-value store.
|
||||||
@ -44,20 +45,18 @@ impl From<serde_json::Error> for KvsError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<KvsError> for crate::vault::error::CryptoError {
|
impl From<KvsError> for crate::hero_vault::error::CryptoError {
|
||||||
fn from(err: KvsError) -> Self {
|
fn from(err: KvsError) -> Self {
|
||||||
crate::vault::error::CryptoError::SerializationError(err.to_string())
|
crate::hero_vault::error::CryptoError::SerializationError(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::vault::error::CryptoError> for KvsError {
|
impl From<crate::hero_vault::error::CryptoError> for KvsError {
|
||||||
fn from(err: crate::vault::error::CryptoError) -> Self {
|
fn from(err: crate::hero_vault::error::CryptoError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
crate::vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
crate::hero_vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
||||||
crate::vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
crate::hero_vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
||||||
crate::vault::error::CryptoError::SerializationError(msg) => {
|
crate::hero_vault::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg),
|
||||||
KvsError::Serialization(msg)
|
|
||||||
}
|
|
||||||
_ => KvsError::Other(err.to_string()),
|
_ => KvsError::Other(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,11 @@
|
|||||||
//! Implementation of a simple key-value store using the filesystem.
|
//! Implementation of a simple key-value store using the filesystem.
|
||||||
|
|
||||||
use crate::vault::kvs::error::{KvsError, Result};
|
use crate::hero_vault::kvs::error::{KvsError, Result};
|
||||||
use crate::vault::symmetric::implementation::{
|
use crate::hero_vault::symmetric;
|
||||||
decrypt_symmetric, derive_key_from_password, encrypt_symmetric,
|
|
||||||
};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
/// A key-value pair.
|
/// A key-value pair.
|
||||||
@ -94,9 +92,7 @@ pub fn get_store_path() -> PathBuf {
|
|||||||
pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result<KvStore> {
|
pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result<KvStore> {
|
||||||
// Check if password is provided when encryption is enabled
|
// Check if password is provided when encryption is enabled
|
||||||
if encrypted && password.is_none() {
|
if encrypted && password.is_none() {
|
||||||
return Err(KvsError::Other(
|
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
||||||
"Password required for encrypted store".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the store directory if it doesn't exist
|
// Create the store directory if it doesn't exist
|
||||||
@ -151,9 +147,7 @@ pub fn open_store(name: &str, password: Option<&str>) -> Result<KvStore> {
|
|||||||
|
|
||||||
// If encrypted, we need a password
|
// If encrypted, we need a password
|
||||||
if is_encrypted && password.is_none() {
|
if is_encrypted && password.is_none() {
|
||||||
return Err(KvsError::Other(
|
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
||||||
"Password required for encrypted store".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the store data
|
// Parse the store data
|
||||||
@ -161,8 +155,8 @@ pub fn open_store(name: &str, password: Option<&str>) -> Result<KvStore> {
|
|||||||
// Decrypt the file content
|
// Decrypt the file content
|
||||||
let password = password.unwrap();
|
let password = password.unwrap();
|
||||||
let encrypted_data: Vec<u8> = serde_json::from_str(&file_content)?;
|
let encrypted_data: Vec<u8> = serde_json::from_str(&file_content)?;
|
||||||
let key = derive_key_from_password(password);
|
let key = symmetric::derive_key_from_password(password);
|
||||||
let decrypted_data = decrypt_symmetric(&key, &encrypted_data)?;
|
let decrypted_data = symmetric::decrypt_symmetric(&key, &encrypted_data)?;
|
||||||
let decrypted_str = String::from_utf8(decrypted_data)
|
let decrypted_str = String::from_utf8(decrypted_data)
|
||||||
.map_err(|e| KvsError::Deserialization(e.to_string()))?;
|
.map_err(|e| KvsError::Deserialization(e.to_string()))?;
|
||||||
serde_json::from_str(&decrypted_str)?
|
serde_json::from_str(&decrypted_str)?
|
||||||
@ -249,14 +243,12 @@ impl KvStore {
|
|||||||
if self.encrypted {
|
if self.encrypted {
|
||||||
if let Some(password) = &self.password {
|
if let Some(password) = &self.password {
|
||||||
// Encrypt the data
|
// Encrypt the data
|
||||||
let key = derive_key_from_password(password);
|
let key = symmetric::derive_key_from_password(password);
|
||||||
let encrypted_data = encrypt_symmetric(&key, serialized.as_bytes())?;
|
let encrypted_data = symmetric::encrypt_symmetric(&key, serialized.as_bytes())?;
|
||||||
let encrypted_json = serde_json::to_string(&encrypted_data)?;
|
let encrypted_json = serde_json::to_string(&encrypted_data)?;
|
||||||
fs::write(&self.path, encrypted_json)?;
|
fs::write(&self.path, encrypted_json)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(KvsError::Other(
|
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
||||||
"Password required for encrypted store".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fs::write(&self.path, serialized)?;
|
fs::write(&self.path, serialized)?;
|
||||||
@ -300,9 +292,9 @@ impl KVStore for KvStore {
|
|||||||
|
|
||||||
match data.get(&key_str) {
|
match data.get(&key_str) {
|
||||||
Some(serialized) => {
|
Some(serialized) => {
|
||||||
let value: V = serde_json::from_str(serialized)?;
|
let value = serde_json::from_str(serialized)?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
},
|
||||||
None => Err(KvsError::KeyNotFound(key_str)),
|
None => Err(KvsError::KeyNotFound(key_str)),
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,6 @@ pub mod symmetric;
|
|||||||
pub mod ethereum;
|
pub mod ethereum;
|
||||||
pub mod kvs;
|
pub mod kvs;
|
||||||
|
|
||||||
// Re-export modules
|
|
||||||
// Re-export common types for convenience
|
// Re-export common types for convenience
|
||||||
pub use error::CryptoError;
|
pub use error::CryptoError;
|
||||||
pub use keypair::{KeyPair, KeySpace};
|
pub use keypair::{KeyPair, KeySpace};
|
@ -6,8 +6,8 @@ use rand::{rngs::OsRng, RngCore};
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::vault::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use crate::vault::keypair::KeySpace;
|
use crate::hero_vault::keypair::KeySpace;
|
||||||
|
|
||||||
/// The size of the nonce in bytes.
|
/// The size of the nonce in bytes.
|
||||||
const NONCE_SIZE: usize = 12;
|
const NONCE_SIZE: usize = 12;
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module provides functionality for symmetric encryption using ChaCha20Poly1305.
|
//! This module provides functionality for symmetric encryption using ChaCha20Poly1305.
|
||||||
|
|
||||||
pub mod implementation;
|
mod implementation;
|
||||||
|
|
||||||
// Re-export public types and functions
|
// Re-export public types and functions
|
||||||
pub use implementation::{
|
pub use implementation::{
|
@ -46,8 +46,7 @@ pub mod redisclient;
|
|||||||
pub mod rhai;
|
pub mod rhai;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod virt;
|
pub mod virt;
|
||||||
pub mod vault;
|
pub mod hero_vault;
|
||||||
pub mod zinit_client;
|
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
/// Returns the version of the SAL library
|
/// Returns the version of the SAL library
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
// Define a custom error type for download operations
|
// Define a custom error type for download operations
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -26,17 +26,11 @@ pub enum DownloadError {
|
|||||||
impl fmt::Display for DownloadError {
|
impl fmt::Display for DownloadError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DownloadError::CreateDirectoryFailed(e) => {
|
DownloadError::CreateDirectoryFailed(e) => write!(f, "Error creating directories: {}", e),
|
||||||
write!(f, "Error creating directories: {}", e)
|
|
||||||
}
|
|
||||||
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
||||||
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
||||||
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
||||||
DownloadError::FileTooSmall(size, min) => write!(
|
DownloadError::FileTooSmall(size, min) => write!(f, "Error: Downloaded file is too small ({}KB < {}KB)", size, min),
|
||||||
f,
|
|
||||||
"Error: Downloaded file is too small ({}KB < {}KB)",
|
|
||||||
size, min
|
|
||||||
),
|
|
||||||
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
||||||
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
||||||
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
||||||
@ -80,18 +74,12 @@ impl Error for DownloadError {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::download;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Download a file with no minimum size requirement
|
* // Download a file with no minimum size requirement
|
||||||
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
||||||
*
|
*
|
||||||
* // Download a file with minimum size requirement of 100KB
|
* // Download a file with minimum size requirement of 100KB
|
||||||
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* # Notes
|
* # Notes
|
||||||
@ -107,11 +95,7 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
// Extract filename from URL
|
// Extract filename from URL
|
||||||
let filename = match url.split('/').last() {
|
let filename = match url.split('/').last() {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => {
|
None => return Err(DownloadError::InvalidUrl("cannot extract filename".to_string()))
|
||||||
return Err(DownloadError::InvalidUrl(
|
|
||||||
"cannot extract filename".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a full path for the downloaded file
|
// Create a full path for the downloaded file
|
||||||
@ -123,14 +107,7 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
// Use curl to download the file with progress bar
|
// Use curl to download the file with progress bar
|
||||||
println!("Downloading {} to {}", url, file_path);
|
println!("Downloading {} to {}", url, file_path);
|
||||||
let output = Command::new("curl")
|
let output = Command::new("curl")
|
||||||
.args(&[
|
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
||||||
"--progress-bar",
|
|
||||||
"--location",
|
|
||||||
"--fail",
|
|
||||||
"--output",
|
|
||||||
&temp_path,
|
|
||||||
url,
|
|
||||||
])
|
|
||||||
.status()
|
.status()
|
||||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||||
|
|
||||||
@ -145,17 +122,11 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
let size_kb = size_bytes / 1024;
|
let size_kb = size_bytes / 1024;
|
||||||
let size_mb = size_kb / 1024;
|
let size_mb = size_kb / 1024;
|
||||||
if size_mb > 1 {
|
if size_mb > 1 {
|
||||||
println!(
|
println!("Download complete! File size: {:.2} MB", size_bytes as f64 / (1024.0 * 1024.0));
|
||||||
"Download complete! File size: {:.2} MB",
|
|
||||||
size_bytes as f64 / (1024.0 * 1024.0)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!("Download complete! File size: {:.2} KB", size_bytes as f64 / 1024.0);
|
||||||
"Download complete! File size: {:.2} KB",
|
|
||||||
size_bytes as f64 / 1024.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(_) => println!("Download complete!"),
|
Err(_) => println!("Download complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,10 +142,10 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
|
|
||||||
// Check if it's a compressed file that needs extraction
|
// Check if it's a compressed file that needs extraction
|
||||||
let lower_url = url.to_lowercase();
|
let lower_url = url.to_lowercase();
|
||||||
let is_archive = lower_url.ends_with(".tar.gz")
|
let is_archive = lower_url.ends_with(".tar.gz") ||
|
||||||
|| lower_url.ends_with(".tgz")
|
lower_url.ends_with(".tgz") ||
|
||||||
|| lower_url.ends_with(".tar")
|
lower_url.ends_with(".tar") ||
|
||||||
|| lower_url.ends_with(".zip");
|
lower_url.ends_with(".zip");
|
||||||
|
|
||||||
if is_archive {
|
if is_archive {
|
||||||
// Extract the file using the appropriate command with progress indication
|
// Extract the file using the appropriate command with progress indication
|
||||||
@ -196,11 +167,9 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
match output {
|
match output {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(DownloadError::ExtractionFailed(
|
return Err(DownloadError::ExtractionFailed("Error extracting archive".to_string()));
|
||||||
"Error extracting archive".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +178,7 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
let count = entries.count();
|
let count = entries.count();
|
||||||
println!("Extraction complete! Extracted {} files/directories", count);
|
println!("Extraction complete! Extracted {} files/directories", count);
|
||||||
}
|
},
|
||||||
Err(_) => println!("Extraction complete!"),
|
Err(_) => println!("Extraction complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,18 +210,12 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::download_file;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Download a file with no minimum size requirement
|
* // Download a file with no minimum size requirement
|
||||||
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
||||||
*
|
*
|
||||||
* // Download a file with minimum size requirement of 100KB
|
* // Download a file with minimum size requirement of 100KB
|
||||||
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||||
@ -268,14 +231,7 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
// Use curl to download the file with progress bar
|
// Use curl to download the file with progress bar
|
||||||
println!("Downloading {} to {}", url, dest);
|
println!("Downloading {} to {}", url, dest);
|
||||||
let output = Command::new("curl")
|
let output = Command::new("curl")
|
||||||
.args(&[
|
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
||||||
"--progress-bar",
|
|
||||||
"--location",
|
|
||||||
"--fail",
|
|
||||||
"--output",
|
|
||||||
&temp_path,
|
|
||||||
url,
|
|
||||||
])
|
|
||||||
.status()
|
.status()
|
||||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||||
|
|
||||||
@ -290,17 +246,11 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
let size_kb = size_bytes / 1024;
|
let size_kb = size_bytes / 1024;
|
||||||
let size_mb = size_kb / 1024;
|
let size_mb = size_kb / 1024;
|
||||||
if size_mb > 1 {
|
if size_mb > 1 {
|
||||||
println!(
|
println!("Download complete! File size: {:.2} MB", size_bytes as f64 / (1024.0 * 1024.0));
|
||||||
"Download complete! File size: {:.2} MB",
|
|
||||||
size_bytes as f64 / (1024.0 * 1024.0)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!("Download complete! File size: {:.2} KB", size_bytes as f64 / 1024.0);
|
||||||
"Download complete! File size: {:.2} KB",
|
|
||||||
size_bytes as f64 / 1024.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(_) => println!("Download complete!"),
|
Err(_) => println!("Download complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,14 +284,9 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::chmod_exec;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Make a file executable
|
* // Make a file executable
|
||||||
* chmod_exec("/path/to/file")?;
|
* chmod_exec("/path/to/file")?;
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||||
@ -349,17 +294,11 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
|||||||
|
|
||||||
// Check if the path exists and is a file
|
// Check if the path exists and is a file
|
||||||
if !path_obj.exists() {
|
if !path_obj.exists() {
|
||||||
return Err(DownloadError::NotAFile(format!(
|
return Err(DownloadError::NotAFile(format!("Path does not exist: {}", path)));
|
||||||
"Path does not exist: {}",
|
|
||||||
path
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path_obj.is_file() {
|
if !path_obj.is_file() {
|
||||||
return Err(DownloadError::NotAFile(format!(
|
return Err(DownloadError::NotAFile(format!("Path is not a file: {}", path)));
|
||||||
"Path is not a file: {}",
|
|
||||||
path
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current permissions
|
// Get current permissions
|
||||||
@ -380,19 +319,16 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
|||||||
{
|
{
|
||||||
// On non-Unix platforms, we can't set executable bit directly
|
// On non-Unix platforms, we can't set executable bit directly
|
||||||
// Just return success with a warning
|
// Just return success with a warning
|
||||||
return Ok(format!(
|
return Ok(format!("Made {} executable (note: non-Unix platform, may not be fully supported)", path));
|
||||||
"Made {} executable (note: non-Unix platform, may not be fully supported)",
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the new permissions
|
// Apply the new permissions
|
||||||
fs::set_permissions(path, permissions).map_err(|e| {
|
fs::set_permissions(path, permissions).map_err(|e|
|
||||||
DownloadError::CommandExecutionFailed(io::Error::new(
|
DownloadError::CommandExecutionFailed(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
format!("Failed to set executable permissions: {}", e),
|
format!("Failed to set executable permissions: {}", e)
|
||||||
))
|
))
|
||||||
})?;
|
)?;
|
||||||
|
|
||||||
Ok(format!("Made {} executable", path))
|
Ok(format!("Made {} executable", path))
|
||||||
}
|
}
|
||||||
@ -412,14 +348,9 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::download_install;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Download and install a .deb package
|
* // Download and install a .deb package
|
||||||
* let result = download_install("https://example.com/package.deb", 100)?;
|
* let result = download_install("https://example.com/package.deb", 100)?;
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* # Notes
|
* # Notes
|
||||||
@ -431,11 +362,7 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
// Extract filename from URL
|
// Extract filename from URL
|
||||||
let filename = match url.split('/').last() {
|
let filename = match url.split('/').last() {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => {
|
None => return Err(DownloadError::InvalidUrl("cannot extract filename".to_string()))
|
||||||
return Err(DownloadError::InvalidUrl(
|
|
||||||
"cannot extract filename".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a proper destination path
|
// Create a proper destination path
|
||||||
@ -443,10 +370,10 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
|
|
||||||
// Check if it's a compressed file that needs extraction
|
// Check if it's a compressed file that needs extraction
|
||||||
let lower_url = url.to_lowercase();
|
let lower_url = url.to_lowercase();
|
||||||
let is_archive = lower_url.ends_with(".tar.gz")
|
let is_archive = lower_url.ends_with(".tar.gz") ||
|
||||||
|| lower_url.ends_with(".tgz")
|
lower_url.ends_with(".tgz") ||
|
||||||
|| lower_url.ends_with(".tar")
|
lower_url.ends_with(".tar") ||
|
||||||
|| lower_url.ends_with(".zip");
|
lower_url.ends_with(".zip");
|
||||||
|
|
||||||
let download_result = if is_archive {
|
let download_result = if is_archive {
|
||||||
// For archives, use the directory-based download function
|
// For archives, use the directory-based download function
|
||||||
@ -474,15 +401,13 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(DownloadError::PlatformNotSupported(
|
return Err(DownloadError::PlatformNotSupported(
|
||||||
"Cannot install .deb package: not on a Debian-based system".to_string(),
|
"Cannot install .deb package: not on a Debian-based system".to_string()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(_) => {
|
Err(_) => return Err(DownloadError::PlatformNotSupported(
|
||||||
return Err(DownloadError::PlatformNotSupported(
|
"Failed to check system compatibility for .deb installation".to_string()
|
||||||
"Failed to check system compatibility for .deb installation".to_string(),
|
)),
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the .deb package non-interactively
|
// Install the .deb package non-interactively
|
||||||
@ -503,17 +428,17 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
if let Ok(fix_status) = fix_deps {
|
if let Ok(fix_status) = fix_deps {
|
||||||
if !fix_status.success() {
|
if !fix_status.success() {
|
||||||
return Err(DownloadError::InstallationFailed(
|
return Err(DownloadError::InstallationFailed(
|
||||||
"Failed to resolve package dependencies".to_string(),
|
"Failed to resolve package dependencies".to_string()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(DownloadError::InstallationFailed(
|
return Err(DownloadError::InstallationFailed(
|
||||||
"Failed to resolve package dependencies".to_string(),
|
"Failed to resolve package dependencies".to_string()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Package installation completed successfully");
|
println!("Package installation completed successfully");
|
||||||
}
|
},
|
||||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
235
src/os/fs.rs
235
src/os/fs.rs
@ -1,9 +1,9 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
// Define a custom error type for file system operations
|
// Define a custom error type for file system operations
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -33,18 +33,14 @@ impl fmt::Display for FsError {
|
|||||||
match self {
|
match self {
|
||||||
FsError::DirectoryNotFound(dir) => write!(f, "Directory '{}' does not exist", dir),
|
FsError::DirectoryNotFound(dir) => write!(f, "Directory '{}' does not exist", dir),
|
||||||
FsError::FileNotFound(pattern) => write!(f, "No files found matching '{}'", pattern),
|
FsError::FileNotFound(pattern) => write!(f, "No files found matching '{}'", pattern),
|
||||||
FsError::CreateDirectoryFailed(e) => {
|
FsError::CreateDirectoryFailed(e) => write!(f, "Failed to create parent directories: {}", e),
|
||||||
write!(f, "Failed to create parent directories: {}", e)
|
|
||||||
}
|
|
||||||
FsError::CopyFailed(e) => write!(f, "Failed to copy file: {}", e),
|
FsError::CopyFailed(e) => write!(f, "Failed to copy file: {}", e),
|
||||||
FsError::DeleteFailed(e) => write!(f, "Failed to delete: {}", e),
|
FsError::DeleteFailed(e) => write!(f, "Failed to delete: {}", e),
|
||||||
FsError::CommandFailed(e) => write!(f, "{}", e),
|
FsError::CommandFailed(e) => write!(f, "{}", e),
|
||||||
FsError::CommandNotFound(e) => write!(f, "Command not found: {}", e),
|
FsError::CommandNotFound(e) => write!(f, "Command not found: {}", e),
|
||||||
FsError::CommandExecutionError(e) => write!(f, "Failed to execute command: {}", e),
|
FsError::CommandExecutionError(e) => write!(f, "Failed to execute command: {}", e),
|
||||||
FsError::InvalidGlobPattern(e) => write!(f, "Invalid glob pattern: {}", e),
|
FsError::InvalidGlobPattern(e) => write!(f, "Invalid glob pattern: {}", e),
|
||||||
FsError::NotADirectory(path) => {
|
FsError::NotADirectory(path) => write!(f, "Path '{}' exists but is not a directory", path),
|
||||||
write!(f, "Path '{}' exists but is not a directory", path)
|
|
||||||
}
|
|
||||||
FsError::NotAFile(path) => write!(f, "Path '{}' is not a regular file", path),
|
FsError::NotAFile(path) => write!(f, "Path '{}' is not a regular file", path),
|
||||||
FsError::UnknownFileType(path) => write!(f, "Unknown file type at '{}'", path),
|
FsError::UnknownFileType(path) => write!(f, "Unknown file type at '{}'", path),
|
||||||
FsError::MetadataError(e) => write!(f, "Failed to get file metadata: {}", e),
|
FsError::MetadataError(e) => write!(f, "Failed to get file metadata: {}", e),
|
||||||
@ -90,10 +86,7 @@ impl Error for FsError {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::copy;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Copy a single file
|
* // Copy a single file
|
||||||
* let result = copy("file.txt", "backup/file.txt")?;
|
* let result = copy("file.txt", "backup/file.txt")?;
|
||||||
*
|
*
|
||||||
@ -102,9 +95,6 @@ impl Error for FsError {
|
|||||||
*
|
*
|
||||||
* // Copy a directory recursively
|
* // Copy a directory recursively
|
||||||
* let result = copy("src_dir", "dest_dir")?;
|
* let result = copy("src_dir", "dest_dir")?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||||
@ -120,7 +110,9 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
// Use glob to expand wildcards
|
// Use glob to expand wildcards
|
||||||
let entries = glob::glob(src).map_err(FsError::InvalidGlobPattern)?;
|
let entries = glob::glob(src).map_err(FsError::InvalidGlobPattern)?;
|
||||||
|
|
||||||
let paths: Vec<_> = entries.filter_map(Result::ok).collect();
|
let paths: Vec<_> = entries
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Err(FsError::FileNotFound(src.to_string()));
|
return Err(FsError::FileNotFound(src.to_string()));
|
||||||
@ -158,23 +150,16 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
// For directories, use platform-specific command
|
// For directories, use platform-specific command
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let output = Command::new("xcopy")
|
let output = Command::new("xcopy")
|
||||||
.args(&[
|
.args(&["/E", "/I", "/H", "/Y",
|
||||||
"/E",
|
|
||||||
"/I",
|
|
||||||
"/H",
|
|
||||||
"/Y",
|
|
||||||
&path.to_string_lossy(),
|
&path.to_string_lossy(),
|
||||||
&target_path.to_string_lossy(),
|
&target_path.to_string_lossy()])
|
||||||
])
|
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let output = Command::new("cp")
|
let output = Command::new("cp")
|
||||||
.args(&[
|
.args(&["-R",
|
||||||
"-R",
|
|
||||||
&path.to_string_lossy(),
|
&path.to_string_lossy(),
|
||||||
&target_path.to_string_lossy(),
|
&target_path.to_string_lossy()])
|
||||||
])
|
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
@ -182,26 +167,17 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
if status.success() {
|
if status.success() {
|
||||||
success_count += 1;
|
success_count += 1;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(e) => println!(
|
Err(e) => println!("Warning: Failed to copy directory {}: {}", path.display(), e),
|
||||||
"Warning: Failed to copy directory {}: {}",
|
|
||||||
path.display(),
|
|
||||||
e
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if success_count > 0 {
|
if success_count > 0 {
|
||||||
Ok(format!(
|
Ok(format!("Successfully copied {} items from '{}' to '{}'",
|
||||||
"Successfully copied {} items from '{}' to '{}'",
|
success_count, src, dest))
|
||||||
success_count, src, dest
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Err(FsError::CommandFailed(format!(
|
Err(FsError::CommandFailed(format!("Failed to copy any files from '{}' to '{}'", src, dest)))
|
||||||
"Failed to copy any files from '{}' to '{}'",
|
|
||||||
src, dest
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle non-wildcard paths normally
|
// Handle non-wildcard paths normally
|
||||||
@ -224,12 +200,7 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
let file_name = src_path.file_name().unwrap_or_default();
|
let file_name = src_path.file_name().unwrap_or_default();
|
||||||
let new_dest_path = dest_path.join(file_name);
|
let new_dest_path = dest_path.join(file_name);
|
||||||
fs::copy(src_path, new_dest_path).map_err(FsError::CopyFailed)?;
|
fs::copy(src_path, new_dest_path).map_err(FsError::CopyFailed)?;
|
||||||
Ok(format!(
|
Ok(format!("Successfully copied file '{}' to '{}/{}'", src, dest, file_name.to_string_lossy()))
|
||||||
"Successfully copied file '{}' to '{}/{}'",
|
|
||||||
src,
|
|
||||||
dest,
|
|
||||||
file_name.to_string_lossy()
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise copy file to the specified destination
|
// Otherwise copy file to the specified destination
|
||||||
fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
|
fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
|
||||||
@ -243,23 +214,19 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
.output();
|
.output();
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let output = Command::new("cp").args(&["-R", src, dest]).output();
|
let output = Command::new("cp")
|
||||||
|
.args(&["-R", src, dest])
|
||||||
|
.output();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
if out.status.success() {
|
if out.status.success() {
|
||||||
Ok(format!(
|
Ok(format!("Successfully copied directory '{}' to '{}'", src, dest))
|
||||||
"Successfully copied directory '{}' to '{}'",
|
|
||||||
src, dest
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&out.stderr);
|
let error = String::from_utf8_lossy(&out.stderr);
|
||||||
Err(FsError::CommandFailed(format!(
|
Err(FsError::CommandFailed(format!("Failed to copy directory: {}", error)))
|
||||||
"Failed to copy directory: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(e) => Err(FsError::CommandExecutionError(e)),
|
Err(e) => Err(FsError::CommandExecutionError(e)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -282,8 +249,6 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::exist;
|
|
||||||
*
|
|
||||||
* if exist("file.txt") {
|
* if exist("file.txt") {
|
||||||
* println!("File exists");
|
* println!("File exists");
|
||||||
* }
|
* }
|
||||||
@ -308,14 +273,9 @@ pub fn exist(path: &str) -> bool {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::find_file;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let file_path = find_file("/path/to/dir", "*.txt")?;
|
* let file_path = find_file("/path/to/dir", "*.txt")?;
|
||||||
* println!("Found file: {}", file_path);
|
* println!("Found file: {}", file_path);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
||||||
@ -341,10 +301,7 @@ pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
|||||||
_ => {
|
_ => {
|
||||||
// If multiple matches, just return the first one instead of erroring
|
// If multiple matches, just return the first one instead of erroring
|
||||||
// This makes wildcard searches more practical
|
// This makes wildcard searches more practical
|
||||||
println!(
|
println!("Note: Multiple files found matching '{}', returning first match", filename);
|
||||||
"Note: Multiple files found matching '{}', returning first match",
|
|
||||||
filename
|
|
||||||
);
|
|
||||||
Ok(files[0].to_string_lossy().to_string())
|
Ok(files[0].to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,16 +322,11 @@ pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::find_files;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let files = find_files("/path/to/dir", "*.txt")?;
|
* let files = find_files("/path/to/dir", "*.txt")?;
|
||||||
* for file in files {
|
* for file in files {
|
||||||
* println!("Found file: {}", file);
|
* println!("Found file: {}", file);
|
||||||
* }
|
* }
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
|
pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
|
||||||
@ -413,14 +365,9 @@ pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::find_dir;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let dir_path = find_dir("/path/to/parent", "sub*")?;
|
* let dir_path = find_dir("/path/to/parent", "sub*")?;
|
||||||
* println!("Found directory: {}", dir_path);
|
* println!("Found directory: {}", dir_path);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
||||||
@ -443,10 +390,7 @@ pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
|||||||
match dirs.len() {
|
match dirs.len() {
|
||||||
0 => Err(FsError::DirectoryNotFound(dirname.to_string())),
|
0 => Err(FsError::DirectoryNotFound(dirname.to_string())),
|
||||||
1 => Ok(dirs[0].to_string_lossy().to_string()),
|
1 => Ok(dirs[0].to_string_lossy().to_string()),
|
||||||
_ => Err(FsError::CommandFailed(format!(
|
_ => Err(FsError::CommandFailed(format!("Multiple directories found matching '{}', expected only one", dirname))),
|
||||||
"Multiple directories found matching '{}', expected only one",
|
|
||||||
dirname
|
|
||||||
))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,16 +409,11 @@ pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::find_dirs;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let dirs = find_dirs("/path/to/parent", "sub*")?;
|
* let dirs = find_dirs("/path/to/parent", "sub*")?;
|
||||||
* for dir in dirs {
|
* for dir in dirs {
|
||||||
* println!("Found directory: {}", dir);
|
* println!("Found directory: {}", dir);
|
||||||
* }
|
* }
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
|
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
|
||||||
@ -513,17 +452,11 @@ pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::delete;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Delete a file
|
* // Delete a file
|
||||||
* let result = delete("file.txt")?;
|
* let result = delete("file.txt")?;
|
||||||
*
|
*
|
||||||
* // Delete a directory and all its contents
|
* // Delete a directory and all its contents
|
||||||
* let result = delete("directory/")?;
|
* let result = delete("directory/")?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn delete(path: &str) -> Result<String, FsError> {
|
pub fn delete(path: &str) -> Result<String, FsError> {
|
||||||
@ -561,13 +494,8 @@ pub fn delete(path: &str) -> Result<String, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::mkdir;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = mkdir("path/to/new/directory")?;
|
* let result = mkdir("path/to/new/directory")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn mkdir(path: &str) -> Result<String, FsError> {
|
pub fn mkdir(path: &str) -> Result<String, FsError> {
|
||||||
@ -601,14 +529,9 @@ pub fn mkdir(path: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::file_size;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let size = file_size("file.txt")?;
|
* let size = file_size("file.txt")?;
|
||||||
* println!("File size: {} bytes", size);
|
* println!("File size: {} bytes", size);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn file_size(path: &str) -> Result<i64, FsError> {
|
pub fn file_size(path: &str) -> Result<i64, FsError> {
|
||||||
@ -644,14 +567,9 @@ pub fn file_size(path: &str) -> Result<i64, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::rsync;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = rsync("source_dir/", "backup_dir/")?;
|
* let result = rsync("source_dir/", "backup_dir/")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
||||||
@ -681,17 +599,13 @@ pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
if out.status.success() || out.status.code() == Some(1) {
|
if out.status.success() || out.status.code() == Some(1) { // rsync and robocopy return 1 for some non-error cases
|
||||||
// rsync and robocopy return 1 for some non-error cases
|
|
||||||
Ok(format!("Successfully synced '{}' to '{}'", src, dest))
|
Ok(format!("Successfully synced '{}' to '{}'", src, dest))
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&out.stderr);
|
let error = String::from_utf8_lossy(&out.stderr);
|
||||||
Err(FsError::CommandFailed(format!(
|
Err(FsError::CommandFailed(format!("Failed to sync directories: {}", error)))
|
||||||
"Failed to sync directories: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(e) => Err(FsError::CommandExecutionError(e)),
|
Err(e) => Err(FsError::CommandExecutionError(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -710,14 +624,9 @@ pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::chdir;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = chdir("/path/to/directory")?;
|
* let result = chdir("/path/to/directory")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn chdir(path: &str) -> Result<String, FsError> {
|
pub fn chdir(path: &str) -> Result<String, FsError> {
|
||||||
@ -753,14 +662,9 @@ pub fn chdir(path: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::file_read;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let content = file_read("file.txt")?;
|
* let content = file_read("file.txt")?;
|
||||||
* println!("File content: {}", content);
|
* println!("File content: {}", content);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn file_read(path: &str) -> Result<String, FsError> {
|
pub fn file_read(path: &str) -> Result<String, FsError> {
|
||||||
@ -796,13 +700,8 @@ pub fn file_read(path: &str) -> Result<String, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::file_write;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = file_write("file.txt", "Hello, world!")?;
|
* let result = file_write("file.txt", "Hello, world!")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
|
pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
|
||||||
@ -835,13 +734,8 @@ pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::file_write_append;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = file_write_append("log.txt", "New log entry\n")?;
|
* let result = file_write_append("log.txt", "New log entry\n")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
||||||
@ -861,8 +755,7 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
|||||||
|
|
||||||
// Append content to file
|
// Append content to file
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
file.write_all(content.as_bytes())
|
file.write_all(content.as_bytes()).map_err(FsError::AppendFailed)?;
|
||||||
.map_err(FsError::AppendFailed)?;
|
|
||||||
|
|
||||||
Ok(format!("Successfully appended to file '{}'", path))
|
Ok(format!("Successfully appended to file '{}'", path))
|
||||||
}
|
}
|
||||||
@ -882,10 +775,7 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::os::mv;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Move a file
|
* // Move a file
|
||||||
* let result = mv("file.txt", "new_location/file.txt")?;
|
* let result = mv("file.txt", "new_location/file.txt")?;
|
||||||
*
|
*
|
||||||
@ -894,9 +784,6 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
|||||||
*
|
*
|
||||||
* // Rename a file
|
* // Rename a file
|
||||||
* let result = mv("old_name.txt", "new_name.txt")?;
|
* let result = mv("old_name.txt", "new_name.txt")?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||||
@ -939,7 +826,7 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
return FsError::DeleteFailed(del_err);
|
return FsError::DeleteFailed(del_err);
|
||||||
}
|
}
|
||||||
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||||
}
|
},
|
||||||
Err(copy_err) => return FsError::CopyFailed(copy_err),
|
Err(copy_err) => return FsError::CopyFailed(copy_err),
|
||||||
}
|
}
|
||||||
} else if src_path.is_dir() {
|
} else if src_path.is_dir() {
|
||||||
@ -950,7 +837,9 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
.status();
|
.status();
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let output = Command::new("cp").args(&["-R", src, dest]).status();
|
let output = Command::new("cp")
|
||||||
|
.args(&["-R", src, dest])
|
||||||
|
.status();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
@ -961,11 +850,9 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
}
|
}
|
||||||
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||||
} else {
|
} else {
|
||||||
return FsError::CommandFailed(
|
return FsError::CommandFailed("Failed to copy directory for move operation".to_string());
|
||||||
"Failed to copy directory for move operation".to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Err(cmd_err) => return FsError::CommandExecutionError(cmd_err),
|
Err(cmd_err) => return FsError::CommandExecutionError(cmd_err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -977,10 +864,7 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
if src_path.is_file() {
|
if src_path.is_file() {
|
||||||
Ok(format!("Successfully moved file '{}' to '{}'", src, dest))
|
Ok(format!("Successfully moved file '{}' to '{}'", src, dest))
|
||||||
} else {
|
} else {
|
||||||
Ok(format!(
|
Ok(format!("Successfully moved directory '{}' to '{}'", src, dest))
|
||||||
"Successfully moved directory '{}' to '{}'",
|
|
||||||
src, dest
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -998,8 +882,6 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::which;
|
|
||||||
*
|
|
||||||
* let cmd_path = which("ls");
|
* let cmd_path = which("ls");
|
||||||
* if cmd_path != "" {
|
* if cmd_path != "" {
|
||||||
* println!("ls is available at: {}", cmd_path);
|
* println!("ls is available at: {}", cmd_path);
|
||||||
@ -1009,10 +891,14 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
pub fn which(command: &str) -> String {
|
pub fn which(command: &str) -> String {
|
||||||
// Use the appropriate command based on the platform
|
// Use the appropriate command based on the platform
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let output = Command::new("where").arg(command).output();
|
let output = Command::new("where")
|
||||||
|
.arg(command)
|
||||||
|
.output();
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let output = Command::new("which").arg(command).output();
|
let output = Command::new("which")
|
||||||
|
.arg(command)
|
||||||
|
.output();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
@ -1022,7 +908,7 @@ pub fn which(command: &str) -> String {
|
|||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(_) => String::new(),
|
Err(_) => String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1043,31 +929,22 @@ pub fn which(command: &str) -> String {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::os::cmd_ensure_exists;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* // Check if a single command exists
|
* // Check if a single command exists
|
||||||
* let result = cmd_ensure_exists("nerdctl")?;
|
* let result = cmd_ensure_exists("nerdctl")?;
|
||||||
*
|
*
|
||||||
* // Check if multiple commands exist
|
* // Check if multiple commands exist
|
||||||
* let result = cmd_ensure_exists("nerdctl,docker,containerd")?;
|
* let result = cmd_ensure_exists("nerdctl,docker,containerd")?;
|
||||||
*
|
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn cmd_ensure_exists(commands: &str) -> Result<String, FsError> {
|
pub fn cmd_ensure_exists(commands: &str) -> Result<String, FsError> {
|
||||||
// Split the input by commas to handle multiple commands
|
// Split the input by commas to handle multiple commands
|
||||||
let command_list: Vec<&str> = commands
|
let command_list: Vec<&str> = commands.split(',')
|
||||||
.split(',')
|
|
||||||
.map(|s| s.trim())
|
.map(|s| s.trim())
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if command_list.is_empty() {
|
if command_list.is_empty() {
|
||||||
return Err(FsError::CommandFailed(
|
return Err(FsError::CommandFailed("No commands specified to check".to_string()));
|
||||||
"No commands specified to check".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut missing_commands = Vec::new();
|
let mut missing_commands = Vec::new();
|
||||||
|
@ -794,7 +794,7 @@ pub fn query_opt_with_pool_params(
|
|||||||
/// This function sends a notification on the specified channel with the specified payload.
|
/// This function sends a notification on the specified channel with the specified payload.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```no_run
|
/// ```
|
||||||
/// use sal::postgresclient::notify;
|
/// use sal::postgresclient::notify;
|
||||||
///
|
///
|
||||||
/// notify("my_channel", "Hello, world!").expect("Failed to send notification");
|
/// notify("my_channel", "Hello, world!").expect("Failed to send notification");
|
||||||
@ -810,7 +810,7 @@ pub fn notify(channel: &str, payload: &str) -> Result<(), PostgresError> {
|
|||||||
/// This function sends a notification on the specified channel with the specified payload using the connection pool.
|
/// This function sends a notification on the specified channel with the specified payload using the connection pool.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```no_run
|
/// ```
|
||||||
/// use sal::postgresclient::notify_with_pool;
|
/// use sal::postgresclient::notify_with_pool;
|
||||||
///
|
///
|
||||||
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
|
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
/// Error type for process management operations
|
/// Error type for process management operations
|
||||||
///
|
///
|
||||||
@ -23,18 +23,11 @@ pub enum ProcessError {
|
|||||||
impl fmt::Display for ProcessError {
|
impl fmt::Display for ProcessError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ProcessError::CommandExecutionFailed(e) => {
|
ProcessError::CommandExecutionFailed(e) => write!(f, "Failed to execute command: {}", e),
|
||||||
write!(f, "Failed to execute command: {}", e)
|
|
||||||
}
|
|
||||||
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
||||||
ProcessError::NoProcessFound(pattern) => {
|
ProcessError::NoProcessFound(pattern) => write!(f, "No processes found matching '{}'", pattern),
|
||||||
write!(f, "No processes found matching '{}'", pattern)
|
ProcessError::MultipleProcessesFound(pattern, count) =>
|
||||||
}
|
write!(f, "Multiple processes ({}) found matching '{}'", count, pattern),
|
||||||
ProcessError::MultipleProcessesFound(pattern, count) => write!(
|
|
||||||
f,
|
|
||||||
"Multiple processes ({}) found matching '{}'",
|
|
||||||
count, pattern
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,8 +65,6 @@ pub struct ProcessInfo {
|
|||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* use sal::process::which;
|
|
||||||
*
|
|
||||||
* match which("git") {
|
* match which("git") {
|
||||||
* Some(path) => println!("Git is installed at: {}", path),
|
* Some(path) => println!("Git is installed at: {}", path),
|
||||||
* None => println!("Git is not installed"),
|
* None => println!("Git is not installed"),
|
||||||
@ -87,7 +78,9 @@ pub fn which(cmd: &str) -> Option<String> {
|
|||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
let which_cmd = "which";
|
let which_cmd = "which";
|
||||||
|
|
||||||
let output = Command::new(which_cmd).arg(cmd).output();
|
let output = Command::new(which_cmd)
|
||||||
|
.arg(cmd)
|
||||||
|
.output();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
@ -97,8 +90,8 @@ pub fn which(cmd: &str) -> Option<String> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(_) => None,
|
Err(_) => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,13 +111,8 @@ pub fn which(cmd: &str) -> Option<String> {
|
|||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* // Kill all processes with "server" in their name
|
* // Kill all processes with "server" in their name
|
||||||
* use sal::process::kill;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let result = kill("server")?;
|
* let result = kill("server")?;
|
||||||
* println!("{}", result);
|
* println!("{}", result);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||||
@ -156,16 +144,10 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
if stdout.contains("No tasks") {
|
if stdout.contains("No tasks") {
|
||||||
Ok("No matching processes found".to_string())
|
Ok("No matching processes found".to_string())
|
||||||
} else {
|
} else {
|
||||||
Err(ProcessError::CommandFailed(format!(
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", stdout)))
|
||||||
"Failed to kill processes: {}",
|
|
||||||
stdout
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ProcessError::CommandFailed(format!(
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
||||||
"Failed to kill processes: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,10 +168,7 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
Ok("No matching processes found".to_string())
|
Ok("No matching processes found".to_string())
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(ProcessError::CommandFailed(format!(
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
||||||
"Failed to kill processes: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,9 +189,6 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* // List all processes
|
* // List all processes
|
||||||
* use sal::process::process_list;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let processes = process_list("")?;
|
* let processes = process_list("")?;
|
||||||
*
|
*
|
||||||
* // List processes with "server" in their name
|
* // List processes with "server" in their name
|
||||||
@ -220,8 +196,6 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
* for proc in processes {
|
* for proc in processes {
|
||||||
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
||||||
* }
|
* }
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||||
@ -240,8 +214,7 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
||||||
// Parse output (assuming format: Handle Name Priority)
|
// Parse output (assuming format: Handle Name Priority)
|
||||||
for line in stdout.lines().skip(1) {
|
for line in stdout.lines().skip(1) { // Skip header
|
||||||
// Skip header
|
|
||||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||||
@ -262,10 +235,7 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
return Err(ProcessError::CommandFailed(format!(
|
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
||||||
"Failed to list processes: {}",
|
|
||||||
stderr
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,8 +251,7 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
||||||
// Parse output (assuming format: PID COMMAND)
|
// Parse output (assuming format: PID COMMAND)
|
||||||
for line in stdout.lines().skip(1) {
|
for line in stdout.lines().skip(1) { // Skip header
|
||||||
// Skip header
|
|
||||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||||
@ -303,10 +272,7 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
return Err(ProcessError::CommandFailed(format!(
|
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
||||||
"Failed to list processes: {}",
|
|
||||||
stderr
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,14 +293,9 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```no_run
|
* ```
|
||||||
* use sal::process::process_get;
|
|
||||||
*
|
|
||||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
* let process = process_get("unique-server-name")?;
|
* let process = process_get("unique-server-name")?;
|
||||||
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
||||||
* Ok(())
|
|
||||||
* }
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
||||||
@ -343,9 +304,6 @@ pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
|||||||
match processes.len() {
|
match processes.len() {
|
||||||
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
||||||
1 => Ok(processes[0].clone()),
|
1 => Ok(processes[0].clone()),
|
||||||
_ => Err(ProcessError::MultipleProcessesFound(
|
_ => Err(ProcessError::MultipleProcessesFound(pattern.to_string(), processes.len())),
|
||||||
pattern.to_string(),
|
|
||||||
processes.len(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,8 @@ mod postgresclient;
|
|||||||
mod process;
|
mod process;
|
||||||
mod redisclient;
|
mod redisclient;
|
||||||
mod rfs;
|
mod rfs;
|
||||||
mod vault;
|
mod hero_vault; // This module now uses hero_vault internally
|
||||||
mod text;
|
mod text;
|
||||||
mod zinit;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@ -92,9 +91,6 @@ pub use rfs::register as register_rfs_module;
|
|||||||
pub use crate::git::{GitRepo, GitTree};
|
pub use crate::git::{GitRepo, GitTree};
|
||||||
pub use git::register_git_module;
|
pub use git::register_git_module;
|
||||||
|
|
||||||
// Re-export zinit module
|
|
||||||
pub use zinit::register_zinit_module;
|
|
||||||
|
|
||||||
// Re-export text module
|
// Re-export text module
|
||||||
pub use text::register_text_module;
|
pub use text::register_text_module;
|
||||||
// Re-export text functions directly from text module
|
// Re-export text functions directly from text module
|
||||||
@ -111,7 +107,7 @@ pub use crate::text::{
|
|||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
// Re-export crypto module
|
// Re-export crypto module
|
||||||
pub use vault::register_crypto_module;
|
pub use hero_vault::register_crypto_module;
|
||||||
|
|
||||||
// Rename copy functions to avoid conflicts
|
// Rename copy functions to avoid conflicts
|
||||||
pub use os::copy as os_copy;
|
pub use os::copy as os_copy;
|
||||||
@ -124,7 +120,7 @@ pub use os::copy as os_copy;
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
/// use sal::rhai;
|
/// use sal::rhai;
|
||||||
///
|
///
|
||||||
@ -132,8 +128,7 @@ pub use os::copy as os_copy;
|
|||||||
/// rhai::register(&mut engine);
|
/// rhai::register(&mut engine);
|
||||||
///
|
///
|
||||||
/// // Now you can use SAL functions in Rhai scripts
|
/// // Now you can use SAL functions in Rhai scripts
|
||||||
/// // You can evaluate Rhai scripts with SAL functions
|
/// let result = engine.eval::<bool>("exist('some_file.txt')").unwrap();
|
||||||
/// let result = engine.eval::<i64>("exist('some_file.txt')").unwrap();
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
// Register OS module functions
|
// Register OS module functions
|
||||||
@ -151,10 +146,6 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
// Register Git module functions
|
// Register Git module functions
|
||||||
git::register_git_module(engine)?;
|
git::register_git_module(engine)?;
|
||||||
|
|
||||||
|
|
||||||
// Register Zinit module functions
|
|
||||||
zinit::register_zinit_module(engine)?;
|
|
||||||
|
|
||||||
// Register Text module functions
|
// Register Text module functions
|
||||||
text::register_text_module(engine)?;
|
text::register_text_module(engine)?;
|
||||||
|
|
||||||
@ -162,7 +153,7 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
rfs::register(engine)?;
|
rfs::register(engine)?;
|
||||||
|
|
||||||
// Register Crypto module functions
|
// Register Crypto module functions
|
||||||
vault::register_crypto_module(engine)?;
|
hero_vault::register_crypto_module(engine)?;
|
||||||
|
|
||||||
|
|
||||||
// Register Redis client module functions
|
// Register Redis client module functions
|
||||||
|
@ -1,345 +0,0 @@
|
|||||||
//! Rhai wrappers for Zinit client module functions
|
|
||||||
//!
|
|
||||||
//! This module provides Rhai wrappers for the functions in the Zinit client module.
|
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
|
||||||
use crate::zinit_client as client;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use crate::rhai::error::ToRhaiError;
|
|
||||||
|
|
||||||
/// Register Zinit module functions with the Rhai engine
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `engine` - The Rhai engine to register the functions with
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
|
||||||
pub fn register_zinit_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
|
||||||
// Register Zinit client functions
|
|
||||||
engine.register_fn("zinit_list", zinit_list);
|
|
||||||
engine.register_fn("zinit_status", zinit_status);
|
|
||||||
engine.register_fn("zinit_start", zinit_start);
|
|
||||||
engine.register_fn("zinit_stop", zinit_stop);
|
|
||||||
engine.register_fn("zinit_restart", zinit_restart);
|
|
||||||
engine.register_fn("zinit_monitor", zinit_monitor);
|
|
||||||
engine.register_fn("zinit_forget", zinit_forget);
|
|
||||||
engine.register_fn("zinit_kill", zinit_kill);
|
|
||||||
engine.register_fn("zinit_create_service", zinit_create_service);
|
|
||||||
engine.register_fn("zinit_delete_service", zinit_delete_service);
|
|
||||||
engine.register_fn("zinit_get_service", zinit_get_service);
|
|
||||||
engine.register_fn("zinit_logs", zinit_logs);
|
|
||||||
engine.register_fn("zinit_logs_all", zinit_logs_all);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ToRhaiError<T> for Result<T, zinit_client::ClientError> {
|
|
||||||
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
|
|
||||||
self.map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Zinit error: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get a runtime
|
|
||||||
fn get_runtime() -> Result<Runtime, Box<EvalAltResult>> {
|
|
||||||
tokio::runtime::Runtime::new().map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Failed to create Tokio runtime: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Zinit Client Function Wrappers
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::list
|
|
||||||
///
|
|
||||||
/// Lists all services managed by Zinit.
|
|
||||||
pub fn zinit_list(socket_path: &str) -> Result<Map, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::list(socket_path).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let services = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert HashMap<String, String> to Rhai Map
|
|
||||||
let mut map = Map::new();
|
|
||||||
for (name, state) in services {
|
|
||||||
map.insert(name.into(), Dynamic::from(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::status
|
|
||||||
///
|
|
||||||
/// Gets the status of a specific service.
|
|
||||||
pub fn zinit_status(socket_path: &str, name: &str) -> Result<Map, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::status(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let status = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Status to Rhai Map
|
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert("name".into(), Dynamic::from(status.name));
|
|
||||||
map.insert("pid".into(), Dynamic::from(status.pid));
|
|
||||||
map.insert("state".into(), Dynamic::from(status.state));
|
|
||||||
map.insert("target".into(), Dynamic::from(status.target));
|
|
||||||
|
|
||||||
// Convert dependencies
|
|
||||||
let mut deps_map = Map::new();
|
|
||||||
for (dep, state) in status.after {
|
|
||||||
deps_map.insert(dep.into(), Dynamic::from(state));
|
|
||||||
}
|
|
||||||
map.insert("after".into(), Dynamic::from_map(deps_map));
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::start
|
|
||||||
///
|
|
||||||
/// Starts a service.
|
|
||||||
pub fn zinit_start(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::start(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::stop
|
|
||||||
///
|
|
||||||
/// Stops a service.
|
|
||||||
pub fn zinit_stop(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::stop(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::restart
|
|
||||||
///
|
|
||||||
/// Restarts a service.
|
|
||||||
pub fn zinit_restart(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::restart(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::monitor
|
|
||||||
///
|
|
||||||
/// Starts monitoring a service.
|
|
||||||
pub fn zinit_monitor(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.monitor(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::forget
|
|
||||||
///
|
|
||||||
/// Stops monitoring a service.
|
|
||||||
pub fn zinit_forget(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.forget(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::kill
|
|
||||||
///
|
|
||||||
/// Sends a signal to a service.
|
|
||||||
pub fn zinit_kill(socket_path: &str, name: &str, signal: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.kill(name, signal).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::create_service
|
|
||||||
///
|
|
||||||
/// Creates a new service.
|
|
||||||
pub fn zinit_create_service(socket_path: &str, name: &str, exec: &str, oneshot: bool) -> Result<String, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
// Create service configuration
|
|
||||||
let content = serde_json::from_value(json!({
|
|
||||||
"exec": exec,
|
|
||||||
"oneshot": oneshot
|
|
||||||
})).map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Failed to create service configuration: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.create_service(name, content).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::delete_service
|
|
||||||
///
|
|
||||||
/// Deletes a service.
|
|
||||||
pub fn zinit_delete_service(socket_path: &str, name: &str) -> Result<String, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.delete_service(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::get_service
|
|
||||||
///
|
|
||||||
/// Gets a service configuration.
|
|
||||||
pub fn zinit_get_service(socket_path: &str, name: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.get_service(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let value = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Value to Dynamic
|
|
||||||
match value {
|
|
||||||
Value::Object(map) => {
|
|
||||||
let mut rhai_map = Map::new();
|
|
||||||
for (k, v) in map {
|
|
||||||
rhai_map.insert(k.into(), value_to_dynamic(v));
|
|
||||||
}
|
|
||||||
Ok(Dynamic::from_map(rhai_map))
|
|
||||||
},
|
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"Expected object from get_service".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::logs with a filter
|
|
||||||
///
|
|
||||||
/// Gets logs for a specific service.
|
|
||||||
pub fn zinit_logs(socket_path: &str, filter: &str) -> Result<Array, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let filter_string = Some(filter.to_string());
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.logs(filter_string).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let logs = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for log in logs {
|
|
||||||
array.push(Dynamic::from(log));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::logs without a filter
|
|
||||||
///
|
|
||||||
/// Gets all logs.
|
|
||||||
pub fn zinit_logs_all(socket_path: &str) -> Result<Array, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.logs(None).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let logs = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for log in logs {
|
|
||||||
array.push(Dynamic::from(log));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to convert serde_json::Value to rhai::Dynamic
|
|
||||||
fn value_to_dynamic(value: Value) -> Dynamic {
|
|
||||||
match value {
|
|
||||||
Value::Null => Dynamic::UNIT,
|
|
||||||
Value::Bool(b) => Dynamic::from(b),
|
|
||||||
Value::Number(n) => {
|
|
||||||
if let Some(i) = n.as_i64() {
|
|
||||||
Dynamic::from(i)
|
|
||||||
} else if let Some(f) = n.as_f64() {
|
|
||||||
Dynamic::from(f)
|
|
||||||
} else {
|
|
||||||
Dynamic::from(n.to_string())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Value::String(s) => Dynamic::from(s),
|
|
||||||
Value::Array(arr) => {
|
|
||||||
let mut rhai_arr = Array::new();
|
|
||||||
for item in arr {
|
|
||||||
rhai_arr.push(value_to_dynamic(item));
|
|
||||||
}
|
|
||||||
Dynamic::from(rhai_arr)
|
|
||||||
},
|
|
||||||
Value::Object(map) => {
|
|
||||||
let mut rhai_map = Map::new();
|
|
||||||
for (k, v) in map {
|
|
||||||
rhai_map.insert(k.into(), value_to_dynamic(v));
|
|
||||||
}
|
|
||||||
Dynamic::from_map(rhai_map)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user