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}