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}