Remove the old leaf declaration path

Delete the deprecated Leaf derive path, migrate the remaining tests and example to leaf!, and add direct coverage for endpoint-only, TUI-only, and shared-host leaf declarations.
This commit is contained in:
Michael Mikovsky
2026-04-26 14:14:49 -06:00
parent 0aa0b187d7
commit 54c44b407e
17 changed files with 284 additions and 380 deletions
+7 -3
View File
@@ -8,18 +8,22 @@ use crate::protocol::tree::{
decode_call_input, encode_call_reply,
};
use crate::protocol::{PacketType, decode_frame};
use crate::{Leaf, procedures};
use crate::{leaf, procedures};
fn path(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}
#[derive(Leaf)]
#[leaf(id = "org.example.v1.echo")]
struct EchoLeaf {
prefix: String,
}
leaf! {
id = "org.example.v1.echo",
endpoint_struct = EchoLeaf,
procedures = ["echo"],
}
#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
struct EchoRequest {
text: String,
@@ -0,0 +1,98 @@
use alloc::{string::String, vec};
use crate::leaf;
use crate::protocol::tree::{LeafBinding, LeafDeclaration, ProcedureMetadata, ProtocolLeaf};
struct EndpointHost;
struct Open;
struct Reset;
impl ProcedureMetadata for Open {
type Leaf = EndpointHost;
const PROCEDURE_SUFFIX: &'static str = "open";
}
impl ProcedureMetadata for Reset {
type Leaf = EndpointHost;
const PROCEDURE_SUFFIX: &'static str = "reset";
}
leaf! {
id = "org.example.v1.demo",
procedures = [Open, Reset],
endpoint_struct = EndpointHost,
}
struct EndpointHalf;
struct TuiHalf;
struct Connect;
impl ProcedureMetadata for Connect {
type Leaf = EndpointHalf;
const PROCEDURE_SUFFIX: &'static str = "connect";
}
leaf! {
name = "chat",
org = "org",
product = "example",
version = "v2",
procedures = [Connect],
endpoint_struct = EndpointHalf,
tui_struct = TuiHalf,
}
struct TuiOnly;
struct Tail;
impl ProcedureMetadata for Tail {
type Leaf = TuiOnly;
const PROCEDURE_SUFFIX: &'static str = "tail";
}
leaf! {
id = "org.example.v1.transcript",
procedures = [Tail],
tui_struct = TuiOnly,
}
#[test]
fn leaf_declaration_generates_endpoint_host_metadata() {
assert_eq!(EndpointHost::protocol_leaf_name(), "org.example.v1.demo");
assert_eq!(
EndpointHost::protocol_leaf_spec().procedures,
vec![
String::from("org.example.v1.demo.open"),
String::from("org.example.v1.demo.reset"),
]
);
assert_eq!(
<EndpointHost as LeafBinding>::Declaration::leaf_name(),
"org.example.v1.demo"
);
}
#[test]
fn leaf_declaration_shares_metadata_between_endpoint_and_tui_hosts() {
assert_eq!(
EndpointHalf::protocol_leaf_name(),
TuiHalf::protocol_leaf_name()
);
assert_eq!(
EndpointHalf::protocol_leaf_spec().procedures,
TuiHalf::protocol_leaf_spec().procedures
);
assert_eq!(
<EndpointHalf as LeafBinding>::Declaration::procedure_id("connect"),
Some(String::from("org.example.v2.chat.connect"))
);
}
#[test]
fn leaf_declaration_supports_tui_only_hosts() {
assert_eq!(TuiOnly::protocol_leaf_name(), "org.example.v1.transcript");
assert_eq!(
<TuiOnly as LeafDeclaration>::procedure_id("tail"),
Some(String::from("org.example.v1.transcript.tail"))
);
}
@@ -1,4 +1,5 @@
mod call;
mod leaf_decl;
mod procedure;
mod protocol;
mod tree;
@@ -6,18 +6,23 @@ use crate::protocol::tree::{
ProcedureEffect, ProcedureRuntime, ProcedureStore, ProtocolEndpoint, encode_call_reply,
};
use crate::protocol::{PacketType, decode_frame};
use crate::{Leaf, Procedure};
use crate::{Procedure, leaf};
fn path(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}
#[derive(Default, Leaf)]
#[leaf(id = "org.example.v1.stream")]
#[derive(Default)]
struct StreamLeaf {
sessions: BTreeMap<HookKey, ProcedureOpen>,
}
leaf! {
id = "org.example.v1.stream",
procedures = [ProcedureOpen],
endpoint_struct = StreamLeaf,
}
impl ProcedureStore<ProcedureOpen> for StreamLeaf {
fn procedure_sessions(&mut self) -> &mut BTreeMap<HookKey, ProcedureOpen> {
&mut self.sessions
@@ -67,10 +72,7 @@ fn procedure_runtime_routes_data_to_stored_session() {
path(&["agent"]),
Some(Vec::new()),
Vec::new(),
vec![crate::protocol::tree::LeafSpec {
name: StreamLeaf::protocol_leaf_name(),
procedures: vec![ProcedureOpen::protocol_procedure_id()],
}],
vec![StreamLeaf::protocol_leaf_spec()],
);
let mut runtime =
ProcedureRuntime::<StreamLeaf, ProcedureOpen>::new(endpoint, StreamLeaf::default());
@@ -139,12 +141,17 @@ fn procedure_runtime_routes_data_to_stored_session() {
assert!(runtime.leaf_mut().procedure_sessions().is_empty());
}
#[derive(Default, Leaf)]
#[leaf(id = "org.example.v1.duplex")]
#[derive(Default)]
struct DuplexLeaf {
sessions: BTreeMap<HookKey, DuplexProcedure>,
}
leaf! {
id = "org.example.v1.duplex",
procedures = [DuplexProcedure],
endpoint_struct = DuplexLeaf,
}
impl ProcedureStore<DuplexProcedure> for DuplexLeaf {
fn procedure_sessions(&mut self) -> &mut BTreeMap<HookKey, DuplexProcedure> {
&mut self.sessions
@@ -197,10 +204,7 @@ fn procedure_runtime_keeps_session_after_local_end_until_explicit_close() {
path(&["agent"]),
Some(Vec::new()),
Vec::new(),
vec![crate::protocol::tree::LeafSpec {
name: DuplexLeaf::protocol_leaf_name(),
procedures: vec![DuplexProcedure::protocol_procedure_id()],
}],
vec![DuplexLeaf::protocol_leaf_spec()],
);
let mut runtime =
ProcedureRuntime::<DuplexLeaf, DuplexProcedure>::new(endpoint, DuplexLeaf::default());
@@ -46,7 +46,7 @@ use super::{
/// struct Open;
/// impl ProcedureMetadata for Open {
/// type Leaf = ExampleLeaf;
/// fn procedure_suffix() -> &'static str { "open" }
/// const PROCEDURE_SUFFIX: &'static str = "open";
/// }
/// assert_eq!(Open::procedure_id(), "org.example.v1.shell.open");
/// ```
@@ -55,7 +55,12 @@ pub trait ProcedureMetadata: Sized {
type Leaf: ProtocolLeaf;
/// Returns the local suffix used to derive the full canonical `procedure_id`.
fn procedure_suffix() -> &'static str;
const PROCEDURE_SUFFIX: &'static str;
/// Returns the local suffix used to derive the full canonical `procedure_id`.
fn procedure_suffix() -> &'static str {
Self::PROCEDURE_SUFFIX
}
/// Returns the canonical `procedure_id` for this procedure.
fn procedure_id() -> String {
@@ -81,8 +86,7 @@ pub trait ProcedureMetadata: Sized {
/// struct Open;
/// impl ProcedureMetadata for Open {
/// type Leaf = ExampleLeaf;
///
/// fn procedure_suffix() -> &'static str { "open" }
/// const PROCEDURE_SUFFIX: &'static str = "open";
/// }
/// fn _compat<T: StatefulProcedureMetadata<ExampleLeaf>>() {}
/// _compat::<Open>();
@@ -133,15 +137,20 @@ pub trait ProcedureStore<P> {
/// ```rust
/// use std::collections::BTreeMap;
/// use std::string::String;
/// use unshell::{Leaf, Procedure};
/// use unshell::{Procedure, leaf};
/// use unshell::protocol::tree::{Call, HookKey, Procedure, ProcedureEffect, ProcedureStore};
///
/// #[derive(Default, Leaf)]
/// #[leaf(id = "org.example.v1.stream")]
/// #[derive(Default)]
/// struct StreamLeaf {
/// sessions: BTreeMap<HookKey, OpenProcedure>,
/// }
///
/// leaf! {
/// id = "org.example.v1.stream",
/// procedures = [OpenProcedure],
/// endpoint_struct = StreamLeaf,
/// }
///
/// impl ProcedureStore<OpenProcedure> for StreamLeaf {
/// fn procedure_sessions(&mut self) -> &mut BTreeMap<HookKey, OpenProcedure> {
/// &mut self.sessions