1use core::fmt;
7use core::str::FromStr;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[non_exhaustive]
16pub enum FdError {
17 InvalidValue(i32),
22 TooLarge(i32),
29}
30
31impl fmt::Display for FdError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::InvalidValue(v) => write!(f, "invalid FD value: {v} (must be non-negative)"),
35 Self::TooLarge(v) => write!(f, "FD too large: {v}"),
36 }
37 }
38}
39
40#[cfg(feature = "std")]
41impl std::error::Error for FdError {}
42
43#[repr(transparent)]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct Fd(i32);
62
63impl Fd {
64 #[cfg(target_os = "linux")]
66 pub const MAX: i32 = 1_048_576;
67
68 #[cfg(not(target_os = "linux"))]
69 pub const MAX: i32 = i32::MAX;
70
71 pub const STDIN: Self = Self(0);
73
74 pub const STDOUT: Self = Self(1);
76
77 pub const STDERR: Self = Self(2);
79
80 pub const fn new(value: i32) -> Result<Self, FdError> {
86 if value < 0 {
87 return Err(FdError::InvalidValue(value));
88 }
89 if value > Self::MAX {
90 return Err(FdError::TooLarge(value));
91 }
92 Ok(Self(value))
93 }
94
95 #[must_use]
111 #[inline]
112 pub const fn new_unchecked(value: i32) -> Self {
113 Self(value)
114 }
115
116 #[must_use]
118 #[inline]
119 pub const fn as_i32(&self) -> i32 {
120 self.0
121 }
122
123 #[must_use]
125 #[inline]
126 pub const fn is_standard(&self) -> bool {
127 self.0 >= 0 && self.0 <= 2
128 }
129
130 #[must_use]
132 #[inline]
133 pub const fn is_stdin(&self) -> bool {
134 self.0 == 0
135 }
136
137 #[must_use]
139 #[inline]
140 pub const fn is_stdout(&self) -> bool {
141 self.0 == 1
142 }
143
144 #[must_use]
146 #[inline]
147 pub const fn is_stderr(&self) -> bool {
148 self.0 == 2
149 }
150}
151
152impl TryFrom<i32> for Fd {
153 type Error = FdError;
154
155 fn try_from(value: i32) -> Result<Self, Self::Error> {
156 Self::new(value)
157 }
158}
159
160impl TryFrom<u32> for Fd {
161 type Error = FdError;
162
163 fn try_from(value: u32) -> Result<Self, Self::Error> {
164 if value > i32::MAX as u32 {
165 return Err(FdError::TooLarge(i32::MAX));
166 }
167 #[allow(clippy::cast_possible_wrap)]
168 Self::new(value as i32)
169 }
170}
171
172impl FromStr for Fd {
173 type Err = FdError;
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 let value = s.parse::<i32>().map_err(|_| FdError::InvalidValue(-1))?;
177 Self::new(value)
178 }
179}
180
181impl fmt::Display for Fd {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 write!(f, "{}", self.0)
184 }
185}
186
187#[cfg(feature = "arbitrary")]
188impl<'a> arbitrary::Arbitrary<'a> for Fd {
189 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
190 let value = i32::arbitrary(u)?;
191 if (0..=Self::MAX).contains(&value) {
192 Ok(Self(value))
193 } else {
194 Ok(Self(0))
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use core::convert::TryInto;
203
204 #[test]
205 fn test_new_valid() {
206 let fd = Fd::new(0).unwrap();
207 assert_eq!(fd.as_i32(), 0);
208 let fd = Fd::new(100).unwrap();
209 assert_eq!(fd.as_i32(), 100);
210 }
211
212 #[test]
213 fn test_new_negative() {
214 assert!(matches!(Fd::new(-1), Err(FdError::InvalidValue(-1))));
215 }
216
217 #[test]
218 fn test_new_too_large() {
219 assert!(matches!(Fd::new(i32::MAX), Err(FdError::TooLarge(_))));
220 }
221
222 #[test]
223 fn test_try_from_i32() {
224 let fd: Fd = 100i32.try_into().unwrap();
225 assert_eq!(fd.as_i32(), 100);
226 }
227
228 #[test]
229 fn test_try_from_u32() {
230 let fd: Fd = 100u32.try_into().unwrap();
231 assert_eq!(fd.as_i32(), 100);
232 }
233
234 #[test]
235 fn test_try_from_u32_too_large() {
236 assert!(matches!(
237 TryInto::<Fd>::try_into(i32::MAX as u32 + 1),
238 Err(FdError::TooLarge(_))
239 ));
240 }
241
242 #[test]
243 fn test_from_str() {
244 let fd: Fd = "100".parse().unwrap();
245 assert_eq!(fd.as_i32(), 100);
246 }
247
248 #[test]
249 fn test_from_str_error() {
250 assert!(matches!(
251 ("-1").parse::<Fd>(),
252 Err(FdError::InvalidValue(_))
253 ));
254 assert!("abc".parse::<Fd>().is_err());
255 }
256
257 #[test]
258 fn test_display() {
259 let fd = Fd::new(100).unwrap();
260 assert_eq!(format!("{}", fd), "100");
261 }
262
263 #[test]
264 fn test_constants() {
265 assert_eq!(Fd::STDIN.as_i32(), 0);
266 assert_eq!(Fd::STDOUT.as_i32(), 1);
267 assert_eq!(Fd::STDERR.as_i32(), 2);
268 }
269
270 #[test]
271 fn test_is_standard() {
272 assert!(Fd::STDIN.is_standard());
273 assert!(Fd::STDOUT.is_standard());
274 assert!(Fd::STDERR.is_standard());
275 let fd = Fd::new(100).unwrap();
276 assert!(!fd.is_standard());
277 }
278
279 #[test]
280 fn test_is_stdin() {
281 assert!(Fd::STDIN.is_stdin());
282 assert!(!Fd::STDOUT.is_stdin());
283 assert!(!Fd::STDERR.is_stdin());
284 }
285
286 #[test]
287 fn test_is_stdout() {
288 assert!(!Fd::STDIN.is_stdout());
289 assert!(Fd::STDOUT.is_stdout());
290 assert!(!Fd::STDERR.is_stdout());
291 }
292
293 #[test]
294 fn test_is_stderr() {
295 assert!(!Fd::STDIN.is_stderr());
296 assert!(!Fd::STDOUT.is_stderr());
297 assert!(Fd::STDERR.is_stderr());
298 }
299
300 #[test]
301 fn test_clone() {
302 let fd = Fd::new(100).unwrap();
303 let fd2 = fd.clone();
304 assert_eq!(fd, fd2);
305 }
306
307 #[test]
308 fn test_equality() {
309 let f1 = Fd::new(100).unwrap();
310 let f2 = Fd::new(100).unwrap();
311 let f3 = Fd::new(200).unwrap();
312 assert_eq!(f1, f2);
313 assert_ne!(f1, f3);
314 }
315
316 #[test]
317 fn test_ordering() {
318 let f1 = Fd::new(100).unwrap();
319 let f2 = Fd::new(200).unwrap();
320 assert!(f1 < f2);
321 assert!(f2 > f1);
322 }
323
324 #[test]
325 fn test_new_unchecked() {
326 let fd = Fd::new_unchecked(100);
327 assert_eq!(fd.as_i32(), 100);
328 }
329}