Skip to content

Commit a3ea700

Browse files
authored
feat: make sure the error kind is properly transferred when serializing io errors (#111)
## Description I now go through a lot of effort assigning good error kinds and don't want them to be lost! ## Breaking Changes None, except the rpc API will behave slightly differently. But we don't care for that level of stability yet! ## Notes & open questions Note: you can cast an ErrorKind to u32, but the order in the enum is not stable. Hence the boilerplatey match. Fixes #110 ## Change checklist - [ ] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [ ] Tests if relevant. - [ ] All breaking changes documented.
1 parent 845e37c commit a3ea700

File tree

1 file changed

+175
-10
lines changed

1 file changed

+175
-10
lines changed

src/util.rs

Lines changed: 175 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,108 @@ pub mod serde {
1111
use std::{fmt, io};
1212

1313
use serde::{
14-
de::{self, Visitor},
14+
de::{self, SeqAccess, Visitor},
15+
ser::SerializeTuple,
1516
Deserializer, Serializer,
1617
};
1718

19+
fn error_kind_to_u8(kind: io::ErrorKind) -> u8 {
20+
match kind {
21+
io::ErrorKind::AddrInUse => 0,
22+
io::ErrorKind::AddrNotAvailable => 1,
23+
io::ErrorKind::AlreadyExists => 2,
24+
io::ErrorKind::ArgumentListTooLong => 3,
25+
io::ErrorKind::BrokenPipe => 4,
26+
io::ErrorKind::ConnectionAborted => 5,
27+
io::ErrorKind::ConnectionRefused => 6,
28+
io::ErrorKind::ConnectionReset => 7,
29+
io::ErrorKind::CrossesDevices => 8,
30+
io::ErrorKind::Deadlock => 9,
31+
io::ErrorKind::DirectoryNotEmpty => 10,
32+
io::ErrorKind::ExecutableFileBusy => 11,
33+
io::ErrorKind::FileTooLarge => 12,
34+
io::ErrorKind::HostUnreachable => 13,
35+
io::ErrorKind::Interrupted => 14,
36+
io::ErrorKind::InvalidData => 15,
37+
io::ErrorKind::InvalidInput => 17,
38+
io::ErrorKind::IsADirectory => 18,
39+
io::ErrorKind::NetworkDown => 19,
40+
io::ErrorKind::NetworkUnreachable => 20,
41+
io::ErrorKind::NotADirectory => 21,
42+
io::ErrorKind::NotConnected => 22,
43+
io::ErrorKind::NotFound => 23,
44+
io::ErrorKind::NotSeekable => 24,
45+
io::ErrorKind::Other => 25,
46+
io::ErrorKind::OutOfMemory => 26,
47+
io::ErrorKind::PermissionDenied => 27,
48+
io::ErrorKind::QuotaExceeded => 28,
49+
io::ErrorKind::ReadOnlyFilesystem => 29,
50+
io::ErrorKind::ResourceBusy => 30,
51+
io::ErrorKind::StaleNetworkFileHandle => 31,
52+
io::ErrorKind::StorageFull => 32,
53+
io::ErrorKind::TimedOut => 33,
54+
io::ErrorKind::TooManyLinks => 34,
55+
io::ErrorKind::UnexpectedEof => 35,
56+
io::ErrorKind::Unsupported => 36,
57+
io::ErrorKind::WouldBlock => 37,
58+
io::ErrorKind::WriteZero => 38,
59+
_ => 25,
60+
}
61+
}
62+
63+
fn u8_to_error_kind(num: u8) -> io::ErrorKind {
64+
match num {
65+
0 => io::ErrorKind::AddrInUse,
66+
1 => io::ErrorKind::AddrNotAvailable,
67+
2 => io::ErrorKind::AlreadyExists,
68+
3 => io::ErrorKind::ArgumentListTooLong,
69+
4 => io::ErrorKind::BrokenPipe,
70+
5 => io::ErrorKind::ConnectionAborted,
71+
6 => io::ErrorKind::ConnectionRefused,
72+
7 => io::ErrorKind::ConnectionReset,
73+
8 => io::ErrorKind::CrossesDevices,
74+
9 => io::ErrorKind::Deadlock,
75+
10 => io::ErrorKind::DirectoryNotEmpty,
76+
11 => io::ErrorKind::ExecutableFileBusy,
77+
12 => io::ErrorKind::FileTooLarge,
78+
13 => io::ErrorKind::HostUnreachable,
79+
14 => io::ErrorKind::Interrupted,
80+
15 => io::ErrorKind::InvalidData,
81+
// 16 => io::ErrorKind::InvalidFilename,
82+
17 => io::ErrorKind::InvalidInput,
83+
18 => io::ErrorKind::IsADirectory,
84+
19 => io::ErrorKind::NetworkDown,
85+
20 => io::ErrorKind::NetworkUnreachable,
86+
21 => io::ErrorKind::NotADirectory,
87+
22 => io::ErrorKind::NotConnected,
88+
23 => io::ErrorKind::NotFound,
89+
24 => io::ErrorKind::NotSeekable,
90+
25 => io::ErrorKind::Other,
91+
26 => io::ErrorKind::OutOfMemory,
92+
27 => io::ErrorKind::PermissionDenied,
93+
28 => io::ErrorKind::QuotaExceeded,
94+
29 => io::ErrorKind::ReadOnlyFilesystem,
95+
30 => io::ErrorKind::ResourceBusy,
96+
31 => io::ErrorKind::StaleNetworkFileHandle,
97+
32 => io::ErrorKind::StorageFull,
98+
33 => io::ErrorKind::TimedOut,
99+
34 => io::ErrorKind::TooManyLinks,
100+
35 => io::ErrorKind::UnexpectedEof,
101+
36 => io::ErrorKind::Unsupported,
102+
37 => io::ErrorKind::WouldBlock,
103+
38 => io::ErrorKind::WriteZero,
104+
_ => io::ErrorKind::Other,
105+
}
106+
}
107+
18108
pub fn serialize<S>(error: &io::Error, serializer: S) -> Result<S::Ok, S::Error>
19109
where
20110
S: Serializer,
21111
{
22-
// Serialize the error kind and message
23-
serializer.serialize_str(&format!("{:?}:{}", error.kind(), error))
112+
let mut tup = serializer.serialize_tuple(2)?;
113+
tup.serialize_element(&error_kind_to_u8(error.kind()))?;
114+
tup.serialize_element(&error.to_string())?;
115+
tup.end()
24116
}
25117

26118
pub fn deserialize<'de, D>(deserializer: D) -> Result<io::Error, D::Error>
@@ -33,20 +125,93 @@ pub mod serde {
33125
type Value = io::Error;
34126

35127
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
36-
formatter.write_str("an io::Error string representation")
128+
formatter.write_str("a tuple of (u32, String) representing io::Error")
37129
}
38130

39-
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
131+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
40132
where
41-
E: de::Error,
133+
A: SeqAccess<'de>,
42134
{
43-
// For simplicity, create a generic error
44-
// In a real app, you might want to parse the kind from the string
45-
Ok(io::Error::other(value))
135+
let num: u8 = seq
136+
.next_element()?
137+
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
138+
let message: String = seq
139+
.next_element()?
140+
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
141+
let kind = u8_to_error_kind(num);
142+
Ok(io::Error::new(kind, message))
46143
}
47144
}
48145

49-
deserializer.deserialize_str(IoErrorVisitor)
146+
deserializer.deserialize_tuple(2, IoErrorVisitor)
147+
}
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
use std::io::{self, ErrorKind};
153+
154+
use postcard;
155+
use serde::{Deserialize, Serialize};
156+
157+
use super::io_error_serde;
158+
159+
#[derive(Serialize, Deserialize)]
160+
struct TestError(#[serde(with = "io_error_serde")] io::Error);
161+
162+
#[test]
163+
fn test_roundtrip_error_kinds() {
164+
let message = "test error";
165+
let kinds = [
166+
ErrorKind::AddrInUse,
167+
ErrorKind::AddrNotAvailable,
168+
ErrorKind::AlreadyExists,
169+
ErrorKind::ArgumentListTooLong,
170+
ErrorKind::BrokenPipe,
171+
ErrorKind::ConnectionAborted,
172+
ErrorKind::ConnectionRefused,
173+
ErrorKind::ConnectionReset,
174+
ErrorKind::CrossesDevices,
175+
ErrorKind::Deadlock,
176+
ErrorKind::DirectoryNotEmpty,
177+
ErrorKind::ExecutableFileBusy,
178+
ErrorKind::FileTooLarge,
179+
ErrorKind::HostUnreachable,
180+
ErrorKind::Interrupted,
181+
ErrorKind::InvalidData,
182+
// ErrorKind::InvalidFilename,
183+
ErrorKind::InvalidInput,
184+
ErrorKind::IsADirectory,
185+
ErrorKind::NetworkDown,
186+
ErrorKind::NetworkUnreachable,
187+
ErrorKind::NotADirectory,
188+
ErrorKind::NotConnected,
189+
ErrorKind::NotFound,
190+
ErrorKind::NotSeekable,
191+
ErrorKind::Other,
192+
ErrorKind::OutOfMemory,
193+
ErrorKind::PermissionDenied,
194+
ErrorKind::QuotaExceeded,
195+
ErrorKind::ReadOnlyFilesystem,
196+
ErrorKind::ResourceBusy,
197+
ErrorKind::StaleNetworkFileHandle,
198+
ErrorKind::StorageFull,
199+
ErrorKind::TimedOut,
200+
ErrorKind::TooManyLinks,
201+
ErrorKind::UnexpectedEof,
202+
ErrorKind::Unsupported,
203+
ErrorKind::WouldBlock,
204+
ErrorKind::WriteZero,
205+
];
206+
207+
for kind in kinds {
208+
let err = TestError(io::Error::new(kind, message));
209+
let serialized = postcard::to_allocvec(&err).unwrap();
210+
let deserialized: TestError = postcard::from_bytes(&serialized).unwrap();
211+
212+
assert_eq!(err.0.kind(), deserialized.0.kind());
213+
assert_eq!(err.0.to_string(), deserialized.0.to_string());
214+
}
50215
}
51216
}
52217
}

0 commit comments

Comments
 (0)