Files
unshell/examples/protocol/leaf_derive.rs
T

108 lines
3.2 KiB
Rust
Raw Normal View History

//! Small end-to-end example for the `Leaf` and `procedures` derive macros.
//!
//! This stays entirely local. A controller endpoint opens one call against a single in-process
//! leaf runtime, and the example decodes the returned reply payload.
2026-04-25 14:46:59 -06:00
use std::error::Error;
2026-04-25 15:35:08 -06:00
use std::{convert::Infallible, string::String};
2026-04-25 14:46:59 -06:00
2026-04-25 15:35:08 -06:00
use rkyv::{Archive, Deserialize, Serialize};
2026-04-25 20:47:37 -06:00
use unshell::protocol::tree::{
Call, CallLeaf, ChildRoute, EndpointOutcome, Ingress, LeafRuntime, ProtocolEndpoint,
};
2026-04-25 15:35:08 -06:00
use unshell::protocol::{PacketType, decode_frame};
use unshell::{Leaf, procedures};
2026-04-25 14:46:59 -06:00
#[derive(Leaf)]
#[leaf(org = "org", product = "example", version = "v1", leaf_name = "echo")]
2026-04-25 15:35:08 -06:00
struct EchoLeaf {
prefix: String,
}
#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
struct EchoRequest {
text: String,
}
#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
struct EchoResponse {
text: String,
}
#[procedures(error = Infallible)]
impl EchoLeaf {
#[call]
fn echo(&mut self, request: Call<EchoRequest>) -> EchoResponse {
EchoResponse {
text: format!("{}{}", self.prefix, request.input.text),
}
}
}
impl CallLeaf for EchoLeaf {
type Error = Infallible;
}
2026-04-25 14:46:59 -06:00
fn path(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}
fn main() -> Result<(), Box<dyn Error>> {
2026-04-25 15:35:08 -06:00
let endpoint = ProtocolEndpoint::new(
2026-04-25 14:46:59 -06:00
path(&["agent"]),
Some(Vec::new()),
Vec::new(),
vec![EchoLeaf::protocol_leaf_spec()],
);
2026-04-25 15:35:08 -06:00
let mut runtime = LeafRuntime::new(
endpoint,
EchoLeaf {
prefix: String::from("echo: "),
},
);
2026-04-25 14:46:59 -06:00
2026-04-25 15:35:08 -06:00
let mut controller = ProtocolEndpoint::new(
Vec::new(),
None,
vec![ChildRoute {
path: path(&["agent"]),
2026-04-25 20:47:37 -06:00
registered: true,
2026-04-25 15:35:08 -06:00
}],
Vec::new(),
);
let hook_id = controller.allocate_hook_id();
let controller_outcome = controller.send_call(
2026-04-25 14:46:59 -06:00
path(&["agent"]),
Some(EchoLeaf::protocol_leaf_name()),
2026-04-25 15:35:08 -06:00
EchoLeaf::protocol_procedure_id("echo").expect("known procedure suffix"),
2026-04-25 14:46:59 -06:00
Some(hook_id),
2026-04-25 15:35:08 -06:00
unshell::protocol::tree::encode_call_reply(&EchoRequest {
text: String::from("hello leaf"),
})?,
2026-04-25 14:46:59 -06:00
)?;
2026-04-25 20:47:37 -06:00
let EndpointOutcome::Forward { frame, .. } = controller_outcome else {
2026-04-25 15:35:08 -06:00
return Err("expected controller to forward call".into());
};
2026-04-25 14:46:59 -06:00
2026-04-25 15:35:08 -06:00
let outcome = runtime.receive(&Ingress::Parent, frame)?;
let [response_frame] = outcome.frames.as_slice() else {
return Err("expected one response frame".into());
2026-04-25 14:46:59 -06:00
};
2026-04-25 15:35:08 -06:00
let parsed = decode_frame(response_frame.as_slice())?;
assert_eq!(parsed.packet_type(), PacketType::Data);
let response = unshell::protocol::tree::decode_call_input::<EchoResponse>(
parsed.deserialize_data()?.data.as_slice(),
)?;
2026-04-25 14:46:59 -06:00
2026-04-25 15:35:08 -06:00
assert_eq!(EchoLeaf::protocol_leaf_name(), "org.example.v1.echo");
assert_eq!(response.text, "echo: hello leaf");
2026-04-25 14:46:59 -06:00
println!(
2026-04-25 15:35:08 -06:00
"leaf={} procedure={} response={}",
2026-04-25 14:46:59 -06:00
EchoLeaf::protocol_leaf_name(),
2026-04-25 15:35:08 -06:00
EchoLeaf::protocol_procedure_id("echo").expect("known procedure suffix"),
response.text,
2026-04-25 14:46:59 -06:00
);
Ok(())
}