sacp/component.rs
1//! Component abstraction for agents and proxies.
2//!
3//! This module provides the [`Component`] trait that defines the interface for things
4//! that can be run as part of a conductor's chain - agents, proxies, or any ACP-speaking component.
5//!
6//! ## Usage
7//!
8//! Components are always servers that communicate via message channels with their conductor.
9//! To implement a component, create a handler chain and serve it on the provided channels:
10//!
11//! ```rust,ignore
12//! use sacp::component::Component;
13//! use sacp::{Channels, JrHandlerChain};
14//! use futures::future::BoxFuture;
15//!
16//! struct MyProxy {
17//! // configuration fields
18//! }
19//!
20//! impl Component for MyProxy {
21//! fn run(self: Box<Self>, channels: Channels) -> BoxFuture<'static, Result<(), sacp::Error>> {
22//! Box::pin(async move {
23//! JrHandlerChain::new()
24//! .name("my-proxy")
25//! // configure handlers here
26//! .serve(channels)
27//! .await
28//! })
29//! }
30//! }
31//! ```
32
33use futures::future::BoxFuture;
34
35use crate::{Channels, Transport};
36
37/// A component that can be run as part of a conductor's chain.
38///
39/// Components are always servers that receive [`Channels`] for bidirectional
40/// communication with their conductor. The channels carry JSON-RPC messages
41/// between the conductor and component.
42///
43/// # Implementation
44///
45/// Most implementations will:
46/// 1. Create a [`JrHandlerChain`](crate::JrHandlerChain) with the desired handlers
47/// 2. Call `.serve(channels)` on it
48/// 3. Return the resulting future wrapped in a `BoxFuture`
49///
50/// # Examples
51///
52/// Basic component implementation:
53///
54/// ```rust,ignore
55/// use sacp::component::Component;
56/// use sacp::{Channels, JrHandlerChain};
57/// use futures::future::BoxFuture;
58///
59/// struct MyComponent;
60///
61/// impl Component for MyComponent {
62/// fn serve(self: Box<Self>, channels: Channels) -> BoxFuture<'static, Result<(), sacp::Error>> {
63/// Box::pin(async move {
64/// JrHandlerChain::new()
65/// .name("my-component")
66/// .on_receive_request(async |req: MyRequest, cx| {
67/// cx.respond(MyResponse { status: "ok".into() })
68/// })
69/// .serve(channels)
70/// .await
71/// })
72/// }
73/// }
74/// ```
75pub trait Component: Send {
76 /// Serve this component on the given channels.
77 ///
78 /// The component should set up its handler chain and serve on the provided
79 /// channels, which connect it to its conductor.
80 ///
81 /// # Arguments
82 ///
83 /// * `channels` - Bidirectional message channels for communicating with the conductor
84 ///
85 /// # Returns
86 ///
87 /// A future that resolves when the component stops serving, either successfully
88 /// or with an error.
89 fn serve(self: Box<Self>, channels: Channels) -> BoxFuture<'static, Result<(), crate::Error>>;
90}
91
92/// Blanket implementation: any `Transport` can be used as a `Component`.
93///
94/// Since both `Channels` and `Channels` now have the same signature
95/// (both use `Result<Message, Error>` for incoming), we can directly pass through.
96impl<T: Transport + 'static> Component for T {
97 fn serve(self: Box<Self>, channels: Channels) -> BoxFuture<'static, Result<(), crate::Error>> {
98 Box::pin(async move {
99 // Both Channels and Channels now have identical types,
100 // so we can convert directly
101 let transport_channels =
102 Channels::new(channels.remote_outgoing_rx, channels.remote_incoming_tx);
103 self.transport(transport_channels).await
104 })
105 }
106}
107
108/// Blanket implementation: any `Component` can be used as a `Transport`.
109///
110/// Since both `Channels` and `Channels` now have the same signature
111/// (both use `Result<Message, Error>` for incoming), we can directly pass through.
112impl Transport for dyn Component {
113 fn transport(
114 self: Box<Self>,
115 channels: Channels,
116 ) -> BoxFuture<'static, Result<(), crate::Error>> {
117 Box::pin(async move {
118 // Both Channels and Channels now have identical types,
119 // so we can convert directly
120 let channels = Channels::new(channels.remote_outgoing_rx, channels.remote_incoming_tx);
121 self.serve(channels).await
122 })
123 }
124}