Skip to main content

zlink_core/varlink_service/
proxy.rs

1//! Client-side proxy API for the `org.varlink.service` interface.
2//!
3//! This module provides the [`Proxy`] trait which offers convenient methods to call
4//! the standard Varlink service interface methods on any connection.
5
6use crate::proxy;
7
8use super::{Error, Info, InterfaceDescription, OwnedError, OwnedInfo};
9
10/// Client-side proxy for the `org.varlink.service` interface.
11///
12/// This trait provides methods to call the standard Varlink service interface methods on a
13/// connection.
14///
15/// # Borrowed vs Owned Methods
16///
17/// The trait provides both borrowed and owned variants of each method:
18///
19/// - **Borrowed methods** (`get_info`, `get_interface_description`): Return borrowed types for
20///   efficient zero-copy deserialization. These are preferred for single calls.
21///
22/// - **Owned methods** (`owned_get_info`, `owned_get_interface_description`): Return owned types
23///   ([`OwnedInfo`], [`OwnedError`]) required for the chain API. Chain methods (`chain_*`) are
24///   generated only for these variants since `DeserializeOwned` is required for pipelining.
25///
26/// # Example: Basic Usage
27///
28/// ```no_run
29/// use zlink_core::{Connection, varlink_service::Proxy};
30///
31/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
32/// # let mut conn: Connection<zlink_core::connection::socket::impl_for_doc::Socket> = todo!();
33/// // Get service information (borrowed - zero-copy).
34/// let info = conn.get_info().await?.map_err(|e| e.to_string())?;
35/// println!("Service: {} v{} by {}", info.product, info.version, info.vendor);
36/// println!("URL: {}", info.url);
37/// println!("Interfaces: {:?}", info.interfaces);
38///
39/// // Get interface description.
40/// let desc = conn
41///     .get_interface_description("org.varlink.service")
42///     .await?
43///     .map_err(|e| e.to_string())?;
44/// println!("Interface description: {}", desc.as_raw().unwrap());
45///
46/// # Ok(())
47/// # }
48/// ```
49///
50/// # Example: Chaining (Pipelining)
51///
52/// ```no_run
53/// use zlink_core::{
54///     Connection,
55///     varlink_service::{Chain, OwnedError, OwnedReply, Proxy},
56/// };
57/// use futures_util::{pin_mut, stream::StreamExt};
58///
59/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
60/// # let mut conn: Connection<zlink_core::connection::socket::impl_for_doc::Socket> = todo!();
61/// // Chain multiple calls using owned methods (required for pipelining).
62/// let chain = conn
63///     .chain_owned_get_info()?
64///     .owned_get_interface_description("org.example.interface")?
65///     .owned_get_info()?;
66///
67/// // Send the chain and process replies.
68/// let replies = chain.send::<OwnedReply, OwnedError>().await?;
69/// pin_mut!(replies);
70///
71/// // Process each reply in the order they were chained.
72/// while let Some(result) = replies.next().await {
73///     let (reply, _fds) = result?;
74///     match reply.unwrap().into_parameters().unwrap() {
75///         OwnedReply::Info(info) => {
76///             println!("Service: {} v{} by {}", info.product, info.version, info.vendor);
77///         }
78///         OwnedReply::InterfaceDescription(desc) => {
79///             println!("Interface description: {}", desc.as_raw().unwrap());
80///         }
81///     }
82/// }
83///
84/// # Ok(())
85/// # }
86/// ```
87#[proxy(
88    interface = "org.varlink.service",
89    crate = "crate",
90    chain_name = "Chain"
91)]
92#[cfg(feature = "std")]
93pub trait Proxy {
94    /// Get information about a Varlink service.
95    ///
96    /// This method uses borrowed types for zero-copy deserialization. For chaining (pipelining),
97    /// use [`owned_get_info`](Self::owned_get_info) instead.
98    ///
99    /// # Returns
100    ///
101    /// Two-layer result: outer for connection errors, inner for method errors. On success, contains
102    /// service information as [`Info`].
103    async fn get_info(&mut self) -> crate::Result<core::result::Result<Info<'_>, Error<'_>>>;
104
105    /// Get information about a Varlink service (owned variant for chain API).
106    ///
107    /// This method returns owned types, which is required for the chain API (pipelining).
108    /// For single calls, prefer [`get_info`](Self::get_info) for zero-copy deserialization.
109    ///
110    /// # Returns
111    ///
112    /// Two-layer result: outer for connection errors, inner for method errors. On success, contains
113    /// service information as [`OwnedInfo`].
114    #[zlink(rename = "GetInfo")]
115    async fn owned_get_info(
116        &mut self,
117    ) -> crate::Result<core::result::Result<OwnedInfo, OwnedError>>;
118
119    /// Get the IDL description of an interface.
120    ///
121    /// This method uses borrowed types for zero-copy deserialization. For chaining (pipelining),
122    /// use [`owned_get_interface_description`](Self::owned_get_interface_description) instead.
123    ///
124    /// # Arguments
125    ///
126    /// * `interface` - The name of the interface to get the description for.
127    ///
128    /// # Returns
129    ///
130    /// Two-layer result: outer for connection errors, inner for method errors. On success, contains
131    /// the unparsed interface definition as a [`InterfaceDescription`]. Use
132    /// [`InterfaceDescription::parse`] to parse it.
133    async fn get_interface_description(
134        &mut self,
135        interface: &str,
136    ) -> crate::Result<core::result::Result<InterfaceDescription<'static>, Error<'_>>>;
137
138    /// Get the IDL description of an interface (owned variant for chain API).
139    ///
140    /// This method returns owned types, which is required for the chain API (pipelining).
141    /// For single calls, prefer [`get_interface_description`](Self::get_interface_description)
142    /// for zero-copy deserialization.
143    ///
144    /// # Arguments
145    ///
146    /// * `interface` - The name of the interface to get the description for.
147    ///
148    /// # Returns
149    ///
150    /// Two-layer result: outer for connection errors, inner for method errors. On success, contains
151    /// the unparsed interface definition as a [`InterfaceDescription`]. Use
152    /// [`InterfaceDescription::parse`] to parse it.
153    #[zlink(rename = "GetInterfaceDescription")]
154    async fn owned_get_interface_description(
155        &mut self,
156        interface: &str,
157    ) -> crate::Result<core::result::Result<InterfaceDescription<'static>, OwnedError>>;
158}
159
160#[cfg(test)]
161mod tests {
162    use super::{super::OwnedReply, *};
163    use crate::{Connection, test_utils::mock_socket::MockSocket};
164    use futures_util::{pin_mut, stream::StreamExt};
165
166    #[tokio::test]
167    async fn chain_api_creation() -> crate::Result<()> {
168        // Test that we can create chains with the owned API.
169        let responses = [
170            r#"{"parameters":{"vendor":"Test","product":"TestProduct","version":"1.0","url":"https://test.com","interfaces":["org.varlink.service"]}}"#,
171            r#"{"parameters":{"description":"interface org.varlink.service {}"}}"#,
172        ];
173        let socket = MockSocket::with_responses(&responses);
174        let mut conn = Connection::new(socket);
175
176        // Test that we can create the chain APIs.
177        let _chain1 = conn.chain_owned_get_info()?;
178        let _chain2 = conn.chain_owned_get_interface_description("org.varlink.service")?;
179
180        Ok(())
181    }
182
183    #[tokio::test]
184    async fn chain_extension_methods() -> crate::Result<()> {
185        // Test that we can use chain extension methods.
186        let responses = [
187            r#"{"parameters":{"vendor":"Test","product":"TestProduct","version":"1.0","url":"https://test.com","interfaces":["org.varlink.service"]}}"#,
188            r#"{"parameters":{"description":"interface org.varlink.service {}"}}"#,
189            r#"{"parameters":{"vendor":"Test","product":"TestProduct","version":"1.0","url":"https://test.com","interfaces":["org.varlink.service"]}}"#,
190        ];
191        let socket = MockSocket::with_responses(&responses);
192        let mut conn = Connection::new(socket);
193
194        // Test that we can chain calls using extension methods and actually read replies.
195        let chained = conn
196            .chain_owned_get_info()?
197            .owned_get_interface_description("org.varlink.service")?
198            .owned_get_info()?;
199
200        let replies = chained.send::<OwnedReply, OwnedError>().await?;
201        pin_mut!(replies);
202
203        // Read first reply (GetInfo).
204        let (first_reply, _fds) = replies.next().await.unwrap()?;
205        let first_reply = first_reply.unwrap();
206        match first_reply.into_parameters().unwrap() {
207            OwnedReply::Info(info) => {
208                assert_eq!(info.vendor, "Test");
209                assert_eq!(info.product, "TestProduct");
210                assert_eq!(info.version, "1.0");
211                assert_eq!(info.url, "https://test.com");
212                assert_eq!(info.interfaces, ["org.varlink.service"]);
213            }
214            _ => panic!("Expected Info reply"),
215        }
216
217        // Read second reply (GetInterfaceDescription).
218        let (second_reply, _fds) = replies.next().await.unwrap()?;
219        let second_reply = second_reply.unwrap();
220        match second_reply.into_parameters().unwrap() {
221            OwnedReply::InterfaceDescription(desc) => {
222                assert_eq!(desc.as_raw().unwrap(), "interface org.varlink.service {}");
223            }
224            _ => panic!("Expected InterfaceDescription reply"),
225        }
226
227        // Read third reply (GetInfo again).
228        let (third_reply, _fds) = replies.next().await.unwrap()?;
229        let third_reply = third_reply.unwrap();
230        match third_reply.into_parameters().unwrap() {
231            OwnedReply::Info(info) => {
232                assert_eq!(info.vendor, "Test");
233            }
234            _ => panic!("Expected Info reply"),
235        }
236
237        // No more replies.
238        assert!(replies.next().await.is_none());
239
240        Ok(())
241    }
242}