Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
233
vendor/ruvector/crates/agentic-robotics-node/src/lib.rs
vendored
Normal file
233
vendor/ruvector/crates/agentic-robotics-node/src/lib.rs
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
//! Agentic Robotics Node.js Bindings
|
||||
//!
|
||||
//! NAPI bindings for Node.js/TypeScript integration with agentic-robotics-core
|
||||
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use agentic_robotics_core::{Publisher, Subscriber};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// Node for creating publishers and subscribers
|
||||
#[napi]
|
||||
pub struct AgenticNode {
|
||||
name: String,
|
||||
publishers: Arc<RwLock<HashMap<String, Arc<Publisher<JsonValue>>>>>,
|
||||
subscribers: Arc<RwLock<HashMap<String, Arc<Subscriber<JsonValue>>>>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticNode {
|
||||
/// Create a new node
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String) -> Result<Self> {
|
||||
Ok(Self {
|
||||
name,
|
||||
publishers: Arc::new(RwLock::new(HashMap::new())),
|
||||
subscribers: Arc::new(RwLock::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get node name
|
||||
#[napi]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
/// Create a publisher for a topic
|
||||
#[napi]
|
||||
pub async fn create_publisher(&self, topic: String) -> Result<AgenticPublisher> {
|
||||
// Use JSON format for serde_json::Value to avoid CDR serialization issues
|
||||
let publisher = Arc::new(Publisher::<JsonValue>::with_format(
|
||||
topic.clone(),
|
||||
agentic_robotics_core::serialization::Format::Json,
|
||||
));
|
||||
|
||||
let mut publishers = self.publishers.write().await;
|
||||
publishers.insert(topic.clone(), publisher.clone());
|
||||
|
||||
Ok(AgenticPublisher {
|
||||
topic,
|
||||
inner: publisher,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a subscriber for a topic
|
||||
#[napi]
|
||||
pub async fn create_subscriber(&self, topic: String) -> Result<AgenticSubscriber> {
|
||||
let subscriber = Arc::new(Subscriber::<JsonValue>::new(topic.clone()));
|
||||
|
||||
let mut subscribers = self.subscribers.write().await;
|
||||
subscribers.insert(topic.clone(), subscriber.clone());
|
||||
|
||||
Ok(AgenticSubscriber {
|
||||
topic,
|
||||
inner: subscriber,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get library version
|
||||
#[napi]
|
||||
pub fn get_version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
/// List all active publishers
|
||||
#[napi]
|
||||
pub async fn list_publishers(&self) -> Vec<String> {
|
||||
let publishers = self.publishers.read().await;
|
||||
publishers.keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// List all active subscribers
|
||||
#[napi]
|
||||
pub async fn list_subscribers(&self) -> Vec<String> {
|
||||
let subscribers = self.subscribers.read().await;
|
||||
subscribers.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Publisher for sending messages to a topic
|
||||
#[napi]
|
||||
pub struct AgenticPublisher {
|
||||
topic: String,
|
||||
inner: Arc<Publisher<JsonValue>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticPublisher {
|
||||
/// Publish a message (JSON string or object)
|
||||
#[napi]
|
||||
pub async fn publish(&self, data: String) -> Result<()> {
|
||||
let value: JsonValue = serde_json::from_str(&data)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid JSON: {}", e)))?;
|
||||
|
||||
self.inner
|
||||
.publish(&value)
|
||||
.await
|
||||
.map_err(|e| Error::from_reason(format!("Publish failed: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get topic name
|
||||
#[napi]
|
||||
pub fn get_topic(&self) -> String {
|
||||
self.topic.clone()
|
||||
}
|
||||
|
||||
/// Get publisher statistics (messages sent, bytes sent)
|
||||
#[napi]
|
||||
pub fn get_stats(&self) -> PublisherStats {
|
||||
let (messages, bytes) = self.inner.stats();
|
||||
PublisherStats {
|
||||
messages: messages as i64,
|
||||
bytes: bytes as i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Publisher statistics
|
||||
#[napi(object)]
|
||||
pub struct PublisherStats {
|
||||
pub messages: i64,
|
||||
pub bytes: i64,
|
||||
}
|
||||
|
||||
/// Subscriber for receiving messages from a topic
|
||||
#[napi]
|
||||
pub struct AgenticSubscriber {
|
||||
topic: String,
|
||||
inner: Arc<Subscriber<JsonValue>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AgenticSubscriber {
|
||||
/// Get topic name
|
||||
#[napi]
|
||||
pub fn get_topic(&self) -> String {
|
||||
self.topic.clone()
|
||||
}
|
||||
|
||||
/// Try to receive a message immediately (non-blocking)
|
||||
#[napi]
|
||||
pub async fn try_recv(&self) -> Result<Option<String>> {
|
||||
match self.inner.try_recv() {
|
||||
Ok(Some(msg)) => {
|
||||
let json_str = serde_json::to_string(&msg)
|
||||
.map_err(|e| Error::from_reason(format!("Serialization failed: {}", e)))?;
|
||||
Ok(Some(json_str))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(Error::from_reason(format!("Receive failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a message (blocking until message arrives)
|
||||
#[napi]
|
||||
pub async fn recv(&self) -> Result<String> {
|
||||
let msg = self
|
||||
.inner
|
||||
.recv_async()
|
||||
.await
|
||||
.map_err(|e| Error::from_reason(format!("Receive failed: {}", e)))?;
|
||||
|
||||
let json_str = serde_json::to_string(&msg)
|
||||
.map_err(|e| Error::from_reason(format!("Serialization failed: {}", e)))?;
|
||||
|
||||
Ok(json_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_node_creation() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
assert_eq!(node.get_name(), "test_node");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_publisher() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let publisher = node.create_publisher("/test".to_string()).await.unwrap();
|
||||
assert_eq!(publisher.get_topic(), "/test");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_publish() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let publisher = node.create_publisher("/test".to_string()).await.unwrap();
|
||||
|
||||
let result = publisher.publish(r#"{"message": "hello"}"#.to_string()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let stats = publisher.get_stats();
|
||||
assert_eq!(stats.messages, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_subscriber() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
let subscriber = node.create_subscriber("/test".to_string()).await.unwrap();
|
||||
assert_eq!(subscriber.get_topic(), "/test");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_publishers() {
|
||||
let node = AgenticNode::new("test_node".to_string()).unwrap();
|
||||
node.create_publisher("/test1".to_string()).await.unwrap();
|
||||
node.create_publisher("/test2".to_string()).await.unwrap();
|
||||
|
||||
let publishers = node.list_publishers().await;
|
||||
assert_eq!(publishers.len(), 2);
|
||||
assert!(publishers.contains(&"/test1".to_string()));
|
||||
assert!(publishers.contains(&"/test2".to_string()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user