Skip to main content

zlink_core/varlink_service/
api.rs

1use alloc::borrow::Cow;
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "introspection")]
5use crate::introspect;
6
7use crate::ReplyError;
8
9#[cfg(feature = "idl")]
10use super::InterfaceDescription;
11use super::{Info, OwnedInfo};
12
13/// `org.varlink.service` interface methods.
14#[derive(Debug, Serialize, Deserialize)]
15#[serde(tag = "method", content = "parameters")]
16pub enum Method<'a> {
17    /// Get information about the Varlink service.
18    #[serde(rename = "org.varlink.service.GetInfo")]
19    GetInfo,
20    /// Get the description of the specified interface.
21    #[serde(rename = "org.varlink.service.GetInterfaceDescription")]
22    GetInterfaceDescription {
23        /// The interface to get the description for.
24        interface: &'a str,
25    },
26}
27
28/// `org.varlink.service` interface replies.
29///
30/// This enum represents all possible replies from the varlink service interface methods.
31#[derive(Debug, Serialize)]
32#[cfg_attr(feature = "idl-parse", derive(Deserialize))]
33#[serde(untagged)]
34pub enum Reply<'a> {
35    /// Reply for `GetInfo` method.
36    #[serde(borrow)]
37    Info(Info<'a>),
38    /// Reply for `GetInterfaceDescription` method.
39    /// Note: InterfaceDescription only supports 'static lifetime for deserialization.
40    #[cfg(feature = "idl")]
41    InterfaceDescription(InterfaceDescription<'static>),
42}
43
44/// Owned version of [`Reply`] for use with the chain API.
45///
46/// This type uses owned types ([`OwnedInfo`]) instead of borrowed types, allowing it to be
47/// deserialized as owned data. This is required for the chain API because the internal buffer
48/// may be reused between stream iterations.
49#[derive(Debug, Serialize)]
50#[cfg_attr(any(not(feature = "idl"), feature = "idl-parse"), derive(Deserialize))]
51#[serde(untagged)]
52pub enum OwnedReply {
53    /// Reply for `GetInfo` method.
54    Info(OwnedInfo),
55    /// Reply for `GetInterfaceDescription` method.
56    #[cfg(feature = "idl")]
57    InterfaceDescription(InterfaceDescription<'static>),
58}
59
60#[cfg(feature = "idl")]
61impl<'a> From<Reply<'a>> for OwnedReply {
62    fn from(reply: Reply<'a>) -> Self {
63        match reply {
64            Reply::Info(info) => OwnedReply::Info(info.into()),
65            Reply::InterfaceDescription(desc) => OwnedReply::InterfaceDescription(desc),
66        }
67    }
68}
69
70#[cfg(not(feature = "idl"))]
71impl<'a> From<Reply<'a>> for OwnedReply {
72    fn from(reply: Reply<'a>) -> Self {
73        match reply {
74            Reply::Info(info) => OwnedReply::Info(info.into()),
75        }
76    }
77}
78
79/// Errors that can be returned by the `org.varlink.service` interface.
80#[derive(Debug, Clone, PartialEq, ReplyError)]
81#[cfg_attr(feature = "introspection", derive(introspect::ReplyError))]
82#[zlink(interface = "org.varlink.service")]
83#[cfg_attr(feature = "introspection", zlink(crate = "crate"))]
84pub enum Error<'a> {
85    /// The requested interface was not found.
86    InterfaceNotFound {
87        /// The interface that was not found.
88        #[zlink(borrow)]
89        interface: Cow<'a, str>,
90    },
91    /// The requested method was not found.
92    MethodNotFound {
93        /// The method that was not found.
94        #[zlink(borrow)]
95        method: Cow<'a, str>,
96    },
97    /// The interface defines the requested method, but the service does not implement it.
98    MethodNotImplemented {
99        /// The method that is not implemented.
100        #[zlink(borrow)]
101        method: Cow<'a, str>,
102    },
103    /// One of the passed parameters is invalid.
104    InvalidParameter {
105        /// The parameter that is invalid.
106        #[zlink(borrow)]
107        parameter: Cow<'a, str>,
108    },
109    /// Client is denied access.
110    PermissionDenied,
111    /// Method is expected to be called with 'more' set to true, but wasn't.
112    ExpectedMore,
113}
114
115impl Error<'_> {
116    /// Convert this error into an owned version with `'static` lifetime.
117    ///
118    /// This is useful when you need to store or propagate the error.
119    pub fn into_owned(self) -> Error<'static> {
120        match self {
121            Error::InterfaceNotFound { interface } => Error::InterfaceNotFound {
122                interface: Cow::Owned(interface.into_owned()),
123            },
124            Error::MethodNotFound { method } => Error::MethodNotFound {
125                method: Cow::Owned(method.into_owned()),
126            },
127            Error::MethodNotImplemented { method } => Error::MethodNotImplemented {
128                method: Cow::Owned(method.into_owned()),
129            },
130            Error::InvalidParameter { parameter } => Error::InvalidParameter {
131                parameter: Cow::Owned(parameter.into_owned()),
132            },
133            Error::PermissionDenied => Error::PermissionDenied,
134            Error::ExpectedMore => Error::ExpectedMore,
135        }
136    }
137}
138
139impl core::error::Error for Error<'_> {
140    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
141        None
142    }
143}
144
145impl core::fmt::Display for Error<'_> {
146    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
147        match self {
148            Error::InterfaceNotFound { interface } => {
149                write!(f, "Interface not found: {interface}")
150            }
151            Error::MethodNotFound { method } => {
152                write!(f, "Method not found: {method}")
153            }
154            Error::InvalidParameter { parameter } => {
155                write!(f, "Invalid parameter: {parameter}")
156            }
157            Error::PermissionDenied => {
158                write!(f, "Permission denied")
159            }
160            Error::ExpectedMore => {
161                write!(f, "Expected more")
162            }
163            Error::MethodNotImplemented { method } => {
164                write!(f, "Method not implemented: {method}")
165            }
166        }
167    }
168}
169
170/// Owned version of [`Error`] for use with the chain API.
171///
172/// This is a newtype wrapper around `Error<'static>`, allowing it to be deserialized as owned data.
173/// This is required for the chain API because the internal buffer may be reused between stream
174/// iterations.
175#[derive(Debug, Clone, PartialEq)]
176pub struct OwnedError(Error<'static>);
177
178impl OwnedError {
179    /// Returns a reference to the inner `Error`.
180    pub fn inner(&self) -> &Error<'static> {
181        &self.0
182    }
183
184    /// Consumes self and returns the inner `Error`.
185    pub fn into_inner(self) -> Error<'static> {
186        self.0
187    }
188}
189
190impl core::ops::Deref for OwnedError {
191    type Target = Error<'static>;
192
193    fn deref(&self) -> &Self::Target {
194        &self.0
195    }
196}
197
198impl core::ops::DerefMut for OwnedError {
199    fn deref_mut(&mut self) -> &mut Self::Target {
200        &mut self.0
201    }
202}
203
204impl core::error::Error for OwnedError {
205    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
206        self.0.source()
207    }
208}
209
210impl core::fmt::Display for OwnedError {
211    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212        self.0.fmt(f)
213    }
214}
215
216impl<'a> From<Error<'a>> for OwnedError {
217    fn from(err: Error<'a>) -> Self {
218        Self(err.into_owned())
219    }
220}
221
222impl Serialize for OwnedError {
223    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
224    where
225        S: serde::Serializer,
226    {
227        self.0.serialize(serializer)
228    }
229}
230
231impl<'de> Deserialize<'de> for OwnedError {
232    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
233    where
234        D: serde::Deserializer<'de>,
235    {
236        use alloc::string::String;
237
238        // Helper enum that deserializes into owned strings.
239        #[derive(Deserialize)]
240        #[serde(tag = "error", content = "parameters")]
241        enum ErrorHelper {
242            #[serde(rename = "org.varlink.service.InterfaceNotFound")]
243            InterfaceNotFound { interface: String },
244            #[serde(rename = "org.varlink.service.MethodNotFound")]
245            MethodNotFound { method: String },
246            #[serde(rename = "org.varlink.service.MethodNotImplemented")]
247            MethodNotImplemented { method: String },
248            #[serde(rename = "org.varlink.service.InvalidParameter")]
249            InvalidParameter { parameter: String },
250            #[serde(rename = "org.varlink.service.PermissionDenied")]
251            PermissionDenied,
252            #[serde(rename = "org.varlink.service.ExpectedMore")]
253            ExpectedMore,
254        }
255
256        let helper = ErrorHelper::deserialize(deserializer)?;
257        let error = match helper {
258            ErrorHelper::InterfaceNotFound { interface } => Error::InterfaceNotFound {
259                interface: Cow::Owned(interface),
260            },
261            ErrorHelper::MethodNotFound { method } => Error::MethodNotFound {
262                method: Cow::Owned(method),
263            },
264            ErrorHelper::MethodNotImplemented { method } => Error::MethodNotImplemented {
265                method: Cow::Owned(method),
266            },
267            ErrorHelper::InvalidParameter { parameter } => Error::InvalidParameter {
268                parameter: Cow::Owned(parameter),
269            },
270            ErrorHelper::PermissionDenied => Error::PermissionDenied,
271            ErrorHelper::ExpectedMore => Error::ExpectedMore,
272        };
273        Ok(Self(error))
274    }
275}
276
277/// Result type for Varlink service methods.
278pub type Result<'a, T> = core::result::Result<T, Error<'a>>;
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn error_serialization() {
286        let err = Error::InterfaceNotFound {
287            interface: Cow::Borrowed("com.example.missing"),
288        };
289
290        let json = serialize_error(&err);
291        assert!(json.contains("org.varlink.service.InterfaceNotFound"));
292        assert!(json.contains("com.example.missing"));
293
294        let err = Error::PermissionDenied;
295
296        let json = serialize_error(&err);
297        assert!(json.contains("org.varlink.service.PermissionDenied"));
298    }
299
300    #[test]
301    fn error_deserialization() {
302        // Test error with parameter.
303        let json = r#"{"error":"org.varlink.service.InterfaceNotFound","parameters":{"interface":"com.example.missing"}}"#;
304        let err: Error<'_> = deserialize_error(json);
305        assert_eq!(
306            err,
307            Error::InterfaceNotFound {
308                interface: Cow::Borrowed("com.example.missing")
309            }
310        );
311
312        // Test error without parameters.
313        let json = r#"{"error":"org.varlink.service.PermissionDenied"}"#;
314        let err: Error<'_> = deserialize_error(json);
315        assert_eq!(err, Error::PermissionDenied);
316
317        // Test MethodNotFound error.
318        let json = r#"{"error":"org.varlink.service.MethodNotFound","parameters":{"method":"NonExistentMethod"}}"#;
319        let err: Error<'_> = deserialize_error(json);
320        assert_eq!(
321            err,
322            Error::MethodNotFound {
323                method: Cow::Borrowed("NonExistentMethod")
324            }
325        );
326
327        // Test InvalidParameter error.
328        let json = r#"{"error":"org.varlink.service.InvalidParameter","parameters":{"parameter":"invalid_param"}}"#;
329        let err: Error<'_> = deserialize_error(json);
330        assert_eq!(
331            err,
332            Error::InvalidParameter {
333                parameter: Cow::Borrowed("invalid_param")
334            }
335        );
336
337        // Test MethodNotImplemented error.
338        let json = r#"{"error":"org.varlink.service.MethodNotImplemented","parameters":{"method":"UnimplementedMethod"}}"#;
339        let err: Error<'_> = deserialize_error(json);
340        assert_eq!(
341            err,
342            Error::MethodNotImplemented {
343                method: Cow::Borrowed("UnimplementedMethod")
344            }
345        );
346
347        // Test ExpectedMore error.
348        let json = r#"{"error":"org.varlink.service.ExpectedMore"}"#;
349        let err: Error<'_> = deserialize_error(json);
350        assert_eq!(err, Error::ExpectedMore);
351    }
352
353    #[test]
354    fn error_round_trip_serialization() {
355        // Test with error that has parameters.
356        let original = Error::InterfaceNotFound {
357            interface: Cow::Borrowed("com.example.missing"),
358        };
359
360        test_round_trip_serialize(&original);
361
362        // Test with error that has no parameters.
363        let original = Error::PermissionDenied;
364
365        test_round_trip_serialize(&original);
366    }
367
368    #[test]
369    fn into_owned() {
370        let borrowed = Error::InterfaceNotFound {
371            interface: Cow::Borrowed("test.interface"),
372        };
373        let owned = borrowed.into_owned();
374        assert_eq!(
375            owned,
376            Error::InterfaceNotFound {
377                interface: Cow::Owned("test.interface".into())
378            }
379        );
380    }
381
382    // Helper function to serialize Error to JSON string.
383    fn serialize_error(err: &Error<'_>) -> String {
384        serde_json::to_string(err).unwrap()
385    }
386
387    // Helper function to deserialize JSON string to Error.
388    fn deserialize_error(json: &str) -> Error<'_> {
389        serde_json::from_str(json).unwrap()
390    }
391
392    // Helper function for round-trip serialization test.
393    fn test_round_trip_serialize(original: &Error<'_>) {
394        let json = serde_json::to_string(original).unwrap();
395        let deserialized: Error<'_> = serde_json::from_str(&json).unwrap();
396        assert_eq!(*original, deserialized);
397    }
398}