Skip to main content

steamlocate/
error.rs

1use std::{
2    fmt, io,
3    path::{Path, PathBuf},
4};
5
6pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug)]
9#[non_exhaustive]
10pub enum Error {
11    FailedLocate(LocateError),
12    InvalidSteamDir(ValidationError),
13    Io {
14        inner: io::Error,
15        path: PathBuf,
16    },
17    Parse {
18        kind: ParseErrorKind,
19        error: ParseError,
20        path: PathBuf,
21    },
22    MissingExpectedApp {
23        app_id: u32,
24    },
25}
26
27impl fmt::Display for Error {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::FailedLocate(error) => {
31                write!(f, "Failed locating the steam dir. Error: {error}")
32            }
33            Self::InvalidSteamDir(error) => {
34                write!(f, "Failed validating steam dir. Error: {error}")
35            }
36            Self::Io { inner: err, path } => {
37                write!(f, "Encountered an I/O error: {} at {}", err, path.display())
38            }
39            Self::Parse { kind, error, path } => write!(
40                f,
41                "Failed parsing VDF file. File kind: {:?}, Error: {} at {}",
42                kind,
43                error,
44                path.display(),
45            ),
46            Self::MissingExpectedApp { app_id } => {
47                write!(f, "Missing expected app with id: {app_id}")
48            }
49        }
50    }
51}
52
53impl std::error::Error for Error {}
54
55impl Error {
56    pub(crate) fn locate(locate: LocateError) -> Self {
57        Self::FailedLocate(locate)
58    }
59
60    pub(crate) fn validation(validation: ValidationError) -> Self {
61        Self::InvalidSteamDir(validation)
62    }
63
64    pub(crate) fn io(io: io::Error, path: &Path) -> Self {
65        Self::Io {
66            inner: io,
67            path: path.to_owned(),
68        }
69    }
70
71    pub(crate) fn parse(kind: ParseErrorKind, error: ParseError, path: &Path) -> Self {
72        Self::Parse {
73            kind,
74            error,
75            path: path.to_owned(),
76        }
77    }
78}
79
80#[derive(Clone, Debug)]
81pub enum LocateError {
82    Backend(BackendError),
83    Unsupported,
84}
85
86impl LocateError {
87    #[cfg(target_os = "windows")]
88    pub(crate) fn winreg(io: io::Error) -> Self {
89        Self::Backend(BackendError {
90            inner: BackendErrorInner(std::sync::Arc::new(io)),
91        })
92    }
93
94    #[cfg(any(target_os = "macos", target_os = "linux"))]
95    pub(crate) fn no_home() -> Self {
96        Self::Backend(BackendError {
97            inner: BackendErrorInner::NoHome,
98        })
99    }
100}
101
102impl fmt::Display for LocateError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match self {
105            Self::Backend(error) => error.fmt(f),
106            Self::Unsupported => f.write_str("Unsupported platform"),
107        }
108    }
109}
110
111#[derive(Clone, Debug)]
112pub struct BackendError {
113    #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
114    #[allow(dead_code)] // Only used for displaying currently
115    inner: BackendErrorInner,
116}
117
118impl fmt::Display for BackendError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        #[cfg(target_os = "windows")]
121        {
122            write!(f, "{}", self.inner.0)
123        }
124        #[cfg(any(target_os = "macos", target_os = "linux"))]
125        {
126            match self.inner {
127                BackendErrorInner::NoHome => f.write_str("Unable to locate the user's $HOME"),
128            }
129        }
130        #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
131        {
132            // "Use" the unused value
133            let _ = f;
134            unreachable!("This should never be constructed!");
135        }
136    }
137}
138
139// TODO: move all this conditional junk into different modules, so that I don't have to keep
140// repeating it everywhere
141#[derive(Clone, Debug)]
142#[cfg(target_os = "windows")]
143struct BackendErrorInner(std::sync::Arc<io::Error>);
144#[derive(Clone, Debug)]
145#[cfg(any(target_os = "macos", target_os = "linux"))]
146enum BackendErrorInner {
147    NoHome,
148}
149
150#[derive(Clone, Debug)]
151pub struct ValidationError {
152    #[allow(dead_code)] // Only used for displaying currently
153    inner: ValidationErrorInner,
154}
155
156impl ValidationError {
157    pub(crate) fn missing_dir() -> Self {
158        Self {
159            inner: ValidationErrorInner::MissingDirectory,
160        }
161    }
162}
163
164impl fmt::Display for ValidationError {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self.inner {
167            ValidationErrorInner::MissingDirectory => f.write_str(
168                "The Steam installation directory either isn't a directory or doesn't exist",
169            ),
170        }
171    }
172}
173
174#[derive(Clone, Debug)]
175enum ValidationErrorInner {
176    MissingDirectory,
177}
178
179#[derive(Copy, Clone, Debug)]
180#[non_exhaustive]
181pub enum ParseErrorKind {
182    Config,
183    LibraryFolders,
184    App,
185    Shortcut,
186}
187
188#[derive(Debug)]
189pub struct ParseError {
190    // Keep `keyvalues_parser` and `keyvalues_serde` types out of the public API (this includes
191    // from traits, so no using `thiserror` with `#[from]`)
192    #[allow(dead_code)] // Only used for displaying currently
193    inner: Box<ParseErrorInner>,
194}
195
196impl fmt::Display for ParseError {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        write!(f, "{}", self.inner)
199    }
200}
201
202#[derive(Debug)]
203pub(crate) enum ParseErrorInner {
204    Parse(keyvalues_parser::error::Error),
205    Serde(keyvalues_serde::error::Error),
206    UnexpectedStructure,
207    Missing,
208}
209
210impl fmt::Display for ParseErrorInner {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        match self {
213            Self::Parse(err) => write!(f, "{err}"),
214            Self::Serde(err) => write!(f, "{err}"),
215            Self::UnexpectedStructure => f.write_str("File did not match expected structure"),
216            Self::Missing => f.write_str("Expected file was missing"),
217        }
218    }
219}
220
221impl ParseError {
222    pub(crate) fn new(inner: ParseErrorInner) -> Self {
223        Self {
224            inner: Box::new(inner),
225        }
226    }
227
228    pub(crate) fn from_parser(err: keyvalues_parser::error::Error) -> Self {
229        Self::new(ParseErrorInner::Parse(err))
230    }
231
232    pub(crate) fn from_serde(err: keyvalues_serde::error::Error) -> Self {
233        Self::new(ParseErrorInner::Serde(err))
234    }
235
236    pub(crate) fn unexpected_structure() -> Self {
237        Self::new(ParseErrorInner::UnexpectedStructure)
238    }
239
240    pub(crate) fn missing() -> Self {
241        Self::new(ParseErrorInner::Missing)
242    }
243}