Software Development Kit
Getting Started
saasexpress operator init [op-name]This will create a Rust project structure like the one below.
saasexpress_[op-name]/
├── Cargo.toml # Project configuration, dependencies, metadata
├── Cargo.lock # Lock file for deterministic builds (auto-generated)
├── src/ # Source code directory
│ ├── main.rs # Entry point for binary projects
│ ├── lib.rs # Entry point for library projects
│ └── operators/ # Utility functions
│ ├── [op-name].rs # Module declaration file
└── .gitignore # Git ignore file
Graph “Service Activator”
Data Model
pub struct Graph {
pub name: String,
pub start_node: String,
/// Collection of nodes in the Graph, indexed by their unique ID
pub nodes: HashMap<String, Arc<Mutex<dyn Operator + 'static>>>,
/// Mapping of node IDs to their outgoing edges (children)
edges: HashMap<String, HashSet<String>>,
pub processor: Option<Arc<Mutex<BasicProcessor>>>,
ports: Ports,
}Interfaces
pub trait Graph {
pub fn init(&mut self) -> &mut Self;
pub fn no_processor(&mut self) -> &mut Self;
pub fn init_ports(&mut self);
pub fn add_edge(&mut self, from: String, to: String) -> &mut Self;
pub fn add_node<O>(&mut self, id: &str, mut operator: O) -> &mut Self;
}
pub trait GraphRun {
async fn end_to_end(&mut self, message: Vec<u8>) -> Message;
async fn end_to_end_2(&mut self, message: Vec<u8>) -> Message;
async fn process(&mut self, message: Message) -> Message;
}lifecycle
new: New graphadd_node_to_graph: Adds new node Operators and callsnode.init()add_edgescontrol(Message::Init{}): Call Init for each Operatorfinalize: Called when all graphs are registered
Operator
Data Model
Depends on the Operator.
Interfaces
fn _type(&self) -> OperatorType
Specify the type of operator. Valid values are:
Endpoint: Used when we want to have full control of the messaging flowFilter: Has a convenience handler for automatically passing messages onto the next node
fn name(&self) -> String
Sets the name of the Operator.
fn handle(&self, message: Message) -> Message
The message handler. For a full list of message types see: concepts/messages.
The message returned is forwarded to the child nodes. If there are no child nodes, then it is forwarded to the origin.respond_to or origin.mpsc_respond_to channels.
fn control(&mut self, message: Message)
Handles commands that manage the lifecycle of the Operator for the particular node.
In particular the MessageType::Init is used to allow the Operator to setup the communication channels with its edge nodes.
fn init(&mut self, graph: &mut Graph)
Used for initializing the Operator for the Node when the Operator needs the full Graph to complete initialization.
Full interface
pub trait Operator: Send + Sync + Debug {
fn _type(&self) -> OperatorType;
fn name(&self) -> String;
//fn meta(&self) -> NodeMeta;
fn handle(&self, message: Message) -> Message;
fn control(&mut self, message: Message);
fn send(&self, message: Message);
fn get(&self) -> Option<Arc<dyn AsyncHandleTrait>>;
fn wait(&self) -> Message;
fn init(&mut self, graph: &mut Graph);
fn send_ptr(&self, _message: Arc<Message>) {
let message = _message.to_owned();
self.next_ptr(self.handle_ptr(message));
}
fn handle_ptr(&self, message: Arc<Message>) -> Arc<Message> {
debug!("default handle (passthrough)... {}", self.name());
return message;
}
fn next_ptr(&self, message: Arc<Message>) {
// Sending message to next operator
for n in self.get_output_channels() {
n.lock().unwrap().send_ptr(message.to_owned());
//break;
}
}
fn get_output_channels(&self) -> &Vec<Arc<Mutex<dyn Operator>>>;
}