Skip to content

Commit 9300694

Browse files
Merge pull request #13 from thunderstore-io/error-refactor
refactor error variants to be more module-specific
2 parents 6d985ae + f4fbf19 commit 9300694

File tree

28 files changed

+369
-300
lines changed

28 files changed

+369
-300
lines changed

src/error.rs

Lines changed: 67 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,109 @@
11
use std::path::{Path, PathBuf};
22

3-
use crate::ts::version::Version;
3+
use crate::ts::error::ApiError;
4+
5+
use crate::game::error::GameError;
6+
use crate::package::error::PackageError;
7+
use crate::project::error::ProjectError;
48

5-
#[allow(clippy::enum_variant_names)]
69
#[derive(Debug, thiserror::Error)]
710
#[repr(u32)]
811
pub enum Error {
9-
#[error("An API error occurred.")]
10-
ApiError {
11-
source: reqwest::Error,
12-
response_body: Option<String>,
13-
} = 1,
12+
#[error("{0}")]
13+
Game(#[from] GameError),
1414

15-
#[error("A game import error occurred.")]
16-
GameImportError(#[from] crate::game::import::Error),
15+
#[error("{0}")]
16+
Package(#[from] PackageError),
1717

18-
#[error("The file at {0} does not exist or is otherwise not accessible.")]
19-
FileNotFound(PathBuf),
18+
#[error("{0}")]
19+
Project(#[from] ProjectError),
20+
21+
#[error("{0}")]
22+
Api(#[from] ApiError),
23+
24+
#[error("{0}")]
25+
Io(#[from] IoError),
26+
27+
#[error("{0}")]
28+
JsonParse(#[from] serde_json::Error),
2029

21-
#[error("The directory at {0} does not exist or is otherwise not accessible.")]
22-
DirectoryNotFound(PathBuf),
30+
#[error("{0}")]
31+
TomlDeserialize(#[from] toml::de::Error),
2332

24-
#[error("A network error occurred while sending an API request.")]
25-
NetworkError(#[from] reqwest::Error),
33+
#[error("{0}")]
34+
TomlSerialize(#[from] toml::ser::Error),
35+
}
2636

27-
#[error("The path at {0} is actually a file.")]
28-
ProjectDirIsFile(PathBuf),
37+
#[derive(Debug, thiserror::Error)]
38+
pub enum IoError {
39+
#[error("A file IO error occured: {0}.")]
40+
Native(std::io::Error, Option<PathBuf>),
2941

30-
#[error("A project configuration already exists at {0}.")]
31-
ProjectAlreadyExists(PathBuf),
42+
#[error("File not found: {0}.")]
43+
FileNotFound(PathBuf),
3244

33-
#[error("A generic IO error occurred: {0}")]
34-
GenericIoError(#[from] std::io::Error),
45+
#[error("Expected directory at '{0}', got file.")]
46+
DirectoryIsFile(PathBuf),
3547

36-
#[error("A file IO error occurred at path {0}: {1}")]
37-
FileIoError(PathBuf, std::io::Error),
48+
#[error("Directory not found: {0}.")]
49+
DirNotFound(PathBuf),
3850

39-
#[error("Invalid version.")]
40-
InvalidVersion(#[from] crate::ts::version::VersionParseError),
51+
#[error("{0}")]
52+
DirWalker(walkdir::Error),
4153

42-
#[error("Failed to read project file. {0}")]
43-
FailedDeserializeProject(#[from] toml::de::Error),
54+
#[error("Failed to find file '{0}' within the directory '{1}.")]
55+
FailedFileSearch(String, PathBuf),
4456

45-
#[error("No project exists at the path {0}.")]
46-
NoProjectFile(PathBuf),
57+
#[error("Failed to read subkey at '{0}'.")]
58+
RegistrySubkeyRead(String),
59+
60+
#[error("Failed to read value with name '{0}' at key '{1}'.")]
61+
RegistryValueRead(String, String),
4762

4863
#[error("Failed modifying zip file: {0}.")]
4964
ZipError(#[from] zip::result::ZipError),
65+
}
5066

51-
#[error("Project is missing required table '{0}'.")]
52-
MissingTable(&'static str),
53-
54-
#[error("Missing repository url.")]
55-
MissingRepository,
56-
57-
#[error("Missing auth token.")]
58-
MissingAuthToken,
59-
60-
#[error("The game identifier '{0}' does not exist within the ecosystem schema.")]
61-
InvalidGameId(String),
62-
63-
#[error("An error occurred while parsing JSON: {0}")]
64-
JsonParserError(#[from] serde_json::Error),
65-
66-
#[error("An error occured while serializing TOML: {0}")]
67-
TomlSerializer(#[from] toml::ser::Error),
68-
69-
#[error("The installer does not contain a valid manifest.")]
70-
InstallerNoManifest,
71-
72-
#[error(
73-
"The installer executable for the current OS and architecture combination does not exist."
74-
)]
75-
InstallerNotExecutable,
76-
77-
#[error(
78-
"
79-
The installer '{package_id}' does not support the current tcli installer protocol.
80-
Expected: {our_version:#?}
81-
Recieved: {given_version:#?}
82-
"
83-
)]
84-
InstallerBadVersion {
85-
package_id: String,
86-
given_version: Version,
87-
our_version: Version,
88-
},
89-
90-
#[error(
91-
"The installer '{package_id}' did not respond correctly:
92-
\t{message}"
93-
)]
94-
InstallerBadResponse { package_id: String, message: String },
95-
96-
#[error("The installer returned an error:\n\t{message}")]
97-
InstallerError { message: String },
98-
99-
#[error(
100-
"The provided game id '{0}' does not exist or has not been imported into this profile."
101-
)]
102-
BadGameId(String),
103-
104-
#[error("The Steam app with id '{0}' could not be found.")]
105-
SteamAppNotFound(u32),
67+
impl From<std::io::Error> for Error {
68+
fn from(value: std::io::Error) -> Self {
69+
Self::Io(IoError::Native(value, None))
70+
}
71+
}
72+
73+
impl From<reqwest::Error> for Error {
74+
fn from(value: reqwest::Error) -> Self {
75+
Self::Api(ApiError::BadRequest { source: value, response_body: None })
76+
}
77+
}
78+
79+
impl From<zip::result::ZipError> for Error {
80+
fn from(value: zip::result::ZipError) -> Self {
81+
Self::Io(IoError::ZipError(value))
82+
}
10683
}
10784

10885
pub trait IoResultToTcli<R> {
109-
fn map_fs_error(self, path: impl AsRef<Path>) -> Result<R, Error>;
86+
fn map_fs_error(self, path: impl AsRef<Path>) -> Result<R, IoError>;
11087
}
11188

11289
impl<R> IoResultToTcli<R> for Result<R, std::io::Error> {
113-
fn map_fs_error(self, path: impl AsRef<Path>) -> Result<R, Error> {
114-
self.map_err(|e| Error::FileIoError(path.as_ref().into(), e))
90+
fn map_fs_error(self, path: impl AsRef<Path>) -> Result<R, IoError> {
91+
self.map_err(|e| IoError::Native(e, Some(path.as_ref().into())))
11592
}
11693
}
11794

11895
pub trait ReqwestToTcli: Sized {
119-
async fn error_for_status_tcli(self) -> Result<Self, Error>;
96+
async fn error_for_status_tcli(self) -> Result<Self, ApiError>;
12097
}
12198

12299
impl ReqwestToTcli for reqwest::Response {
123-
async fn error_for_status_tcli(self) -> Result<Self, Error> {
100+
async fn error_for_status_tcli(self) -> Result<Self, ApiError> {
124101
match self.error_for_status_ref() {
125102
Ok(_) => Ok(self),
126-
Err(err) => Err(Error::ApiError {
103+
Err(err) => Err(ApiError::BadRequest {
127104
source: err,
128105
response_body: self.text().await.ok(),
129106
}),
130107
}
131108
}
132109
}
133-
134-
impl From<walkdir::Error> for Error {
135-
fn from(value: walkdir::Error) -> Self {
136-
Self::FileIoError(
137-
value.path().unwrap_or(Path::new("")).into(),
138-
value.into_io_error().unwrap(),
139-
)
140-
}
141-
}

src/game/error.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::path::PathBuf;
2+
3+
#[derive(Debug, thiserror::Error)]
4+
#[repr(u32)]
5+
pub enum GameError {
6+
#[error("The game '{0}' is not supported by platform '{1}'.")]
7+
NotSupported(String, String),
8+
9+
#[error("Could not find game with id '{0}' within the ecosystem schema.")]
10+
BadGameId(String),
11+
12+
#[error("Could not find the game '{0}' installed via platform '{1}'.")]
13+
NotFound(String, String),
14+
15+
#[error("Could not find any of '{possible_names:?}' in base directory: '{base_path}'.")]
16+
ExeNotFound { possible_names: Vec<String>, base_path: PathBuf},
17+
18+
#[error("The Steam library could not be automatically found.")]
19+
SteamDirNotFound,
20+
21+
#[error("The path '{0}' does not refer to a valid Steam directory.")]
22+
SteamDirBadPath(PathBuf),
23+
24+
#[error("The app with id '{0}' could not be found in the Steam instance at '{1}'.")]
25+
SteamAppNotFound(u32, PathBuf),
26+
27+
// This should probably live elsewhere but it's fine here for now.
28+
#[error("An error occured while fetching the ecosystem schema.")]
29+
EcosystemSchema,
30+
}

src/game/import/ea.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::path::PathBuf;
22

3-
use super::{Error, GameImporter};
3+
use super::GameImporter;
4+
use crate::error::Error;
5+
use crate::game::error::GameError;
46
use crate::game::import::ImportBase;
57
use crate::game::registry::{ActiveDistribution, GameData};
68
use crate::ts::v1::models::ecosystem::GameDefPlatform;
@@ -34,8 +36,10 @@ impl GameImporter for EaImporter {
3436
.clone()
3537
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
3638
.ok_or_else(|| {
37-
super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone())
38-
})?;
39+
GameError::ExeNotFound {
40+
possible_names: r2mm.exe_names.clone(),
41+
base_path: game_dir.clone(),
42+
}})?;
3943
let dist = ActiveDistribution {
4044
dist: GameDefPlatform::Origin {
4145
identifier: self.ident.to_string(),

src/game/import/egs.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::path::PathBuf;
33

44
use serde::{Deserialize, Serialize};
55

6-
use super::{Error, GameImporter, ImportBase};
6+
use super::{GameImporter, ImportBase};
7+
use crate::error::{IoError, Error};
8+
use crate::game::error::GameError;
79
use crate::game::registry::{ActiveDistribution, GameData};
810
use crate::ts::v1::models::ecosystem::GameDefPlatform;
911
use crate::util::reg::{self, HKey};
@@ -42,7 +44,7 @@ impl GameImporter for EgsImporter {
4244
let manifests_dir = PathBuf::from(value).join("Manifests");
4345

4446
if !manifests_dir.exists() {
45-
Err(Error::DirNotFound(manifests_dir.clone()))?;
47+
Err(IoError::DirNotFound(manifests_dir.clone()))?;
4648
}
4749

4850
// Manifest files are JSON files with .item extensions.
@@ -68,7 +70,7 @@ impl GameImporter for EgsImporter {
6870
None
6971
}
7072
})
71-
.ok_or_else(|| super::Error::NotFound(game_label.clone(), "EGS".to_string()))?;
73+
.ok_or_else(|| GameError::NotFound(game_label.clone(), "EGS".to_string()))?;
7274

7375
let r2mm = base.game_def.r2modman.as_ref().expect(
7476
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
@@ -80,8 +82,10 @@ impl GameImporter for EgsImporter {
8082
.clone()
8183
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
8284
.ok_or_else(|| {
83-
super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone())
84-
})?;
85+
GameError::ExeNotFound {
86+
possible_names: r2mm.exe_names.clone(),
87+
base_path: game_dir.clone(),
88+
}})?;
8589
let dist = ActiveDistribution {
8690
dist: GameDefPlatform::Other,
8791
game_dir: game_dir.to_path_buf(),

src/game/import/gamepass.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::path::PathBuf;
22

3-
use super::Error;
43
use super::{GameImporter, ImportBase};
4+
use crate::error::Error;
5+
use crate::game::error::GameError;
56
use crate::game::registry::{ActiveDistribution, GameData};
67
use crate::ts::v1::models::ecosystem::GameDefPlatform;
78
use crate::util::reg::{self, HKey};
@@ -26,7 +27,7 @@ impl GameImporter for GamepassImporter {
2627
.into_iter()
2728
.find(|x| x.key.starts_with(&self.ident))
2829
.ok_or_else(|| {
29-
super::Error::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
30+
GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
3031
})?
3132
.val
3233
.replace('\"', "");
@@ -35,7 +36,7 @@ impl GameImporter for GamepassImporter {
3536
.into_iter()
3637
.next()
3738
.ok_or_else(|| {
38-
super::Error::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
39+
GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
3940
})?;
4041
let game_dir = PathBuf::from(reg::get_value_at(HKey::LocalMachine, &game_root, "Root")?);
4142

@@ -49,8 +50,10 @@ impl GameImporter for GamepassImporter {
4950
.clone()
5051
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
5152
.ok_or_else(|| {
52-
super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone())
53-
})?;
53+
GameError::ExeNotFound {
54+
possible_names: r2mm.exe_names.clone(),
55+
base_path: game_dir.clone(),
56+
}})?;
5457
let dist = ActiveDistribution {
5558
dist: GameDefPlatform::GamePass {
5659
identifier: self.ident.to_string(),

0 commit comments

Comments
 (0)