zlink_macros/lib.rs
1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/z-galaxy/zlink/3660d731d7de8f60c8d82e122b3ece15617185e4/data/logo.png"
3)]
4#![deny(
5 missing_debug_implementations,
6 nonstandard_style,
7 rust_2018_idioms,
8 missing_docs
9)]
10#![warn(unreachable_pub)]
11#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
12
13mod utils;
14
15#[cfg(feature = "introspection")]
16mod introspect;
17
18mod reply_error;
19
20#[cfg(feature = "proxy")]
21mod proxy;
22
23/// Derives `Type` for structs and enums, generating appropriate `Type::Object` or `Type::Enum`
24/// representation.
25///
26/// **Requires the `introspection` feature to be enabled.**
27///
28/// ## Structs
29///
30/// For structs, this macro supports named fields and unit structs. It will generate a
31/// `Type` implementation that creates a `Type::Object` containing all the fields with their
32/// names and types. Tuple structs are not supported as Varlink does not support unnamed fields.
33///
34/// ## Enums
35///
36/// For enums, this macro only supports unit variants (variants without associated data). It will
37/// generate a `Type` implementation that creates a `Type::Enum` containing all the variant
38/// names.
39///
40/// # Supported Attributes
41///
42/// The following attributes can be used to customize the behavior of this derive macro:
43///
44/// * `#[zlink(crate = "path")]` - Specifies the crate path to use for zlink types. Defaults to
45/// `::zlink`.
46///
47/// # Limitations
48///
49/// The following types are **not** supported by this macro:
50///
51/// - **Tuple structs**: Varlink does not support unnamed fields
52/// - **Enums with data**: Only unit enums (variants without associated data) are supported
53/// - **Unions**: Not supported by Varlink
54///
55/// ```rust,compile_fail
56/// # use zlink::introspect::Type;
57/// #[derive(Type)] // This will fail to compile
58/// struct Point(f32, f32, f32);
59/// ```
60///
61/// ```rust,compile_fail
62/// # use zlink::introspect::Type;
63/// #[derive(Type)] // This will fail to compile
64/// enum Status {
65/// Active(String), // Variants with data are not supported
66/// Inactive,
67/// }
68/// ```
69///
70/// # Examples
71///
72/// ## Named Structs
73///
74/// ```rust
75/// use zlink::introspect::Type;
76/// use zlink::idl;
77///
78/// #[derive(Type)]
79/// struct Person {
80/// name: String,
81/// age: i32,
82/// active: bool,
83/// }
84///
85/// // Access the generated type information
86/// match Person::TYPE {
87/// idl::Type::Object(fields) => {
88/// let field_vec: Vec<_> = fields.iter().collect();
89/// assert_eq!(field_vec.len(), 3);
90///
91/// assert_eq!(field_vec[0].name(), "name");
92/// assert_eq!(field_vec[0].ty(), &idl::Type::String);
93///
94/// assert_eq!(field_vec[1].name(), "age");
95/// assert_eq!(field_vec[1].ty(), &idl::Type::Int);
96///
97/// assert_eq!(field_vec[2].name(), "active");
98/// assert_eq!(field_vec[2].ty(), &idl::Type::Bool);
99/// }
100/// _ => panic!("Expected struct type"),
101/// }
102/// ```
103///
104/// ## Unit Structs
105///
106/// ```rust
107/// # use zlink::introspect::Type;
108/// # use zlink::idl;
109/// #[derive(Type)]
110/// struct Unit;
111///
112/// // Unit structs generate empty field lists
113/// match Unit::TYPE {
114/// idl::Type::Object(fields) => {
115/// assert_eq!(fields.len(), 0);
116/// }
117/// _ => panic!("Expected struct type"),
118/// }
119/// ```
120///
121/// ## Complex Types
122///
123/// ```rust
124/// # use zlink::introspect::Type;
125/// # use zlink::idl;
126/// #[derive(Type)]
127/// struct Complex {
128/// id: u64,
129/// description: Option<String>,
130/// tags: Vec<String>,
131/// }
132///
133/// // The macro handles nested types like Option<T> and Vec<T>
134/// match Complex::TYPE {
135/// idl::Type::Object(fields) => {
136/// let field_vec: Vec<_> = fields.iter().collect();
137///
138/// // Optional field becomes Type::Optional
139/// match field_vec[1].ty() {
140/// idl::Type::Optional(inner) => assert_eq!(inner.inner(), &idl::Type::String),
141/// _ => panic!("Expected optional type"),
142/// }
143///
144/// // Vec field becomes Type::Array
145/// match field_vec[2].ty() {
146/// idl::Type::Array(inner) => assert_eq!(inner.inner(), &idl::Type::String),
147/// _ => panic!("Expected array type"),
148/// }
149/// }
150/// _ => panic!("Expected struct type"),
151/// }
152/// ```
153///
154/// ## Unit Enums
155///
156/// ```rust
157/// # use zlink::introspect::Type;
158/// # use zlink::idl;
159/// #[derive(Type)]
160/// enum Status {
161/// Active,
162/// Inactive,
163/// Pending,
164/// }
165///
166/// // Unit enums generate variant lists
167/// match Status::TYPE {
168/// idl::Type::Enum(variants) => {
169/// let variant_vec: Vec<_> = variants.iter().collect();
170/// assert_eq!(variant_vec.len(), 3);
171/// assert_eq!(variant_vec[0].name(), "Active");
172/// assert_eq!(variant_vec[1].name(), "Inactive");
173/// assert_eq!(variant_vec[2].name(), "Pending");
174/// }
175/// _ => panic!("Expected enum type"),
176/// }
177/// ```
178#[cfg(feature = "introspection")]
179#[proc_macro_derive(IntrospectType, attributes(zlink))]
180pub fn derive_introspect_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
181 introspect::r#type::derive_type(input)
182}
183
184/// Derives `Type` for structs and enums, generating named custom type definitions.
185///
186/// **Requires the `introspection` feature to be enabled.**
187///
188/// This macro generates implementations of the `CustomType` trait, which provides named
189/// custom type definitions suitable for IDL generation. It also generates a `Type` implementation
190/// and therefore is mutually exclusive to `zlink::introspect::Type` derive macro.
191///
192/// ## Structs
193///
194/// For structs, this macro generates a `custom::Type::Object` containing the struct name and
195/// all fields with their names and types.
196///
197/// ## Enums
198///
199/// For enums, this macro only supports unit variants and generates a `custom::Type::Enum`
200/// containing the enum name and all variant names.
201///
202/// # Supported Attributes
203///
204/// The following attributes can be used to customize the behavior of this derive macro:
205///
206/// * `#[zlink(crate = "path")]` - Specifies the crate path to use for zlink types. Defaults to
207/// `::zlink`.
208///
209/// # Examples
210///
211/// ## Named Structs
212///
213/// ```rust
214/// use zlink::introspect::{CustomType, Type};
215/// use zlink::idl;
216///
217/// #[derive(CustomType)]
218/// struct Point {
219/// x: f64,
220/// y: f64,
221/// }
222///
223/// // Access the generated custom type information
224/// match Point::CUSTOM_TYPE {
225/// idl::CustomType::Object(obj) => {
226/// assert_eq!(obj.name(), "Point");
227/// let fields: Vec<_> = obj.fields().collect();
228/// assert_eq!(fields.len(), 2);
229/// assert_eq!(fields[0].name(), "x");
230/// assert_eq!(fields[1].name(), "y");
231/// }
232/// _ => panic!("Expected custom object type"),
233/// }
234///
235/// match Point::TYPE {
236/// idl::Type::Custom(name) => {
237/// assert_eq!(*name, "Point");
238/// }
239/// _ => panic!("Expected custom type"),
240/// }
241/// ```
242///
243/// ## Unit Enums
244///
245/// ```rust
246/// # use zlink::introspect::{CustomType, Type};
247/// # use zlink::idl;
248/// #[derive(CustomType)]
249/// enum Status {
250/// Active,
251/// Inactive,
252/// Pending,
253/// }
254///
255/// // Access the generated custom enum type information
256/// match Status::CUSTOM_TYPE {
257/// idl::CustomType::Enum(enm) => {
258/// assert_eq!(enm.name(), "Status");
259/// let variants: Vec<_> = enm.variants().collect();
260/// assert_eq!(variants.len(), 3);
261/// assert_eq!(variants[0].name(), "Active");
262/// assert_eq!(variants[1].name(), "Inactive");
263/// assert_eq!(variants[2].name(), "Pending");
264/// }
265/// _ => panic!("Expected custom enum type"),
266/// }
267///
268/// match Status::TYPE {
269/// idl::Type::Custom(name) => {
270/// assert_eq!(*name, "Status");
271/// }
272/// _ => panic!("Expected custom type"),
273/// }
274/// ```
275#[cfg(feature = "introspection")]
276#[proc_macro_derive(IntrospectCustomType, attributes(zlink))]
277pub fn derive_introspect_custom_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
278 introspect::custom_type::derive_custom_type(input)
279}
280
281/// Derives `ReplyError` for enums, generating error definitions for Varlink service errors.
282///
283/// **Requires the `introspection` feature to be enabled.**
284///
285/// This macro generates implementations of the `ReplyError` trait, which provides a list of
286/// error variants that can be returned by a Varlink service method. It supports unit variants,
287/// variants with named fields, and single-field tuple variants (where the field type implements
288/// `Type` and has a `Type::Object`).
289///
290/// # Supported Attributes
291///
292/// The following attributes can be used to customize the behavior of this derive macro:
293///
294/// * `#[zlink(crate = "path")]` - Specifies the crate path to use for zlink types. Defaults to
295/// `::zlink`.
296///
297/// # Example
298///
299/// ```rust
300/// use zlink::introspect::ReplyError;
301///
302/// #[derive(ReplyError)]
303/// enum ServiceError {
304/// // Unit variant - no parameters
305/// NotFound,
306///
307/// // Named field variant - multiple parameters
308/// InvalidQuery {
309/// message: String,
310/// line: u32,
311/// },
312///
313/// // Single tuple variant - uses fields from the wrapped type
314/// ValidationFailed(ValidationDetails),
315/// }
316///
317/// // Example struct for tuple variant
318/// #[derive(zlink::introspect::Type)]
319/// struct ValidationDetails {
320/// field_name: String,
321/// expected: String,
322/// }
323///
324/// // Access the generated error variants
325/// assert_eq!(ServiceError::VARIANTS.len(), 3);
326/// assert_eq!(ServiceError::VARIANTS[0].name(), "NotFound");
327/// assert!(ServiceError::VARIANTS[0].has_no_fields());
328///
329/// assert_eq!(ServiceError::VARIANTS[1].name(), "InvalidQuery");
330/// assert!(!ServiceError::VARIANTS[1].has_no_fields());
331/// ```
332#[cfg(feature = "introspection")]
333#[proc_macro_derive(IntrospectReplyError, attributes(zlink))]
334pub fn derive_introspect_reply_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
335 introspect::reply_error::derive_reply_error(input)
336}
337
338/// Creates a client-side proxy for calling Varlink methods on a connection.
339///
340/// **Requires the `proxy` feature to be enabled.**
341///
342/// This attribute macro generates an implementation of the provided trait for `Connection<S>`,
343/// automatically handling the serialization of method calls and deserialization of responses.
344/// Each proxy trait targets a single Varlink interface.
345///
346/// The macro also generates a chain extension trait that allows you to chain multiple method
347/// calls together for efficient batching across multiple interfaces.
348///
349/// # Supported Attributes
350///
351/// The following attributes can be used to customize the behavior of this macro:
352///
353/// * `interface` (required) - The Varlink interface name (e.g., `"org.varlink.service"`).
354/// * `crate` - Specifies the crate path to use for zlink types. Defaults to `::zlink`.
355/// * `chain_name` - Custom name for the generated chain extension trait. Defaults to
356/// `{TraitName}Chain`.
357///
358/// # Example
359///
360/// ```rust
361/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
362/// use zlink::proxy;
363/// use serde::{Deserialize, Serialize};
364/// use serde_prefix_all::prefix_all;
365/// use futures_util::stream::Stream;
366///
367/// #[proxy("org.example.MyService")]
368/// trait MyServiceProxy {
369/// async fn get_status(&mut self) -> zlink::Result<Result<Status<'_>, MyError<'_>>>;
370/// async fn set_value(
371/// &mut self,
372/// key: &str,
373/// value: i32,
374/// ) -> zlink::Result<Result<(), MyError<'_>>>;
375/// // This will call the `io.systemd.Machine.List` method when `list_machines()` is invoked.
376/// #[zlink(rename = "ListMachines")]
377/// async fn list_machines(&mut self) -> zlink::Result<Result<Vec<Machine<'_>>, MyError<'_>>>;
378/// // Streaming version of get_status - calls the same method but returns a stream
379/// #[zlink(rename = "GetStatus", more)]
380/// async fn stream_status(
381/// &mut self,
382/// ) -> zlink::Result<
383/// impl Stream<Item = zlink::Result<Result<Status<'_>, MyError<'_>>>>,
384/// >;
385/// }
386///
387/// // The macro generates:
388/// // impl<S: Socket> MyServiceProxy for Connection<S> { ... }
389///
390/// #[derive(Debug, Serialize, Deserialize)]
391/// struct Status<'m> {
392/// active: bool,
393/// message: &'m str,
394/// }
395///
396/// #[derive(Debug, Serialize, Deserialize)]
397/// struct Machine<'m> { name: &'m str }
398///
399/// #[prefix_all("org.example.MyService.")]
400/// #[derive(Debug, Serialize, Deserialize)]
401/// #[serde(tag = "error", content = "parameters")]
402/// enum MyError<'a> {
403/// NotFound,
404/// InvalidRequest,
405/// // Parameters must be named.
406/// CodedError { code: u32, message: &'a str },
407/// }
408///
409/// // Example usage:
410/// # use zlink::test_utils::mock_socket::MockSocket;
411/// # let responses = vec![
412/// # r#"{"parameters":{"active":true,"message":"System running"}}"#,
413/// # ];
414/// # let socket = MockSocket::with_responses(&responses);
415/// # let mut conn = zlink::Connection::new(socket);
416/// let result = conn.get_status().await?.unwrap();
417/// assert_eq!(result.active, true);
418/// assert_eq!(result.message, "System running");
419/// # Ok::<(), Box<dyn std::error::Error>>(())
420/// # }).unwrap();
421/// ```
422///
423/// # Chaining Method Calls
424///
425/// The proxy macro generates chain extension traits that allow you to batch multiple method calls
426/// together. This is useful for reducing round trips and efficiently calling methods across
427/// multiple interfaces. Each method gets a `chain_` prefixed variant that starts a chain.
428///
429/// ## Example: Chaining Method Calls
430///
431/// ```rust
432/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
433/// # use zlink::proxy;
434/// # use serde::{Deserialize, Serialize};
435/// # use futures_util::{pin_mut, TryStreamExt};
436/// #
437/// # #[derive(Debug, Serialize, Deserialize)]
438/// # struct User<'a> { id: u64, name: &'a str }
439/// # #[derive(Debug, Serialize, Deserialize)]
440/// # struct Post<'a> { id: u64, user_id: u64, content: &'a str }
441/// # #[derive(Debug, Serialize, Deserialize)]
442/// # #[serde(untagged)]
443/// # enum BlogReply<'a> {
444/// # #[serde(borrow)]
445/// # User(User<'a>),
446/// # #[serde(borrow)]
447/// # Post(Post<'a>),
448/// # #[serde(borrow)]
449/// # Posts(Vec<Post<'a>>)
450/// # }
451/// # #[derive(Debug, Serialize, Deserialize)]
452/// # #[serde(tag = "error")]
453/// # enum BlogError { NotFound, InvalidInput }
454/// # impl std::fmt::Display for BlogError {
455/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456/// # match self {
457/// # Self::NotFound => write!(f, "Not found"),
458/// # Self::InvalidInput => write!(f, "Invalid input")
459/// # }
460/// # }
461/// # }
462/// # impl std::error::Error for BlogError {}
463/// #
464/// // Define proxies for two different services
465/// #[proxy("org.example.blog.Users")]
466/// trait UsersProxy {
467/// async fn get_user(&mut self, id: u64) -> zlink::Result<Result<BlogReply<'_>, BlogError>>;
468/// async fn create_user(&mut self, name: &str)
469/// -> zlink::Result<Result<BlogReply<'_>, BlogError>>;
470/// }
471///
472/// #[proxy("org.example.blog.Posts")]
473/// trait PostsProxy {
474/// async fn get_posts_by_user(&mut self, user_id: u64)
475/// -> zlink::Result<Result<BlogReply<'_>, BlogError>>;
476/// async fn create_post(&mut self, user_id: u64, content: &str)
477/// -> zlink::Result<Result<BlogReply<'_>, BlogError>>;
478/// }
479///
480/// # use zlink::test_utils::mock_socket::MockSocket;
481/// # let responses = vec![
482/// # r#"{"parameters":{"id":1,"name":"Alice"}}"#,
483/// # r#"{"parameters":{"id":1,"user_id":1,"content":"My first post!"}}"#,
484/// # r#"{"parameters":[{"id":1,"user_id":1,"content":"My first post!"}]}"#,
485/// # r#"{"parameters":{"id":1,"name":"Alice"}}"#,
486/// # ];
487/// # let socket = MockSocket::with_responses(&responses);
488/// # let mut conn = zlink::Connection::new(socket);
489/// // Chain calls across both interfaces in a single batch
490/// let chain = conn
491/// .chain_create_user::<BlogReply<'_>, BlogError>("Alice")? // Start with Users interface
492/// .create_post(1, "My first post!")? // Chain Posts interface
493/// .get_posts_by_user(1)? // Get all posts
494/// .get_user(1)?; // Get user details
495///
496/// // Send all calls in a single batch
497/// let replies = chain.send().await?;
498/// pin_mut!(replies);
499///
500/// // Process replies in order
501/// let mut reply_count = 0;
502/// while let Some((reply, _fds)) = replies.try_next().await? {
503/// reply_count += 1;
504/// if let Ok(response) = reply {
505/// match response.parameters() {
506/// Some(BlogReply::User(user)) => assert_eq!(user.name, "Alice"),
507/// Some(BlogReply::Post(post)) => assert_eq!(post.content, "My first post!"),
508/// Some(BlogReply::Posts(posts)) => assert_eq!(posts.len(), 1),
509/// None => {} // set_value returns empty response
510/// }
511/// }
512/// }
513/// assert_eq!(reply_count, 4); // We made 4 calls
514/// # Ok::<(), Box<dyn std::error::Error>>(())
515/// # }).unwrap();
516/// ```
517///
518/// ## Combining with Standard Varlink Service
519///
520/// When the `idl-parse` feature is enabled, you can also chain calls between your custom
521/// interfaces and the standard Varlink service interface for introspection:
522///
523/// ```rust
524/// # #[cfg(feature = "idl-parse")] {
525/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
526/// # use zlink::{proxy, varlink_service};
527/// # use serde::{Deserialize, Serialize};
528/// #
529/// # #[derive(Debug, Serialize, Deserialize)]
530/// # struct Status { active: bool, message: String }
531/// # #[derive(Debug, Serialize, Deserialize)]
532/// # #[serde(tag = "error")]
533/// # enum MyError { NotFound, InvalidRequest }
534/// #
535/// #[proxy("com.example.MyService")]
536/// trait MyServiceProxy {
537/// async fn get_status(&mut self) -> zlink::Result<Result<Status, MyError>>;
538/// }
539///
540/// // Combined types for cross-interface chaining
541/// #[derive(Debug, Deserialize)]
542/// #[serde(untagged)]
543/// enum CombinedReply<'a> {
544/// #[serde(borrow)]
545/// VarlinkService(varlink_service::Reply<'a>),
546/// MyService(Status),
547/// }
548///
549/// #[derive(Debug, Deserialize)]
550/// #[serde(untagged)]
551/// enum CombinedError<'a> {
552/// #[serde(borrow)]
553/// VarlinkService(varlink_service::Error<'a>),
554/// MyService(MyError),
555/// }
556///
557/// // Example usage:
558/// # use zlink::test_utils::mock_socket::MockSocket;
559/// # let responses = vec![
560/// # concat!(
561/// # r#"{"parameters":{"vendor":"Test","product":"Example","version":"1.0","#,
562/// # r#""url":"https://example.com","interfaces":["com.example.MyService","#,
563/// # r#""org.varlink.service"]}}"#
564/// # ),
565/// # r#"{"parameters":{"active":true,"message":"Running"}}"#,
566/// # r#"{"parameters":{"description":"interface com.example.MyService\n..."}}"#,
567/// # ];
568/// # let socket = MockSocket::with_responses(&responses);
569/// # let mut conn = zlink::Connection::new(socket);
570/// use varlink_service::Proxy;
571/// use zlink::varlink_service::Chain;
572///
573/// // Get service info and custom status in one batch
574/// let chain = conn
575/// .chain_get_info::<CombinedReply<'_>, CombinedError<'_>>()? // Varlink service interface
576/// .get_status()? // MyService interface
577/// .get_interface_description("com.example.MyService")?; // Back to Varlink service
578///
579/// let replies = chain.send().await?;
580/// # Ok::<(), Box<dyn std::error::Error>>(())
581/// # }).unwrap();
582/// # }
583/// ```
584///
585/// ## Chain Extension Traits
586///
587/// For each proxy trait, the macro generates a corresponding chain extension trait. For example,
588/// `FtlProxy` gets `FtlProxyChain`. This trait is automatically implemented for `Chain` types,
589/// allowing seamless method chaining across interfaces.
590///
591/// # Method Requirements
592///
593/// Proxy methods must:
594/// - Take `&mut self` as the first parameter
595/// - Can be either `async fn` or return `impl Future`
596/// - Return `zlink::Result<Result<ReplyType, ErrorType>>` (outer Result for connection errors,
597/// inner for method errors)
598/// - The arguments can be any type that implement `serde::Serialize`
599/// - The reply type (`Ok` case of the inner `Result`) must be a type that implements
600/// `serde::Deserialize` and deserializes itself from a JSON object. Typically you'd just use a
601/// struct that derives `serde::Deserialize`.
602/// - The reply error type (`Err` case of the inner `Result`) must be a type `serde::Deserialize`
603/// that deserializes itself from a JSON object with two fields:
604/// - `error`: a string containing the fully qualified error name
605/// - `parameters`: an optional object containing all the fields of the error
606///
607/// # Method Names
608///
609/// By default, method names are converted from snake_case to PascalCase for the Varlink call.
610/// To specify a different Varlink method name, use the `#[zlink(rename = "...")]` attribute. See
611/// `list_machines` in the example above.
612///
613/// # Streaming Methods
614///
615/// For methods that support streaming (the 'more' flag), use the `#[zlink(more)]` attribute.
616/// Streaming methods must return `Result<impl Stream<Item = Result<Result<ReplyType,
617/// ErrorType>>>>`. The proxy will automatically set the 'more' flag on the call and return a
618/// stream of replies.
619///
620/// # File Descriptor Passing
621///
622/// **Requires the `std` feature to be enabled.**
623///
624/// Methods can send and receive file descriptors using the following attributes:
625///
626/// ## Sending File Descriptors
627///
628/// Use `#[zlink(fds)]` on a parameter of type `Vec<OwnedFd>` to send file descriptors with the
629/// method call. Only one FD parameter is allowed per method.
630///
631/// ## Receiving File Descriptors
632///
633/// Use `#[zlink(return_fds)]` on a method to indicate it returns file descriptors. The method's
634/// return type must be `Result<(Result<ReplyType, ErrorType>, Vec<OwnedFd>)>` - a tuple containing
635/// both the method result and the received file descriptors. The FDs are available regardless of
636/// whether the method succeeded or failed.
637///
638/// ## Example: File Descriptor Passing
639///
640/// File descriptors are passed out-of-band from the encoded JSON parameters. The typical pattern
641/// is to include integer indexes in your JSON parameters that reference positions in the FD
642/// vector. This is similar to how D-Bus handles FD passing.
643///
644/// ```rust
645/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
646/// use zlink::proxy;
647/// use serde::{Deserialize, Serialize};
648/// use std::os::fd::OwnedFd;
649///
650/// #[proxy("org.example.FileService")]
651/// trait FileServiceProxy {
652/// // Send file descriptors to the service
653/// // The stdin/stdout parameters are indexes into the FDs vector
654/// async fn spawn_process(
655/// &mut self,
656/// command: String,
657/// stdin_fd: u32,
658/// stdout_fd: u32,
659/// #[zlink(fds)] fds: Vec<OwnedFd>,
660/// ) -> zlink::Result<Result<ProcessInfo, FileError>>;
661///
662/// // Receive file descriptors from the service
663/// // Returns metadata with FD indexes and the actual FDs
664/// #[zlink(return_fds)]
665/// async fn open_files(
666/// &mut self,
667/// paths: Vec<String>,
668/// ) -> zlink::Result<(Result<Vec<FileInfo>, FileError>, Vec<OwnedFd>)>;
669/// }
670///
671/// #[derive(Debug, Serialize, Deserialize)]
672/// struct ProcessInfo {
673/// pid: u32,
674/// }
675///
676/// // Response contains FD indexes referencing the FD vector
677/// #[derive(Debug, Serialize, Deserialize)]
678/// struct FileInfo {
679/// path: String,
680/// fd: u32, // Index into the FD vector (0, 1, 2, etc.)
681/// }
682///
683/// #[derive(Debug, Serialize, Deserialize)]
684/// #[serde(tag = "error")]
685/// enum FileError {
686/// NotFound { path: String },
687/// PermissionDenied { path: String },
688/// }
689/// # impl std::error::Error for FileError {}
690/// # impl std::fmt::Display for FileError {
691/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
692/// # match self {
693/// # FileError::NotFound { path } => write!(f, "File not found: {}", path),
694/// # FileError::PermissionDenied { path } =>
695/// # write!(f, "Permission denied: {}", path),
696/// # }
697/// # }
698/// # }
699///
700/// // Example usage:
701/// # use std::os::unix::net::UnixStream;
702/// # use zlink::test_utils::mock_socket::MockSocket;
703/// // Sending FDs: Pass indexes as regular parameters
704/// # let (stdin_pipe, _w1) = UnixStream::pair()?;
705/// # let (stdout_pipe, _w2) = UnixStream::pair()?;
706/// # let send_response = r#"{"parameters":{"pid":1234}}"#;
707/// # let send_socket = MockSocket::with_responses(&[send_response]);
708/// # let mut send_conn = zlink::Connection::new(send_socket);
709/// let fds = vec![stdin_pipe.into(), stdout_pipe.into()];
710/// // Parameters reference FD indexes: stdin_fd=0, stdout_fd=1
711/// let result = send_conn.spawn_process("/bin/cat".to_string(), 0, 1, fds).await?;
712/// let process_info = result?;
713/// assert_eq!(process_info.pid, 1234);
714///
715/// // Receiving FDs: Response contains indexes that reference the FD vector
716/// # let (file1, _w3) = UnixStream::pair()?;
717/// # let (file2, _w4) = UnixStream::pair()?;
718/// # let recv_response = r#"{
719/// # "parameters": [
720/// # {"path": "/etc/config.txt", "fd": 0},
721/// # {"path": "/var/data.bin", "fd": 1}
722/// # ]
723/// # }"#;
724/// # let recv_fds = vec![vec![file1.into(), file2.into()]];
725/// # let recv_socket = MockSocket::new(&[recv_response], recv_fds);
726/// # let mut recv_conn = zlink::Connection::new(recv_socket);
727/// let (result, received_fds) = recv_conn
728/// .open_files(vec!["/etc/config.txt".to_string(), "/var/data.bin".to_string()])
729/// .await?;
730/// let file_list = result?;
731/// assert_eq!(file_list.len(), 2);
732/// assert_eq!(received_fds.len(), 2);
733/// // Use the fd field to match file info with actual FDs
734/// for file_info in &file_list {
735/// let fd = &received_fds[file_info.fd as usize];
736/// println!("File {} has FD at index {}", file_info.path, file_info.fd);
737/// }
738/// # Ok::<(), Box<dyn std::error::Error>>(())
739/// # }).unwrap();
740/// ```
741///
742/// ## Parameter Renaming
743///
744/// Use `#[zlink(rename = "name")]` on parameters to customize their serialized names in the
745/// Varlink protocol. This is useful when the Rust parameter name doesn't match the expected
746/// Varlink parameter name.
747///
748/// ```rust
749/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
750/// # use zlink::proxy;
751/// # use serde::{Deserialize, Serialize};
752/// #[proxy("org.example.Users")]
753/// trait UsersProxy {
754/// async fn create_user(
755/// &mut self,
756/// #[zlink(rename = "user_name")] name: String,
757/// #[zlink(rename = "user_email")] email: String,
758/// ) -> zlink::Result<Result<UserId, UserError>>;
759/// }
760/// # #[derive(Debug, Serialize, Deserialize)]
761/// # struct UserId { id: u32 }
762/// # #[derive(Debug, Serialize, Deserialize)]
763/// # #[serde(tag = "error")]
764/// # enum UserError { InvalidInput }
765/// # Ok::<(), Box<dyn std::error::Error>>(())
766/// # }).unwrap();
767/// ```
768///
769/// # Generic Parameters
770///
771/// The proxy macro supports generic type parameters on individual methods. Note that generic
772/// parameters on the trait itself are not currently supported.
773///
774/// ```rust
775/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
776/// # use zlink::proxy;
777/// # use serde::{Deserialize, Serialize};
778/// # #[derive(Debug, Serialize, Deserialize)]
779/// # struct StoredValue<T> { data: T }
780/// # #[derive(Debug, Serialize, Deserialize)]
781/// # struct ProcessReply<'a> { result: &'a str }
782/// # #[derive(Debug, Serialize, Deserialize)]
783/// # #[serde(tag = "error")]
784/// # enum StorageError { NotFound }
785/// # impl std::fmt::Display for StorageError {
786/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
787/// # write!(f, "Storage error")
788/// # }
789/// # }
790/// # impl std::error::Error for StorageError {}
791/// #
792/// #[proxy("org.example.Storage")]
793/// trait StorageProxy {
794/// // Method-level generics with trait bounds
795/// async fn store<'a, T: Serialize + std::fmt::Debug>(
796/// &mut self,
797/// key: &'a str,
798/// value: T,
799/// ) -> zlink::Result<Result<(), StorageError>>;
800///
801/// // Generic methods with where clauses
802/// async fn process<T>(&mut self, data: T)
803/// -> zlink::Result<Result<ProcessReply<'_>, StorageError>>
804/// where
805/// T: Serialize + std::fmt::Debug;
806///
807/// // Methods can use generic type parameters in both input and output
808/// async fn store_and_return<'a, T>(&mut self, key: &'a str, value: T)
809/// -> zlink::Result<Result<StoredValue<T>, StorageError>>
810/// where
811/// T: Serialize + for<'de> Deserialize<'de> + std::fmt::Debug;
812/// }
813///
814/// // Example usage:
815/// # use zlink::test_utils::mock_socket::MockSocket;
816/// # let responses = vec![
817/// # r#"{"parameters":null}"#, // store returns empty Ok
818/// # ];
819/// # let socket = MockSocket::with_responses(&responses);
820/// # let mut conn = zlink::Connection::new(socket);
821/// // Store a value with generic type
822/// let result = conn.store("my-key", 42i32).await?;
823/// assert!(result.is_ok());
824/// # Ok::<(), Box<dyn std::error::Error>>(())
825/// # }).unwrap();
826/// ```
827#[cfg(feature = "proxy")]
828#[proc_macro_attribute]
829pub fn proxy(
830 attr: proc_macro::TokenStream,
831 input: proc_macro::TokenStream,
832) -> proc_macro::TokenStream {
833 proxy::proxy(attr.into(), input.into()).into()
834}
835
836/// Implements `serde::{Serialize, Deserialize}` for service error enums.
837///
838/// This macro automatically generates both `Serialize` and `Deserialize` implementations for error
839/// types that are used in Varlink service replies.
840///
841/// The macro works in both `std` and `no_std` environments and requires the "error" field
842/// to appear before "parameters" field in JSON for efficient parsing.
843///
844/// # Supported Enum Variants
845///
846/// The macro supports:
847/// - **Unit variants**: Variants without any data
848/// - **Named field variants**: Variants with named fields
849///
850/// Tuple variants are **not** supported.
851///
852/// # Attributes
853///
854/// ## Enum-level attributes
855///
856/// - `interface` - This mandatory attribute specifies the Varlink interface name (e.g.,
857/// "org.varlink.service")
858///
859/// ## Field-level attributes
860///
861/// - `rename = "..."` - Specifies a custom name for the field in the JSON representation
862/// - `borrow` - Enables zero-copy deserialization for types like `Cow<'_, str>`
863///
864/// # Example
865///
866/// ```rust
867/// use std::borrow::Cow;
868/// use zlink::ReplyError;
869///
870/// #[derive(ReplyError)]
871/// #[zlink(interface = "com.example.MyService")]
872/// enum ServiceError<'a> {
873/// // Unit variant - no parameters
874/// NotFound,
875/// PermissionDenied,
876///
877/// // Named field variant - multiple parameters
878/// InvalidInput {
879/// field: String,
880/// reason: String,
881/// },
882///
883/// // Variant with zero-copy deserialization using borrow
884/// CustomError {
885/// #[zlink(borrow)]
886/// message: Cow<'a, str>,
887/// },
888///
889/// // Variant with renamed field
890/// Timeout {
891/// #[zlink(rename = "timeoutSeconds")]
892/// seconds: u32,
893/// },
894/// }
895///
896/// // The macro generates:
897/// // - `Serialize` impl that creates properly tagged enum format
898/// // - `Deserialize` impl that handles the tagged enum format efficiently
899/// ```
900///
901/// # Serialization Format
902///
903/// The generated serialization uses a tagged enum format:
904///
905/// ```json
906/// // Unit variant:
907/// {"error": "NotFound"}
908/// // or with empty parameters:
909/// {"error": "NotFound", "parameters": null}
910///
911/// // Variant with fields:
912/// {
913/// "error": "InvalidInput",
914/// "parameters": {
915/// "field": "username",
916/// "reason": "too short"
917/// }
918/// }
919/// ```
920#[proc_macro_derive(ReplyError, attributes(zlink))]
921pub fn derive_reply_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
922 reply_error::derive_reply_error(input)
923}