Skip to content

Commit bb2eb1f

Browse files
authored
Merge pull request #176 from zeenix/error-borrows
✨ macros: Allow borrowing deserializer lifetime in ReplyError
2 parents 1ff246b + ce0e0a7 commit bb2eb1f

10 files changed

Lines changed: 220 additions & 78 deletions

File tree

zlink-core/src/connection/read_connection.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@ impl<Read: ReadHalf> ReadConnection<Read> {
9797
{
9898
#[derive(Debug, Deserialize)]
9999
#[serde(untagged)]
100-
enum ReplyMsg<ReplyParams, ReplyError> {
101-
Varlink(varlink_service::Error),
100+
enum ReplyMsg<'m, ReplyParams, ReplyError> {
101+
#[serde(borrow)]
102+
Varlink(varlink_service::Error<'m>),
102103
Error(ReplyError),
103104
Reply(Reply<ReplyParams>),
104105
}
105106

106107
let recv_result = self
107-
.read_message::<ReplyMsg<ReplyParams, ReplyError>>()
108+
.read_message::<ReplyMsg<'_, ReplyParams, ReplyError>>()
108109
.await?;
109110

110111
#[cfg(feature = "std")]
@@ -114,7 +115,7 @@ impl<Read: ReadHalf> ReadConnection<Read> {
114115

115116
let result = match msg {
116117
// Varlink service interface error need to be returned as the top-level error.
117-
ReplyMsg::Varlink(e) => Err(crate::Error::VarlinkService(e)),
118+
ReplyMsg::Varlink(e) => Err(crate::Error::VarlinkService(e.into_owned())),
118119
ReplyMsg::Error(e) => Ok(Err(e)),
119120
ReplyMsg::Reply(reply) => Ok(Ok(reply)),
120121
};

zlink-core/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub enum Error {
2525
/// Missing required parameters.
2626
MissingParameters,
2727
/// A general service error.
28-
VarlinkService(crate::varlink_service::Error),
28+
VarlinkService(crate::varlink_service::Error<'static>),
2929
}
3030

3131
/// The Result type for the zlink crate.

zlink-core/src/varlink_service/api.rs

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloc::string::String;
1+
use alloc::borrow::Cow;
22
use serde::{Deserialize, Serialize};
33

44
#[cfg(feature = "introspection")]
@@ -46,40 +46,68 @@ pub enum Reply<'a> {
4646
#[cfg_attr(feature = "introspection", derive(introspect::ReplyError))]
4747
#[zlink(interface = "org.varlink.service")]
4848
#[cfg_attr(feature = "introspection", zlink(crate = "crate"))]
49-
pub enum Error {
49+
pub enum Error<'a> {
5050
/// The requested interface was not found.
5151
InterfaceNotFound {
5252
/// The interface that was not found.
53-
interface: String,
53+
#[zlink(borrow)]
54+
interface: Cow<'a, str>,
5455
},
5556
/// The requested method was not found.
5657
MethodNotFound {
5758
/// The method that was not found.
58-
method: String,
59+
#[zlink(borrow)]
60+
method: Cow<'a, str>,
5961
},
6062
/// The interface defines the requested method, but the service does not implement it.
6163
MethodNotImplemented {
6264
/// The method that is not implemented.
63-
method: String,
65+
#[zlink(borrow)]
66+
method: Cow<'a, str>,
6467
},
6568
/// One of the passed parameters is invalid.
6669
InvalidParameter {
6770
/// The parameter that is invalid.
68-
parameter: String,
71+
#[zlink(borrow)]
72+
parameter: Cow<'a, str>,
6973
},
7074
/// Client is denied access.
7175
PermissionDenied,
7276
/// Method is expected to be called with 'more' set to true, but wasn't.
7377
ExpectedMore,
7478
}
7579

76-
impl core::error::Error for Error {
80+
impl Error<'_> {
81+
/// Convert this error into an owned version with `'static` lifetime.
82+
///
83+
/// This is useful when you need to store or propagate the error.
84+
pub fn into_owned(self) -> Error<'static> {
85+
match self {
86+
Error::InterfaceNotFound { interface } => Error::InterfaceNotFound {
87+
interface: Cow::Owned(interface.into_owned()),
88+
},
89+
Error::MethodNotFound { method } => Error::MethodNotFound {
90+
method: Cow::Owned(method.into_owned()),
91+
},
92+
Error::MethodNotImplemented { method } => Error::MethodNotImplemented {
93+
method: Cow::Owned(method.into_owned()),
94+
},
95+
Error::InvalidParameter { parameter } => Error::InvalidParameter {
96+
parameter: Cow::Owned(parameter.into_owned()),
97+
},
98+
Error::PermissionDenied => Error::PermissionDenied,
99+
Error::ExpectedMore => Error::ExpectedMore,
100+
}
101+
}
102+
}
103+
104+
impl core::error::Error for Error<'_> {
77105
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
78106
None
79107
}
80108
}
81109

82-
impl core::fmt::Display for Error {
110+
impl core::fmt::Display for Error<'_> {
83111
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84112
match self {
85113
Error::InterfaceNotFound { interface } => {
@@ -105,17 +133,16 @@ impl core::fmt::Display for Error {
105133
}
106134

107135
/// Result type for Varlink service methods.
108-
pub type Result<T> = core::result::Result<T, Error>;
136+
pub type Result<'a, T> = core::result::Result<T, Error<'a>>;
109137

110138
#[cfg(test)]
111139
mod tests {
112140
use super::*;
113-
use core::str::FromStr;
114141

115142
#[test]
116143
fn error_serialization() {
117144
let err = Error::InterfaceNotFound {
118-
interface: String::from_str("com.example.missing").unwrap(),
145+
interface: Cow::Borrowed("com.example.missing"),
119146
};
120147

121148
let json = serialize_error(&err);
@@ -130,86 +157,100 @@ mod tests {
130157

131158
#[test]
132159
fn error_deserialization() {
133-
// Test error with parameter
160+
// Test error with parameter.
134161
let json = r#"{"error":"org.varlink.service.InterfaceNotFound","parameters":{"interface":"com.example.missing"}}"#;
135-
let err = deserialize_error(json);
162+
let err: Error<'_> = deserialize_error(json);
136163
assert_eq!(
137164
err,
138165
Error::InterfaceNotFound {
139-
interface: String::from_str("com.example.missing").unwrap()
166+
interface: Cow::Borrowed("com.example.missing")
140167
}
141168
);
142169

143-
// Test error without parameters
170+
// Test error without parameters.
144171
let json = r#"{"error":"org.varlink.service.PermissionDenied"}"#;
145-
let err = deserialize_error(json);
172+
let err: Error<'_> = deserialize_error(json);
146173
assert_eq!(err, Error::PermissionDenied);
147174

148-
// Test MethodNotFound error
175+
// Test MethodNotFound error.
149176
let json = r#"{"error":"org.varlink.service.MethodNotFound","parameters":{"method":"NonExistentMethod"}}"#;
150-
let err = deserialize_error(json);
177+
let err: Error<'_> = deserialize_error(json);
151178
assert_eq!(
152179
err,
153180
Error::MethodNotFound {
154-
method: String::from_str("NonExistentMethod").unwrap()
181+
method: Cow::Borrowed("NonExistentMethod")
155182
}
156183
);
157184

158-
// Test InvalidParameter error
185+
// Test InvalidParameter error.
159186
let json = r#"{"error":"org.varlink.service.InvalidParameter","parameters":{"parameter":"invalid_param"}}"#;
160-
let err = deserialize_error(json);
187+
let err: Error<'_> = deserialize_error(json);
161188
assert_eq!(
162189
err,
163190
Error::InvalidParameter {
164-
parameter: String::from_str("invalid_param").unwrap()
191+
parameter: Cow::Borrowed("invalid_param")
165192
}
166193
);
167194

168-
// Test MethodNotImplemented error
195+
// Test MethodNotImplemented error.
169196
let json = r#"{"error":"org.varlink.service.MethodNotImplemented","parameters":{"method":"UnimplementedMethod"}}"#;
170-
let err = deserialize_error(json);
197+
let err: Error<'_> = deserialize_error(json);
171198
assert_eq!(
172199
err,
173200
Error::MethodNotImplemented {
174-
method: String::from_str("UnimplementedMethod").unwrap()
201+
method: Cow::Borrowed("UnimplementedMethod")
175202
}
176203
);
177204

178-
// Test ExpectedMore error
205+
// Test ExpectedMore error.
179206
let json = r#"{"error":"org.varlink.service.ExpectedMore"}"#;
180-
let err = deserialize_error(json);
207+
let err: Error<'_> = deserialize_error(json);
181208
assert_eq!(err, Error::ExpectedMore);
182209
}
183210

184211
#[test]
185212
fn error_round_trip_serialization() {
186-
// Test with error that has parameters
213+
// Test with error that has parameters.
187214
let original = Error::InterfaceNotFound {
188-
interface: String::from_str("com.example.missing").unwrap(),
215+
interface: Cow::Borrowed("com.example.missing"),
189216
};
190217

191218
test_round_trip_serialize(&original);
192219

193-
// Test with error that has no parameters
220+
// Test with error that has no parameters.
194221
let original = Error::PermissionDenied;
195222

196223
test_round_trip_serialize(&original);
197224
}
198225

199-
// Helper function to serialize Error to JSON string, abstracting std vs nostd differences
200-
fn serialize_error(err: &Error) -> String {
226+
#[test]
227+
fn into_owned() {
228+
let borrowed = Error::InterfaceNotFound {
229+
interface: Cow::Borrowed("test.interface"),
230+
};
231+
let owned = borrowed.into_owned();
232+
assert_eq!(
233+
owned,
234+
Error::InterfaceNotFound {
235+
interface: Cow::Owned("test.interface".into())
236+
}
237+
);
238+
}
239+
240+
// Helper function to serialize Error to JSON string.
241+
fn serialize_error(err: &Error<'_>) -> String {
201242
serde_json::to_string(err).unwrap()
202243
}
203244

204-
// Helper function to deserialize JSON string to Error, abstracting std vs nostd differences
205-
fn deserialize_error(json: &str) -> Error {
245+
// Helper function to deserialize JSON string to Error.
246+
fn deserialize_error(json: &str) -> Error<'_> {
206247
serde_json::from_str(json).unwrap()
207248
}
208249

209-
// Helper function for round-trip serialization test, abstracting std vs nostd differences
210-
fn test_round_trip_serialize(original: &Error) {
250+
// Helper function for round-trip serialization test.
251+
fn test_round_trip_serialize(original: &Error<'_>) {
211252
let json = serde_json::to_string(original).unwrap();
212-
let deserialized: Error = serde_json::from_str(&json).unwrap();
253+
let deserialized: Error<'_> = serde_json::from_str(&json).unwrap();
213254
assert_eq!(*original, deserialized);
214255
}
215256
}

zlink-core/src/varlink_service/proxy.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use super::{Error, Info, InterfaceDescription};
2929
/// # let mut conn: Connection<zlink_core::connection::socket::impl_for_doc::Socket> = todo!();
3030
/// // For a single interface, use the provided Reply enum directly
3131
/// let chain = conn
32-
/// .chain_get_info::<Reply<'_>, Error>()?
32+
/// .chain_get_info::<Reply<'_>, Error<'_>>()?
3333
/// .get_interface_description("org.example.interface")?
3434
/// .get_info()?;
3535
///
@@ -68,15 +68,16 @@ use super::{Error, Info, InterfaceDescription};
6868
///
6969
/// #[derive(Debug, Deserialize)]
7070
/// #[serde(untagged)]
71-
/// enum CombinedError {
72-
/// VarlinkService(Error),
71+
/// enum CombinedError<'a> {
72+
/// #[serde(borrow)]
73+
/// VarlinkService(Error<'a>),
7374
/// // Add other interface error types here
74-
/// // OtherInterface(other_interface::Error),
75+
/// // OtherInterface(other_interface::Error<'a>),
7576
/// }
7677
///
7778
/// // Then use the combined types for cross-interface chaining
7879
/// let combined_chain = conn
79-
/// .chain_get_info::<CombinedReply<'_>, CombinedError>()?;
80+
/// .chain_get_info::<CombinedReply<'_>, CombinedError<'_>>()?;
8081
/// // .other_interface_method()?; // Chain calls from other interfaces
8182
///
8283
/// let combined_replies = combined_chain.send().await?;
@@ -121,7 +122,7 @@ pub trait Proxy {
121122
///
122123
/// Two-layer result: outer for connection errors, inner for method errors. On success, contains
123124
/// service information as [`Info`].
124-
async fn get_info(&mut self) -> crate::Result<core::result::Result<Info<'_>, Error>>;
125+
async fn get_info(&mut self) -> crate::Result<core::result::Result<Info<'_>, Error<'_>>>;
125126

126127
/// Get the IDL description of an interface.
127128
///
@@ -137,7 +138,7 @@ pub trait Proxy {
137138
async fn get_interface_description(
138139
&mut self,
139140
interface: &str,
140-
) -> crate::Result<core::result::Result<InterfaceDescription<'static>, Error>>;
141+
) -> crate::Result<core::result::Result<InterfaceDescription<'static>, Error<'_>>>;
141142
}
142143

143144
#[cfg(test)]
@@ -159,10 +160,10 @@ mod tests {
159160
// Use the provided Reply enum from the varlink service module
160161
use super::{super::Reply, Error};
161162

162-
// Test that we can create the chain APIs
163-
let _chain1 = conn.chain_get_info::<Reply<'_>, Error>()?;
163+
// Test that we can create the chain APIs.
164+
let _chain1 = conn.chain_get_info::<Reply<'_>, Error<'_>>()?;
164165
let _chain2 =
165-
conn.chain_get_interface_description::<Reply<'_>, Error>("org.varlink.service")?;
166+
conn.chain_get_interface_description::<Reply<'_>, Error<'_>>("org.varlink.service")?;
166167

167168
Ok(())
168169
}
@@ -180,9 +181,9 @@ mod tests {
180181

181182
use super::{super::Reply, Error};
182183

183-
// Test that we can chain calls using extension methods and actually read replies
184+
// Test that we can chain calls using extension methods and actually read replies.
184185
let chained = conn
185-
.chain_get_info::<Reply<'_>, Error>()?
186+
.chain_get_info::<Reply<'_>, Error<'_>>()?
186187
.get_interface_description("org.varlink.service")?
187188
.get_info()?;
188189

0 commit comments

Comments
 (0)