1#![forbid(unsafe_code)]
2#![warn(clippy::all)]
3#![forbid(missing_docs)]
4#![deny(warnings)]
5use std::{
25 convert::Infallible,
26 error::Error as StdError,
27 ffi::{OsStr, OsString},
28 fmt::Display,
29 marker::PhantomData,
30 path::Path,
31 str::FromStr,
32};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum Error<T> {
39 Utf8,
41 ParseErr(T),
43}
44
45impl<T: Display> Display for Error<T> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Error::Utf8 => write!(f, "invalid utf-8 sequence"),
49 Error::ParseErr(err) => err.fmt(f),
50 }
51 }
52}
53
54impl<T: StdError + 'static> StdError for Error<T> {
55 fn source(&self) -> Option<&(dyn StdError + 'static)> {
56 match self {
57 Error::Utf8 => None,
58 Error::ParseErr(err) => Some(err),
59 }
60 }
61}
62
63#[doc(hidden)]
64pub struct Wrap<'a, T>(&'a OsStr, PhantomData<T>);
65
66impl<'a, T> Wrap<'a, T> {
67 pub fn new(s: &'a OsStr) -> Self {
68 Wrap(s, PhantomData::<T>)
69 }
70}
71
72macro_rules! specialize {
75 (impl ($($and:tt)+) $name:ident for $from_ty:path {
76 fn from_str($s:ident: &str) -> Result<T, $err:ty> {$($body:tt)*}
77 }) => {
78 specialize! {
79 impl ($($and)+) $name for $from_ty {
80 fn specialized(&self) -> Result<T, Error<$err>> {
81 match self.0.to_str() {
82 None => Err(Error::Utf8),
83 Some($s) => {$($body)*}.map_err(Error::ParseErr),
84 }
85 }
86 }
87 }
88 };
89 (impl ($($and:tt)+) $name:ident for $from_ty:path {
90 fn from_os_str($s:ident: &OsStr) -> Result<T, $err:ty> {$($body:tt)*}
91 }) => {
92 specialize! {
93 impl ($($and)+) $name for $from_ty {
94 fn specialized(&self) -> Result<T, $err> {
95 let $s = self.0;
96 $($body)*
97 }
98 }
99 }
100 };
101 (impl ($($and:tt)+) $name:ident for $from_ty:path {
102 fn specialized(&$self:ident) -> Result<T, $err:ty> {$($body:tt)*}
103 }) => {
104 #[doc(hidden)]
105 pub trait $name {
106 type Return;
107 fn specialized(&self) -> Self::Return;
108 }
109
110 impl<'a, T: $from_ty> $name for $($and)+Wrap<'a, T> {
111 type Return = Result<T, $err>;
112 fn specialized(&$self) -> Self::Return {$($body)*}
113 }
114 };
115}
116
117specialize! {
119 impl (&) Specialize8 for FromStr {
120 fn from_str(s: &str) -> Result<T, T::Err> {
121 T::from_str(s)
122 }
123 }
124}
125
126specialize! {
127 impl (&&) Specialize7 for TryFrom<&'a str> {
128 fn from_str(s: &str) -> Result<T, T::Error> {
129 T::try_from(s)
130 }
131 }
132}
133
134specialize! {
135 impl (&&) Specialize6 for TryFrom<&'a OsStr> {
136 fn from_os_str(s: &OsStr) -> Result<T, T::Error> {
137 T::try_from(s)
138 }
139 }
140}
141
142specialize! {
143 impl (&&&&) Specialize5 for From<String> {
144 fn from_str(s: &str) -> Result<T, Infallible> {
145 Ok(T::from(s.to_string()))
146 }
147 }
148}
149
150specialize! {
151 impl (&&&&&) Specialize4 for From<&'a str> {
152 fn from_str(s: &str) -> Result<T, Infallible> {
153 Ok(T::from(s))
154 }
155 }
156}
157
158specialize! {
159 impl (&&&&&&) Specialize3 for From<OsString> {
160 fn from_os_str(s: &OsStr) -> Result<T, Infallible> {
161 Ok(T::from(s.to_os_string()))
162 }
163 }
164}
165
166specialize! {
167 impl (&&&&&&&) Specialize2 for From<&'a Path> {
168 fn from_os_str(s: &OsStr) -> Result<T, Infallible> {
169 Ok(T::from(Path::new(s)))
170 }
171 }
172}
173
174specialize! {
175 impl (&&&&&&&&) Specialize1 for From<&'a OsStr> {
176 fn from_os_str(s: &OsStr) -> Result<T, Infallible> {
177 Ok(T::from(s))
178 }
179 }
180}
181
182#[macro_export]
205macro_rules! try_from_os_str {
206 ($name:ident as $typ:ty) => {
207 (&&&&&&&&Wrap::<$typ>::new($name)).specialized()
208 };
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use std::path::Path;
215 use std::path::PathBuf;
216
217 #[derive(Debug, Clone, PartialEq, Eq)]
218 struct Foo(String);
219
220 impl From<&OsStr> for Foo {
221 fn from(s: &OsStr) -> Self {
222 Foo("OS: ".to_string() + &s.to_string_lossy())
223 }
224 }
225
226 impl FromStr for Foo {
227 type Err = Infallible;
228
229 fn from_str(s: &str) -> Result<Self, Self::Err> {
230 Ok(Foo("STR: ".to_string() + s))
231 }
232 }
233
234 #[test]
235 fn it_works() {
236 let os_str = OsStr::new("123");
237 let os_str_2 = try_from_os_str!(os_str as &OsStr).unwrap();
238 assert_eq!(os_str_2, os_str);
239
240 let path = try_from_os_str!(os_str as &Path).unwrap();
241 assert_eq!(path, Path::new("123"));
242 let path = try_from_os_str!(os_str as PathBuf).unwrap();
243 assert_eq!(&path, Path::new("123"));
244
245 let str = try_from_os_str!(os_str as &str).unwrap();
246 assert_eq!(str, "123");
247 let string = try_from_os_str!(os_str as String).unwrap();
248 assert_eq!(string, "123".to_string());
249 let int = try_from_os_str!(os_str as u8).unwrap();
250 assert_eq!(int, 123);
251
252 let foo = try_from_os_str!(os_str as Foo);
254 assert_eq!(foo, Ok(Foo("OS: 123".to_owned())));
255 }
256
257 #[test]
258 #[cfg(unix)]
259 fn it_works_with_non_utf8() {
260 use std::os::unix::ffi::OsStrExt;
261 let os_str = OsStr::from_bytes(&[0xff, 0xff]);
262 let os_str_2 = try_from_os_str!(os_str as &OsStr).unwrap();
263 assert_eq!(os_str_2, os_str);
264
265 let path = try_from_os_str!(os_str as &Path).unwrap();
266 assert_eq!(path, Path::new(os_str));
267 let path = try_from_os_str!(os_str as PathBuf).unwrap();
268 assert_eq!(path, Path::new(os_str));
269 let str = try_from_os_str!(os_str as &str);
270 assert_eq!(str, Err(Error::Utf8));
271 let string = try_from_os_str!(os_str as String);
272 assert_eq!(string, Err(Error::Utf8));
273 let int = try_from_os_str!(os_str as u8);
274 assert_eq!(int, Err(Error::Utf8));
275
276 let foo = try_from_os_str!(os_str as Foo);
278 assert_eq!(foo, Ok(Foo("OS: ��".to_owned())));
279 }
280}