Skip to main content

zlink_core/varlink_service/
info.rs

1#[cfg(feature = "introspection")]
2use crate::introspect::Type;
3use alloc::{borrow::Cow, vec::Vec};
4use serde::{Deserialize, Serialize};
5
6/// Information about a Varlink service implementation.
7///
8/// This is the return type for the `GetInfo` method of the `org.varlink.service` interface.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[cfg_attr(feature = "introspection", derive(Type))]
11#[cfg_attr(feature = "introspection", zlink(crate = "crate"))]
12pub struct Info<'a> {
13    /// The vendor of the service.
14    #[serde(borrow)]
15    pub vendor: Cow<'a, str>,
16    /// The product name of the service.
17    #[serde(borrow)]
18    pub product: Cow<'a, str>,
19    /// The version of the service.
20    #[serde(borrow)]
21    pub version: Cow<'a, str>,
22    /// The URL associated with the service.
23    #[serde(borrow)]
24    pub url: Cow<'a, str>,
25    /// List of interfaces provided by the service.
26    #[serde(borrow)]
27    pub interfaces: Vec<Cow<'a, str>>,
28}
29
30impl<'a> Info<'a> {
31    /// Create a new `Info` instance.
32    pub fn new(
33        vendor: impl Into<Cow<'a, str>>,
34        product: impl Into<Cow<'a, str>>,
35        version: impl Into<Cow<'a, str>>,
36        url: impl Into<Cow<'a, str>>,
37        interfaces: impl IntoIterator<Item = impl Into<Cow<'a, str>>>,
38    ) -> Self {
39        Self {
40            vendor: vendor.into(),
41            product: product.into(),
42            version: version.into(),
43            url: url.into(),
44            interfaces: interfaces.into_iter().map(Into::into).collect(),
45        }
46    }
47
48    /// Convert this info into an owned version with `'static` lifetime.
49    pub fn into_owned(self) -> Info<'static> {
50        Info {
51            vendor: Cow::Owned(self.vendor.into_owned()),
52            product: Cow::Owned(self.product.into_owned()),
53            version: Cow::Owned(self.version.into_owned()),
54            url: Cow::Owned(self.url.into_owned()),
55            interfaces: self
56                .interfaces
57                .into_iter()
58                .map(|s| Cow::Owned(s.into_owned()))
59                .collect(),
60        }
61    }
62}
63
64/// Owned version of [`Info`] for use with the chain API.
65///
66/// This is a newtype wrapper around `Info<'static>`, allowing it to be deserialized as owned data.
67/// This is required for the chain API because the internal buffer may be reused between stream
68/// iterations.
69///
70/// # Example
71///
72/// ```no_run
73/// use zlink_core::varlink_service::OwnedInfo;
74///
75/// // OwnedInfo can be deserialized from JSON without borrowing.
76/// let json = r#"{"vendor":"Test","product":"Product","version":"1.0","url":"https://example.com","interfaces":["org.varlink.service"]}"#;
77/// let info: OwnedInfo = serde_json::from_str(json).unwrap();
78/// ```
79#[derive(Debug, Clone, PartialEq, Serialize)]
80pub struct OwnedInfo(Info<'static>);
81
82impl OwnedInfo {
83    /// Create a new `OwnedInfo` instance.
84    pub fn new(
85        vendor: impl Into<Cow<'static, str>>,
86        product: impl Into<Cow<'static, str>>,
87        version: impl Into<Cow<'static, str>>,
88        url: impl Into<Cow<'static, str>>,
89        interfaces: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
90    ) -> Self {
91        Self(Info::new(vendor, product, version, url, interfaces))
92    }
93
94    /// Returns a reference to the inner `Info`.
95    pub fn inner(&self) -> &Info<'static> {
96        &self.0
97    }
98
99    /// Consumes self and returns the inner `Info`.
100    pub fn into_inner(self) -> Info<'static> {
101        self.0
102    }
103}
104
105impl core::ops::Deref for OwnedInfo {
106    type Target = Info<'static>;
107
108    fn deref(&self) -> &Self::Target {
109        &self.0
110    }
111}
112
113impl core::ops::DerefMut for OwnedInfo {
114    fn deref_mut(&mut self) -> &mut Self::Target {
115        &mut self.0
116    }
117}
118
119impl<'a> From<Info<'a>> for OwnedInfo {
120    fn from(info: Info<'a>) -> Self {
121        Self(info.into_owned())
122    }
123}
124
125impl<'de> Deserialize<'de> for OwnedInfo {
126    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127    where
128        D: serde::Deserializer<'de>,
129    {
130        use alloc::string::String;
131
132        // Deserialize into owned strings directly.
133        #[derive(Deserialize)]
134        struct InfoOwned {
135            vendor: String,
136            product: String,
137            version: String,
138            url: String,
139            interfaces: Vec<String>,
140        }
141
142        let info = InfoOwned::deserialize(deserializer)?;
143        Ok(Self(Info {
144            vendor: Cow::Owned(info.vendor),
145            product: Cow::Owned(info.product),
146            version: Cow::Owned(info.version),
147            url: Cow::Owned(info.url),
148            interfaces: info.interfaces.into_iter().map(Cow::Owned).collect(),
149        }))
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn serialization() {
159        let mut interfaces = Vec::new();
160        interfaces.push("com.example.test");
161
162        let info = Info::new(
163            "Test Vendor",
164            "Test Product",
165            "1.0.0",
166            "https://example.com",
167            interfaces,
168        );
169
170        let json = serde_json::to_string(&info).unwrap();
171
172        assert!(json.contains("Test Vendor"));
173        assert!(json.contains("com.example.test"));
174    }
175
176    #[test]
177    fn deserialization() {
178        let json = r#"{
179            "vendor": "Test Vendor",
180            "product": "Test Product",
181            "version": "1.0.0",
182            "url": "https://example.com",
183            "interfaces": ["com.example.test", "com.example.other"]
184        }"#;
185
186        let info: Info<'_> = serde_json::from_str(json).unwrap();
187
188        assert_eq!(info.vendor, "Test Vendor");
189        assert_eq!(info.product, "Test Product");
190        assert_eq!(info.version, "1.0.0");
191        assert_eq!(info.url, "https://example.com");
192        assert_eq!(info.interfaces.len(), 2);
193        assert_eq!(info.interfaces[0], "com.example.test");
194        assert_eq!(info.interfaces[1], "com.example.other");
195    }
196
197    #[test]
198    fn round_trip_serialization() {
199        let mut interfaces = Vec::new();
200        interfaces.push("com.example.test");
201        interfaces.push("com.example.other");
202
203        let original = Info::new(
204            "Test Vendor",
205            "Test Product",
206            "1.0.0",
207            "https://example.com",
208            interfaces,
209        );
210
211        // Serialize to JSON
212        let json = serde_json::to_string(&original).unwrap();
213
214        // Deserialize back from JSON
215        let deserialized: Info<'_> = serde_json::from_str(&json).unwrap();
216
217        // Verify they are equal
218        assert_eq!(original, deserialized);
219    }
220}