Merge pull request 'mycelium-rhai' (#9) from mycelium-rhai into main
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
Reviewed-on: herocode/sal#9
This commit is contained in:
@@ -48,6 +48,7 @@ pub mod text;
|
||||
pub mod virt;
|
||||
pub mod vault;
|
||||
pub mod zinit_client;
|
||||
pub mod mycelium;
|
||||
|
||||
// Version information
|
||||
/// Returns the version of the SAL library
|
||||
|
313
src/mycelium/mod.rs
Normal file
313
src/mycelium/mod.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
use base64::{
|
||||
engine::general_purpose,
|
||||
Engine as _,
|
||||
};
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Get information about the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The node information as a JSON value, or an error message
|
||||
pub async fn get_node_info(api_url: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/admin", api_url);
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// List all peers connected to the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The list of peers as a JSON value, or an error message
|
||||
pub async fn list_peers(api_url: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/admin/peers", api_url);
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Add a new peer to the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
/// * `peer_address` - The address of the peer to add
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The result of the operation as a JSON value, or an error message
|
||||
pub async fn add_peer(api_url: &str, peer_address: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/admin/peers", api_url);
|
||||
|
||||
let response = client
|
||||
.post(&url)
|
||||
.json(&serde_json::json!({
|
||||
"endpoint": peer_address
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::NO_CONTENT {
|
||||
// Successfully added, but no content to parse
|
||||
return Ok(serde_json::json!({"success": true}));
|
||||
}
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
// For other success statuses that might have a body
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Remove a peer from the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
/// * `peer_id` - The ID of the peer to remove
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The result of the operation as a JSON value, or an error message
|
||||
pub async fn remove_peer(api_url: &str, peer_id: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let peer_id_url_encoded = urlencoding::encode(peer_id);
|
||||
let url = format!("{}/api/v1/admin/peers/{}", api_url, peer_id_url_encoded);
|
||||
|
||||
let response = client
|
||||
.delete(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::NO_CONTENT {
|
||||
// Successfully removed, but no content to parse
|
||||
return Ok(serde_json::json!({"success": true}));
|
||||
}
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// List all selected routes in the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The list of selected routes as a JSON value, or an error message
|
||||
pub async fn list_selected_routes(api_url: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/admin/routes/selected", api_url);
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// List all fallback routes in the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The list of fallback routes as a JSON value, or an error message
|
||||
pub async fn list_fallback_routes(api_url: &str) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/admin/routes/fallback", api_url);
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Send a message to a destination via the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
/// * `destination` - The destination address
|
||||
/// * `topic` - The message topic
|
||||
/// * `message` - The message content
|
||||
/// * `reply_deadline` - The deadline in seconds; pass `-1` to indicate we do not want to wait on a reply
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The result of the operation as a JSON value, or an error message
|
||||
pub async fn send_message(
|
||||
api_url: &str,
|
||||
destination: &str,
|
||||
topic: &str,
|
||||
message: &str,
|
||||
reply_deadline: Option<Duration>, // This is passed in URL query
|
||||
) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/messages", api_url);
|
||||
|
||||
let mut request = client.post(&url);
|
||||
if let Some(deadline) = reply_deadline {
|
||||
request = request.query(&[("reply_timeout", deadline.as_secs())]);
|
||||
}
|
||||
|
||||
let response = request
|
||||
.json(&serde_json::json!({
|
||||
"dst": { "ip": destination },
|
||||
"topic": general_purpose::STANDARD.encode(topic),
|
||||
"payload": general_purpose::STANDARD.encode(message)
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Receive messages from a topic via the Mycelium node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `api_url` - The URL of the Mycelium API
|
||||
/// * `topic` - The message topic
|
||||
/// * `wait_deadline` - Time we wait for receiving a message
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Value, String>` - The received messages as a JSON value, or an error message
|
||||
pub async fn receive_messages(
|
||||
api_url: &str,
|
||||
topic: &str,
|
||||
wait_deadline: Option<Duration>,
|
||||
) -> Result<Value, String> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/v1/messages", api_url);
|
||||
|
||||
let mut request = client.get(&url);
|
||||
|
||||
if let Some(deadline) = wait_deadline {
|
||||
request = request.query(&[
|
||||
("topic", general_purpose::STANDARD.encode(topic)),
|
||||
("timeout", deadline.as_secs().to_string()),
|
||||
])
|
||||
} else {
|
||||
request = request.query(&[("topic", general_purpose::STANDARD.encode(topic))])
|
||||
};
|
||||
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("Request failed with status: {}", status));
|
||||
}
|
||||
|
||||
let result: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
@@ -15,6 +15,7 @@ mod rfs;
|
||||
mod vault;
|
||||
mod text;
|
||||
mod zinit;
|
||||
mod mycelium;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -95,6 +96,9 @@ pub use git::register_git_module;
|
||||
// Re-export zinit module
|
||||
pub use zinit::register_zinit_module;
|
||||
|
||||
// Re-export mycelium module
|
||||
pub use mycelium::register_mycelium_module;
|
||||
|
||||
// Re-export text module
|
||||
pub use text::register_text_module;
|
||||
// Re-export text functions directly from text module
|
||||
@@ -155,6 +159,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
// Register Zinit module functions
|
||||
zinit::register_zinit_module(engine)?;
|
||||
|
||||
// Register Mycelium module functions
|
||||
mycelium::register_mycelium_module(engine)?;
|
||||
|
||||
// Register Text module functions
|
||||
text::register_text_module(engine)?;
|
||||
|
||||
|
224
src/rhai/mycelium.rs
Normal file
224
src/rhai/mycelium.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
//! Rhai wrappers for Mycelium client module functions
|
||||
//!
|
||||
//! This module provides Rhai wrappers for the functions in the Mycelium client module.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
||||
use crate::mycelium as client;
|
||||
use tokio::runtime::Runtime;
|
||||
use serde_json::Value;
|
||||
use crate::rhai::error::ToRhaiError;
|
||||
|
||||
/// Register Mycelium 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_mycelium_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register Mycelium client functions
|
||||
engine.register_fn("mycelium_get_node_info", mycelium_get_node_info);
|
||||
engine.register_fn("mycelium_list_peers", mycelium_list_peers);
|
||||
engine.register_fn("mycelium_add_peer", mycelium_add_peer);
|
||||
engine.register_fn("mycelium_remove_peer", mycelium_remove_peer);
|
||||
engine.register_fn("mycelium_list_selected_routes", mycelium_list_selected_routes);
|
||||
engine.register_fn("mycelium_list_fallback_routes", mycelium_list_fallback_routes);
|
||||
engine.register_fn("mycelium_send_message", mycelium_send_message);
|
||||
engine.register_fn("mycelium_receive_messages", mycelium_receive_messages);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 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
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper trait to convert String errors to Rhai errors
|
||||
impl<T> ToRhaiError<T> for Result<T, String> {
|
||||
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
|
||||
self.map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
format!("Mycelium error: {}", e).into(),
|
||||
rhai::Position::NONE
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Mycelium Client Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for mycelium::get_node_info
|
||||
///
|
||||
/// Gets information about the Mycelium node.
|
||||
pub fn mycelium_get_node_info(api_url: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::get_node_info(api_url).await
|
||||
});
|
||||
|
||||
let node_info = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(node_info))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::list_peers
|
||||
///
|
||||
/// Lists all peers connected to the Mycelium node.
|
||||
pub fn mycelium_list_peers(api_url: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::list_peers(api_url).await
|
||||
});
|
||||
|
||||
let peers = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(peers))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::add_peer
|
||||
///
|
||||
/// Adds a new peer to the Mycelium node.
|
||||
pub fn mycelium_add_peer(api_url: &str, peer_address: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::add_peer(api_url, peer_address).await
|
||||
});
|
||||
|
||||
let response = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(response))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::remove_peer
|
||||
///
|
||||
/// Removes a peer from the Mycelium node.
|
||||
pub fn mycelium_remove_peer(api_url: &str, peer_id: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::remove_peer(api_url, peer_id).await
|
||||
});
|
||||
|
||||
let response = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(response))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::list_selected_routes
|
||||
///
|
||||
/// Lists all selected routes in the Mycelium node.
|
||||
pub fn mycelium_list_selected_routes(api_url: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::list_selected_routes(api_url).await
|
||||
});
|
||||
|
||||
let routes = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(routes))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::list_fallback_routes
|
||||
///
|
||||
/// Lists all fallback routes in the Mycelium node.
|
||||
pub fn mycelium_list_fallback_routes(api_url: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::list_fallback_routes(api_url).await
|
||||
});
|
||||
|
||||
let routes = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(routes))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::send_message
|
||||
///
|
||||
/// Sends a message to a destination via the Mycelium node.
|
||||
pub fn mycelium_send_message(api_url: &str, destination: &str, topic: &str, message: &str, reply_deadline_secs: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let deadline = if reply_deadline_secs < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(reply_deadline_secs as u64))
|
||||
};
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::send_message(api_url, destination, topic, message, deadline).await
|
||||
});
|
||||
|
||||
let response = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(response))
|
||||
}
|
||||
|
||||
/// Wrapper for mycelium::receive_messages
|
||||
///
|
||||
/// Receives messages from a topic via the Mycelium node.
|
||||
pub fn mycelium_receive_messages(api_url: &str, topic: &str, wait_deadline_secs: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let rt = get_runtime()?;
|
||||
|
||||
let deadline = if wait_deadline_secs < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(wait_deadline_secs as u64))
|
||||
};
|
||||
|
||||
let result = rt.block_on(async {
|
||||
client::receive_messages(api_url, topic, deadline).await
|
||||
});
|
||||
|
||||
let messages = result.to_rhai_error()?;
|
||||
|
||||
Ok(value_to_dynamic(messages))
|
||||
}
|
Reference in New Issue
Block a user