diff --git a/src/error.rs b/src/error.rs index ced46c5..5fcc731 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,141 +1,109 @@ use std::path::{Path, PathBuf}; -use crate::ts::version::Version; +use crate::ts::error::ApiError; + +use crate::game::error::GameError; +use crate::package::error::PackageError; +use crate::project::error::ProjectError; -#[allow(clippy::enum_variant_names)] #[derive(Debug, thiserror::Error)] #[repr(u32)] pub enum Error { - #[error("An API error occurred.")] - ApiError { - source: reqwest::Error, - response_body: Option, - } = 1, + #[error("{0}")] + Game(#[from] GameError), - #[error("A game import error occurred.")] - GameImportError(#[from] crate::game::import::Error), + #[error("{0}")] + Package(#[from] PackageError), - #[error("The file at {0} does not exist or is otherwise not accessible.")] - FileNotFound(PathBuf), + #[error("{0}")] + Project(#[from] ProjectError), + + #[error("{0}")] + Api(#[from] ApiError), + + #[error("{0}")] + Io(#[from] IoError), + + #[error("{0}")] + JsonParse(#[from] serde_json::Error), - #[error("The directory at {0} does not exist or is otherwise not accessible.")] - DirectoryNotFound(PathBuf), + #[error("{0}")] + TomlDeserialize(#[from] toml::de::Error), - #[error("A network error occurred while sending an API request.")] - NetworkError(#[from] reqwest::Error), + #[error("{0}")] + TomlSerialize(#[from] toml::ser::Error), +} - #[error("The path at {0} is actually a file.")] - ProjectDirIsFile(PathBuf), +#[derive(Debug, thiserror::Error)] +pub enum IoError { + #[error("A file IO error occured: {0}.")] + Native(std::io::Error, Option), - #[error("A project configuration already exists at {0}.")] - ProjectAlreadyExists(PathBuf), + #[error("File not found: {0}.")] + FileNotFound(PathBuf), - #[error("A generic IO error occurred: {0}")] - GenericIoError(#[from] std::io::Error), + #[error("Expected directory at '{0}', got file.")] + DirectoryIsFile(PathBuf), - #[error("A file IO error occurred at path {0}: {1}")] - FileIoError(PathBuf, std::io::Error), + #[error("Directory not found: {0}.")] + DirNotFound(PathBuf), - #[error("Invalid version.")] - InvalidVersion(#[from] crate::ts::version::VersionParseError), + #[error("{0}")] + DirWalker(walkdir::Error), - #[error("Failed to read project file. {0}")] - FailedDeserializeProject(#[from] toml::de::Error), + #[error("Failed to find file '{0}' within the directory '{1}.")] + FailedFileSearch(String, PathBuf), - #[error("No project exists at the path {0}.")] - NoProjectFile(PathBuf), + #[error("Failed to read subkey at '{0}'.")] + RegistrySubkeyRead(String), + + #[error("Failed to read value with name '{0}' at key '{1}'.")] + RegistryValueRead(String, String), #[error("Failed modifying zip file: {0}.")] ZipError(#[from] zip::result::ZipError), +} - #[error("Project is missing required table '{0}'.")] - MissingTable(&'static str), - - #[error("Missing repository url.")] - MissingRepository, - - #[error("Missing auth token.")] - MissingAuthToken, - - #[error("The game identifier '{0}' does not exist within the ecosystem schema.")] - InvalidGameId(String), - - #[error("An error occurred while parsing JSON: {0}")] - JsonParserError(#[from] serde_json::Error), - - #[error("An error occured while serializing TOML: {0}")] - TomlSerializer(#[from] toml::ser::Error), - - #[error("The installer does not contain a valid manifest.")] - InstallerNoManifest, - - #[error( - "The installer executable for the current OS and architecture combination does not exist." - )] - InstallerNotExecutable, - - #[error( - " - The installer '{package_id}' does not support the current tcli installer protocol. - Expected: {our_version:#?} - Recieved: {given_version:#?} - " - )] - InstallerBadVersion { - package_id: String, - given_version: Version, - our_version: Version, - }, - - #[error( - "The installer '{package_id}' did not respond correctly: - \t{message}" - )] - InstallerBadResponse { package_id: String, message: String }, - - #[error("The installer returned an error:\n\t{message}")] - InstallerError { message: String }, - - #[error( - "The provided game id '{0}' does not exist or has not been imported into this profile." - )] - BadGameId(String), - - #[error("The Steam app with id '{0}' could not be found.")] - SteamAppNotFound(u32), +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::Io(IoError::Native(value, None)) + } +} + +impl From for Error { + fn from(value: reqwest::Error) -> Self { + Self::Api(ApiError::BadRequest { source: value, response_body: None }) + } +} + +impl From for Error { + fn from(value: zip::result::ZipError) -> Self { + Self::Io(IoError::ZipError(value)) + } } pub trait IoResultToTcli { - fn map_fs_error(self, path: impl AsRef) -> Result; + fn map_fs_error(self, path: impl AsRef) -> Result; } impl IoResultToTcli for Result { - fn map_fs_error(self, path: impl AsRef) -> Result { - self.map_err(|e| Error::FileIoError(path.as_ref().into(), e)) + fn map_fs_error(self, path: impl AsRef) -> Result { + self.map_err(|e| IoError::Native(e, Some(path.as_ref().into()))) } } pub trait ReqwestToTcli: Sized { - async fn error_for_status_tcli(self) -> Result; + async fn error_for_status_tcli(self) -> Result; } impl ReqwestToTcli for reqwest::Response { - async fn error_for_status_tcli(self) -> Result { + async fn error_for_status_tcli(self) -> Result { match self.error_for_status_ref() { Ok(_) => Ok(self), - Err(err) => Err(Error::ApiError { + Err(err) => Err(ApiError::BadRequest { source: err, response_body: self.text().await.ok(), }), } } } - -impl From for Error { - fn from(value: walkdir::Error) -> Self { - Self::FileIoError( - value.path().unwrap_or(Path::new("")).into(), - value.into_io_error().unwrap(), - ) - } -} diff --git a/src/game/error.rs b/src/game/error.rs new file mode 100644 index 0000000..51707db --- /dev/null +++ b/src/game/error.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +#[repr(u32)] +pub enum GameError { + #[error("The game '{0}' is not supported by platform '{1}'.")] + NotSupported(String, String), + + #[error("Could not find game with id '{0}' within the ecosystem schema.")] + BadGameId(String), + + #[error("Could not find the game '{0}' installed via platform '{1}'.")] + NotFound(String, String), + + #[error("Could not find any of '{possible_names:?}' in base directory: '{base_path}'.")] + ExeNotFound { possible_names: Vec, base_path: PathBuf}, + + #[error("The Steam library could not be automatically found.")] + SteamDirNotFound, + + #[error("The path '{0}' does not refer to a valid Steam directory.")] + SteamDirBadPath(PathBuf), + + #[error("The app with id '{0}' could not be found in the Steam instance at '{1}'.")] + SteamAppNotFound(u32, PathBuf), + + // This should probably live elsewhere but it's fine here for now. + #[error("An error occured while fetching the ecosystem schema.")] + EcosystemSchema, +} diff --git a/src/game/import/ea.rs b/src/game/import/ea.rs index 595fbaa..8282d2b 100644 --- a/src/game/import/ea.rs +++ b/src/game/import/ea.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -use super::{Error, GameImporter}; +use super::GameImporter; +use crate::error::Error; +use crate::game::error::GameError; use crate::game::import::ImportBase; use crate::game::registry::{ActiveDistribution, GameData}; use crate::ts::v1::models::ecosystem::GameDefPlatform; @@ -34,8 +36,10 @@ impl GameImporter for EaImporter { .clone() .or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir)) .ok_or_else(|| { - super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone()) - })?; + GameError::ExeNotFound { + possible_names: r2mm.exe_names.clone(), + base_path: game_dir.clone(), + }})?; let dist = ActiveDistribution { dist: GameDefPlatform::Origin { identifier: self.ident.to_string(), diff --git a/src/game/import/egs.rs b/src/game/import/egs.rs index d73c443..fd2c2a7 100644 --- a/src/game/import/egs.rs +++ b/src/game/import/egs.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; -use super::{Error, GameImporter, ImportBase}; +use super::{GameImporter, ImportBase}; +use crate::error::{IoError, Error}; +use crate::game::error::GameError; use crate::game::registry::{ActiveDistribution, GameData}; use crate::ts::v1::models::ecosystem::GameDefPlatform; use crate::util::reg::{self, HKey}; @@ -42,7 +44,7 @@ impl GameImporter for EgsImporter { let manifests_dir = PathBuf::from(value).join("Manifests"); if !manifests_dir.exists() { - Err(Error::DirNotFound(manifests_dir.clone()))?; + Err(IoError::DirNotFound(manifests_dir.clone()))?; } // Manifest files are JSON files with .item extensions. @@ -68,7 +70,7 @@ impl GameImporter for EgsImporter { None } }) - .ok_or_else(|| super::Error::NotFound(game_label.clone(), "EGS".to_string()))?; + .ok_or_else(|| GameError::NotFound(game_label.clone(), "EGS".to_string()))?; let r2mm = base.game_def.r2modman.as_ref().expect( "Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.", @@ -80,8 +82,10 @@ impl GameImporter for EgsImporter { .clone() .or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir)) .ok_or_else(|| { - super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone()) - })?; + GameError::ExeNotFound { + possible_names: r2mm.exe_names.clone(), + base_path: game_dir.clone(), + }})?; let dist = ActiveDistribution { dist: GameDefPlatform::Other, game_dir: game_dir.to_path_buf(), diff --git a/src/game/import/gamepass.rs b/src/game/import/gamepass.rs index 75cc626..04cfb53 100644 --- a/src/game/import/gamepass.rs +++ b/src/game/import/gamepass.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; -use super::Error; use super::{GameImporter, ImportBase}; +use crate::error::Error; +use crate::game::error::GameError; use crate::game::registry::{ActiveDistribution, GameData}; use crate::ts::v1::models::ecosystem::GameDefPlatform; use crate::util::reg::{self, HKey}; @@ -26,7 +27,7 @@ impl GameImporter for GamepassImporter { .into_iter() .find(|x| x.key.starts_with(&self.ident)) .ok_or_else(|| { - super::Error::NotFound(base.game_def.label.clone(), "Gamepass".to_string()) + GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string()) })? .val .replace('\"', ""); @@ -35,7 +36,7 @@ impl GameImporter for GamepassImporter { .into_iter() .next() .ok_or_else(|| { - super::Error::NotFound(base.game_def.label.clone(), "Gamepass".to_string()) + GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string()) })?; let game_dir = PathBuf::from(reg::get_value_at(HKey::LocalMachine, &game_root, "Root")?); @@ -49,8 +50,10 @@ impl GameImporter for GamepassImporter { .clone() .or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir)) .ok_or_else(|| { - super::Error::ExeNotFound(base.game_def.label.clone(), game_dir.clone()) - })?; + GameError::ExeNotFound { + possible_names: r2mm.exe_names.clone(), + base_path: game_dir.clone(), + }})?; let dist = ActiveDistribution { dist: GameDefPlatform::GamePass { identifier: self.ident.to_string(), diff --git a/src/game/import/mod.rs b/src/game/import/mod.rs index f99f881..571129b 100644 --- a/src/game/import/mod.rs +++ b/src/game/import/mod.rs @@ -6,47 +6,15 @@ pub mod steam; use std::path::{Path, PathBuf}; +use super::error::GameError; use super::registry::{ActiveDistribution, GameData}; +use crate::error::Error; use crate::game::import::ea::EaImporter; use crate::game::import::egs::EgsImporter; use crate::game::import::gamepass::GamepassImporter; use crate::game::import::steam::SteamImporter; use crate::ts::v1::models::ecosystem::GameDef; use crate::ts::v1::{ecosystem, models::ecosystem::GameDefPlatform}; -use crate::util::reg; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("The game '{0}' is not supported by platform '{1}'.")] - NotSupported(String, String), - - #[error("A game with id '{0}' could not be found within the ecosystem schema.")] - InvalidGameId(String), - - #[error("Could not find the game '{0}' installed via the platform '{1}'.")] - NotFound(String, String), - - #[error("The EGS directory at '{0}' does not exist or is unreadable.")] - DirNotFound(PathBuf), - - #[error("Could not find the executable for game '{0}' within the dir '{1}'.")] - ExeNotFound(String, PathBuf), - - #[error("An error occured while fetching the ecosystem schema.")] - EcosystemSchema, - - #[error("Unable to read the registry.")] - RegistryRead(#[from] reg::Error), - - #[error("The Steam library could not be automatically found.")] - SteamDirNotFound, - - #[error("The path '{0}' does not refer to a valid Steam directory.")] - SteamDirBadPath(PathBuf), - - #[error("The app with id '{0}' could not be found in the Steam instance at '{1}'.")] - SteamAppNotFound(u32, PathBuf), -} pub trait GameImporter { fn construct(self: Box, base: ImportBase) -> Result; @@ -71,10 +39,10 @@ impl ImportBase { pub async fn new(game_id: &str) -> Result { let game_def = ecosystem::get_schema() .await - .map_err(|_| Error::EcosystemSchema)? + .map_err(|_| GameError::EcosystemSchema)? .games .get(game_id) - .ok_or_else(|| Error::InvalidGameId(game_id.into()))? + .ok_or_else(|| GameError::BadGameId(game_id.into()))? .clone(); Ok(ImportBase { @@ -119,7 +87,7 @@ pub fn select_importer(base: &ImportBase) -> Result, Error } _ => None, }) - .ok_or_else(|| Error::NotSupported(base.game_id.clone(), "".into())) + .ok_or_else(|| GameError::NotSupported(base.game_id.clone(), "".into()).into()) } pub fn find_game_exe(possible: &[String], base_path: &Path) -> Option { diff --git a/src/game/import/nodrm.rs b/src/game/import/nodrm.rs index 5e07db4..7ad567b 100644 --- a/src/game/import/nodrm.rs +++ b/src/game/import/nodrm.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; -use super::{Error, GameImporter, ImportBase}; +use crate::error::{Error, IoError}; +use crate::game::error::GameError; +use super::{GameImporter, ImportBase}; use crate::game::registry::{ActiveDistribution, GameData}; use crate::ts::v1::models::ecosystem::GameDefPlatform; @@ -19,7 +21,7 @@ impl NoDrmImporter { impl GameImporter for NoDrmImporter { fn construct(self: Box, base: ImportBase) -> Result { if !self.game_dir.exists() { - Err(Error::DirNotFound(self.game_dir.to_path_buf()))?; + Err(IoError::DirNotFound(self.game_dir.to_path_buf()))?; } let r2mm = base.game_def.r2modman.as_ref().expect( @@ -32,7 +34,7 @@ impl GameImporter for NoDrmImporter { .clone() .or_else(|| super::find_game_exe(&r2mm.exe_names, &self.game_dir)) .ok_or_else(|| { - super::Error::ExeNotFound(base.game_def.label.clone(), self.game_dir.clone()) + GameError::ExeNotFound { possible_names: r2mm.exe_names.clone(), base_path: self.game_dir.clone() } })?; let dist = ActiveDistribution { dist: GameDefPlatform::Other, diff --git a/src/game/import/steam.rs b/src/game/import/steam.rs index bab687e..7db54d6 100644 --- a/src/game/import/steam.rs +++ b/src/game/import/steam.rs @@ -2,7 +2,9 @@ use std::path::PathBuf; use steamlocate::SteamDir; -use super::{Error, GameImporter, ImportBase}; +use super::{GameImporter, ImportBase}; +use crate::error::Error; +use crate::game::error::GameError; use crate::game::registry::{ActiveDistribution, GameData}; use crate::ts::v1::models::ecosystem::GameDefPlatform; @@ -39,9 +41,9 @@ impl GameImporter for SteamImporter { .map_or_else(SteamDir::locate, |x| SteamDir::from_dir(x)) .map_err(|e: steamlocate::Error| match e { steamlocate::Error::InvalidSteamDir(_) => { - Error::SteamDirBadPath(self.steam_dir.as_ref().unwrap().to_path_buf()) + GameError::SteamDirBadPath(self.steam_dir.as_ref().unwrap().to_path_buf()) } - steamlocate::Error::FailedLocate(_) => Error::SteamDirNotFound, + steamlocate::Error::FailedLocate(_) => GameError::SteamDirNotFound, _ => unreachable!(), })?; @@ -54,14 +56,14 @@ impl GameImporter for SteamImporter { ) }) .ok_or_else(|| { - Error::SteamAppNotFound(self.appid, steam.path().to_path_buf()) + GameError::SteamAppNotFound(self.appid, steam.path().to_path_buf()) })?; lib.resolve_app_dir(&app) } }; if !app_dir.is_dir() { - Err(Error::SteamDirNotFound)?; + Err(GameError::SteamDirNotFound)?; } let r2mm = base.game_def.r2modman.as_ref().expect( @@ -74,8 +76,10 @@ impl GameImporter for SteamImporter { .map(|x| app_dir.join(x)) .find(|x| x.is_file()) .ok_or_else(|| { - super::Error::ExeNotFound(base.game_def.label.clone(), app_dir.clone()) - })?; + GameError::ExeNotFound { + possible_names: r2mm.exe_names.clone(), + base_path: app_dir.clone(), + }})?; let dist = ActiveDistribution { dist: GameDefPlatform::Steam { diff --git a/src/game/mod.rs b/src/game/mod.rs index 85e1eea..f6b4fde 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -2,3 +2,4 @@ pub mod ecosystem; pub mod import; pub mod proc; pub mod registry; +pub mod error; diff --git a/src/game/proc.rs b/src/game/proc.rs index 51d36f2..b9a6517 100644 --- a/src/game/proc.rs +++ b/src/game/proc.rs @@ -1,8 +1,8 @@ use std::{ffi::OsStr, path::{Path, PathBuf}}; use sysinfo::{ Pid, - ProcessExt, - System, + ProcessExt, + System, SystemExt }; @@ -23,7 +23,7 @@ pub fn get_pid_files(dir: &Path) -> Result, Error> { pub fn is_running(pid: usize) -> bool { let mut system = System::new(); system.refresh_processes(); - + system.process(Pid::from(pid)).is_some() } diff --git a/src/main.rs b/src/main.rs index e68f5d1..c45d329 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,16 +6,18 @@ use clap::Parser; use cli::InitSubcommand; use colored::Colorize; use directories::BaseDirs; +use error::{IoError, Error}; use game::import::GameImporter; use once_cell::sync::Lazy; +use project::error::ProjectError; use project::ProjectKind; +use ts::error::ApiError; use wildmatch::WildMatch; use crate::cli::{Args, Commands, ListSubcommand}; use crate::config::Vars; -use crate::error::Error; -use crate::game::{ecosystem, registry}; use crate::game::import::{self, ImportBase, ImportOverrides}; +use crate::game::{ecosystem, registry}; use crate::package::resolver::DependencyGraph; use crate::package::Package; use crate::project::lock::LockFile; @@ -29,7 +31,7 @@ mod error; mod game; mod package; mod project; -mod server; +// mod server; mod ts; mod ui; mod util; @@ -48,7 +50,7 @@ async fn main() -> Result<(), Error> { std::fs::create_dir_all(TCLI_HOME.as_path())?; } - match Args::parse().commands { + let test: Result<(), Error> = match Args::parse().commands { Commands::Init { command, overwrite, @@ -74,7 +76,7 @@ async fn main() -> Result<(), Error> { } Ok(()) - }, + } Commands::Build { package_name, package_namespace, @@ -88,7 +90,7 @@ async fn main() -> Result<(), Error> { .name_override(package_name) .version_override(package_version) .output_dir_override(output_dir); - + project.build(overrides)?; Ok(()) } @@ -103,31 +105,31 @@ async fn main() -> Result<(), Error> { } => { token = token.or_else(|| Vars::AuthKey.into_var().ok()); if token.is_none() { - return Err(Error::MissingAuthToken); + Err(ApiError::MissingAuthToken)?; } - + let project = Project::open(&project_path)?; let manifest = project.get_manifest()?; - + ts::init_repository( manifest .config .repository .as_deref() - .ok_or(Error::MissingRepository)?, + .ok_or(ProjectError::MissingRepository)?, token.as_deref(), ); let archive_path = match package_archive { Some(x) if x.is_file() => Ok(x), - Some(x) => Err(Error::FileNotFound(x)), + Some(x) => Err(IoError::FileNotFound(x))?, None => { let overrides = ProjectOverrides::new() .namespace_override(package_namespace) .name_override(package_name) .version_override(package_version) .repository_override(repository); - + project.build(overrides) } }?; @@ -183,9 +185,7 @@ async fn main() -> Result<(), Error> { custom_exe: None, game_dir: game_dir.clone(), }; - let import_base = ImportBase::new(&game_id) - .await? - .with_overrides(overrides); + let import_base = ImportBase::new(&game_id).await?.with_overrides(overrides); if platform.is_none() { let importer = import::select_importer(&import_base)?; @@ -200,61 +200,59 @@ async fn main() -> Result<(), Error> { let importer: Box = match (ident, platform.as_str()) { (Some(ident), "steam") => { - Box::new(import::steam::SteamImporter::new(ident).with_steam_dir(steam_dir)) as _ - } - (None, "nodrm") => { - Box::new(import::nodrm::NoDrmImporter::new(game_dir.as_ref().unwrap())) as _ + Box::new(import::steam::SteamImporter::new(ident).with_steam_dir(steam_dir)) + as _ } - _ => panic!("Manually importing games from '{platform}' is not implemented") + (None, "nodrm") => Box::new(import::nodrm::NoDrmImporter::new( + game_dir.as_ref().unwrap(), + )) as _, + _ => panic!("Manually importing games from '{platform}' is not implemented"), }; let game_data = importer.construct(import_base)?; let res = project.add_game_data(game_data); - println!("{} has been imported into the current project", game_id.green()); + println!( + "{} has been imported into the current project", + game_id.green() + ); res } - - Commands::Run { - game_id, - vanilla, - args, - tcli_directory: _, - repository: _, - project_path, - trailing_args + + Commands::Run { + game_id, + vanilla, + args, + tcli_directory: _, + repository: _, + project_path, + trailing_args, } => { let project = Project::open(&project_path)?; - let args = args.unwrap_or(vec![]) + let args = args + .unwrap_or(vec![]) .into_iter() .chain(trailing_args.into_iter()) .collect::>(); - - project.start_game( - &game_id, - !vanilla, - args, - ).await?; + + project.start_game(&game_id, !vanilla, args).await?; Ok(()) } - Commands::Stop { - id, - project_path, - } => { + Commands::Stop { id, project_path } => { match id.parse::() { Ok(x) => { game::proc::kill(x); - }, + } Err(_) => { let project = Project::open(&project_path)?; project.stop_game(&id)?; } }; - + Ok(()) } - + Commands::UpdateSchema {} => { ts::init_repository("https://thunderstore.io", None); @@ -287,7 +285,10 @@ async fn main() -> Result<(), Error> { Ok(()) } Commands::List { command } => match command { - ListSubcommand::Platforms { target, detected: _ } => { + ListSubcommand::Platforms { + target, + detected: _, + } => { let platforms = registry::get_supported_platforms(&target); println!("TCLI supports the following platforms on {target}"); @@ -339,7 +340,7 @@ async fn main() -> Result<(), Error> { for package in graph.digest() { let package = Package::from_any(package).await?; let Some(meta) = package.get_metadata().await? else { - continue + continue; }; let str = serde_json::to_string_pretty(&meta)?; @@ -349,5 +350,7 @@ async fn main() -> Result<(), Error> { Ok(()) } }, - } + }; + + test } diff --git a/src/package/cache.rs b/src/package/cache.rs index 3884eec..da88091 100644 --- a/src/package/cache.rs +++ b/src/package/cache.rs @@ -3,10 +3,10 @@ use std::path::PathBuf; use once_cell::sync::Lazy; -use crate::error::IoResultToTcli; +use crate::error::{IoResultToTcli, Error}; use crate::ts::package_reference::PackageReference; use crate::util::TempFile; -use crate::{Error, TCLI_HOME}; +use crate::TCLI_HOME; static CACHE_LOCATION: Lazy = Lazy::new(|| TCLI_HOME.join("package_cache")); diff --git a/src/package/error.rs b/src/package/error.rs new file mode 100644 index 0000000..84aa9d6 --- /dev/null +++ b/src/package/error.rs @@ -0,0 +1,35 @@ +use crate::ts::version::Version; + +#[derive(Debug, thiserror::Error)] +#[repr(u32)] +pub enum PackageError { + #[error("The installer does not contain a valid manifest.")] + InstallerNoManifest, + + #[error( + "The installer executable for the current OS and architecture combination does not exist." + )] + InstallerNotExecutable, + + #[error( + " + The installer '{package_id}' does not support the current tcli installer protocol. + Expected: {our_version:#?} + Recieved: {given_version:#?} + " + )] + InstallerBadVersion { + package_id: String, + given_version: Version, + our_version: Version, + }, + + #[error( + "The installer '{package_id}' did not respond correctly: + \t{message}" + )] + InstallerBadResponse { package_id: String, message: String }, + + #[error("The installer returned an error:\n\t{message}")] + InstallerError { message: String }, +} diff --git a/src/package/index.rs b/src/package/index.rs index 45ee5ba..78aae08 100644 --- a/src/package/index.rs +++ b/src/package/index.rs @@ -13,7 +13,7 @@ use tokio::fs::OpenOptions; use tokio::io::AsyncWriteExt; use crate::util::file; -use crate::error::Error; +use crate::error::{IoError, Error}; use crate::ts::experimental; use crate::ts::experimental::index::PackageIndexEntry; use crate::ts::package_reference::PackageReference; @@ -25,7 +25,7 @@ struct IndexHeader { } /// An index which contains packages and optimized methods to query them. -/// +/// /// Structurally this refers to three separate files, all contained within TCLI_HOME/index by default. /// 1. The package header `IndexHeader`. This contains index metadata like last update time, etc. /// 2. The package lookup table, `IndexLookup`. This is a fast-lookup datastructure which binds @@ -37,7 +37,7 @@ pub struct PackageIndex { // Yes, we're continuing this naming scheme. Why? I can't come up with anything better. tight_lookup: HashMap, - loose_lookup: HashMap>, + loose_lookup: HashMap>, index_file: File, } @@ -57,7 +57,7 @@ struct LookupTableEntry { impl PackageIndex { /// Determine if the package index requires an update. - /// + /// /// An update is requires if any of the following conditions are true: /// - Index version is less than the remote version /// - Index does not exist @@ -78,13 +78,13 @@ impl PackageIndex { } /// Syncronize the local and remote package index. - /// + /// /// This will syncronize regardless of local and remote update timestamps. /// Use `PackageIndex::requires_update` to determine if an index update is actually required. pub async fn sync(tcli_home: &Path) -> Result<(), Error> { // Assert internal file structure. if !tcli_home.is_dir() { - Err(Error::DirectoryNotFound(tcli_home.into()))?; + Err(IoError::DirNotFound(tcli_home.into()))?; } let index_dir = tcli_home.join("index"); @@ -132,7 +132,7 @@ impl PackageIndex { index_out.write_all(chunk.as_bytes()).await?; } - + let header_path = index_dir.join("header.json"); let header = IndexHeader { update_time: experimental::index::get_index_update_time().await? @@ -205,7 +205,7 @@ impl PackageIndex { .iter() .filter_map(|x| self.lookup.get(*x)) .filter_map(|x| self.read_index_string(x).ok()) - .map(|x| serde_json::from_str(&x)) + .map(|ref x| serde_json::from_str(x)) .collect::, _>>(); if let Err(ref e) = pkgs { diff --git a/src/package/install/mod.rs b/src/package/install/mod.rs index 4bee9cf..d6bcc29 100644 --- a/src/package/install/mod.rs +++ b/src/package/install/mod.rs @@ -11,9 +11,11 @@ use self::api::{Request, TrackedFile}; use self::api::Response; use self::api::PROTOCOL_VERSION; use self::manifest::InstallerManifest; +use super::error::PackageError; use super::Package; +use crate::error::IoError; +use crate::error::Error; use crate::ui::reporter::{Progress, VoidProgress, ProgressBarTrait}; -use crate::Error; pub mod api; mod legacy_compat; @@ -37,7 +39,7 @@ impl Installer { let manifest = { let path = cache_dir.join("installer.json"); if !path.is_file() { - Err(Error::InstallerNoManifest)? + Err(PackageError::InstallerNoManifest)? } else { let contents = fs::read_to_string(path)?; serde_json::from_str::(&contents)? @@ -54,7 +56,7 @@ impl Installer { .find(|x| { x.architecture.to_string() == current_arch && x.target_os.to_string() == current_os }) - .ok_or(Error::InstallerNotExecutable)?; + .ok_or(PackageError::InstallerNotExecutable)?; let exec_path = { let abs = cache_dir.join(&matrix.executable); @@ -62,7 +64,7 @@ impl Installer { if abs.is_file() { Ok(abs) } else { - Err(crate::Error::FileNotFound(abs)) + Err(IoError::FileNotFound(abs)) } }?; @@ -71,14 +73,14 @@ impl Installer { // Validate that the installer is (a) executable and (b) is using a valid protocol version. let response = installer.run(&Request::Version).await?; let Response::Version { author: _, identifier: _, protocol } = response else { - Err(Error::InstallerBadResponse { + Err(PackageError::InstallerBadResponse { package_id: package.identifier.to_string(), message: "The installer did not respond with a valid or otherwise serializable Version response variant.".to_string(), })? }; if protocol.major != PROTOCOL_VERSION.major { - Err(Error::InstallerBadVersion { + Err(PackageError::InstallerBadVersion { package_id: package.identifier.to_string(), given_version: protocol, our_version: PROTOCOL_VERSION, @@ -96,23 +98,23 @@ impl Installer { "TCLI_INSTALLER_OVERRIDE is set to {}, which does not point to a file that actually exists.", override_installer.to_str().unwrap() ) } - + Installer { exec_path: override_installer } } pub async fn install_package( - &self, - package: &Package, - package_dir: &Path, - state_dir: &Path, - staging_dir: &Path, + &self, + package: &Package, + package_dir: &Path, + state_dir: &Path, + staging_dir: &Path, reporter: &dyn ProgressBarTrait - ) -> Result, Error> { + ) -> Result, Error> { // Determine if the package is a modloader or not. let is_modloader = package.identifier.name.to_lowercase().contains("bepinex"); - + let request = Request::PackageInstall { is_modloader, package: package.identifier.clone(), @@ -129,7 +131,7 @@ impl Installer { package.identifier.version.to_string().truecolor(90, 90, 90) ); reporter.set_message(format!("Installing {progress_message}")); - + let response = self.run(&request).await?; match response { Response::PackageInstall { tracked_files, post_hook_context: _ } => { @@ -137,14 +139,14 @@ impl Installer { } Response::Error { message } => { - Err(Error::InstallerError { message }) + Err(PackageError::InstallerError { message })? } x => { - let message = + let message = format!("Didn't recieve one of the expected variants: Response::PackageInstall or Response::Error. Got: {x:#?}"); - - Err(Error::InstallerBadResponse { package_id: package.identifier.to_string(), message }) + + Err(PackageError::InstallerBadResponse { package_id: package.identifier.to_string(), message })? } } } @@ -180,12 +182,12 @@ impl Installer { let response = self.run(&request).await?; match response { Response::PackageUninstall { post_hook_context: _ } => Ok(()), - Response::Error { message } => Err(Error::InstallerError { message }), + Response::Error { message } => Err(PackageError::InstallerError { message })?, x => { let message = format!("Didn't recieve one of the expected variants: Response::PackageInstall or Response::Error. Got: {x:#?}"); - Err(Error::InstallerBadResponse { package_id: package.identifier.to_string(), message }) + Err(PackageError::InstallerBadResponse { package_id: package.identifier.to_string(), message })? } } } @@ -211,13 +213,13 @@ impl Installer { pub async fn run(&self, arg: &Request) -> Result { let args_json = serde_json::to_string(arg)?; - + let child = Command::new(&self.exec_path) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .arg(&args_json) .spawn()?; - + // Execute the installer, capturing and deserializing any output. // TODO: Safety check here to warn / stop an installer from blowing up the heap. let mut output_str = String::new(); diff --git a/src/package/mod.rs b/src/package/mod.rs index 46921a1..14738dd 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -2,6 +2,7 @@ mod cache; pub mod index; pub mod install; pub mod resolver; +pub mod error; use std::borrow::Borrow; use std::fs::File; @@ -15,7 +16,7 @@ use serde_with::{self, serde_as, DisplayFromStr}; use tokio::fs; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use crate::error::{Error, IoResultToTcli}; +use crate::error::{IoError, IoResultToTcli, Error}; use crate::ts::package_manifest::PackageManifestV1; use crate::ts::package_reference::PackageReference; use crate::ts::{self, CLIENT}; @@ -50,7 +51,7 @@ pub struct Package { } impl Package { - /// Attempt to resolve the package from the local cache or remote. + /// Attempt to resolve the package from the local cache or remote. /// This does not download the package, it just finds its "source". pub async fn from_any(ident: impl Borrow) -> Result { if cache::get_cache_location(ident.borrow()).exists() { @@ -154,7 +155,7 @@ impl Package { }; let icon = package_dir.join("icon.png"); let reference = package_dir.file_name().unwrap().to_string_lossy().to_string(); - + Ok(Some(PackageMetadata { manifest, reference, @@ -215,11 +216,14 @@ fn add_to_cache(package: &PackageReference, zipfile: impl Read + Seek) -> Result match std::fs::remove_dir_all(&output_path) { Ok(_) => (), Err(e) if e.kind() == ErrorKind::NotFound => (), - Err(e) => return Err(e).map_fs_error(&output_path), + Err(e) => Err(e).map_fs_error(&output_path)?, }; std::fs::create_dir_all(&output_path).map_fs_error(&output_path)?; - zip::read::ZipArchive::new(zipfile)?.extract(&output_path)?; + zip::read::ZipArchive::new(zipfile) + .map_err(IoError::ZipError)? + .extract(&output_path) + .map_err(IoError::ZipError)?; Ok(output_path) } diff --git a/src/project/error.rs b/src/project/error.rs new file mode 100644 index 0000000..2f61da1 --- /dev/null +++ b/src/project/error.rs @@ -0,0 +1,20 @@ +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +#[repr(u32)] +pub enum ProjectError { + #[error("A project configuration already exists at {0}.")] + ProjectAlreadyExists(PathBuf), + + #[error("No project exists at the path {0}.")] + NoProjectFile(PathBuf), + + #[error("Project is missing required table '{0}'.")] + MissingTable(&'static str), + + #[error("Missing repository url.")] + MissingRepository, + + #[error("The game identifier '{0}' does not exist within the ecosystem schema.")] + InvalidGameId(String), +} diff --git a/src/project/lock.rs b/src/project/lock.rs index bf8bbe2..0539adf 100644 --- a/src/project/lock.rs +++ b/src/project/lock.rs @@ -7,8 +7,8 @@ use md5::Md5; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::error::Error; use crate::package::Package; -use crate::Error; use crate::package::resolver::{DependencyGraph, InnerDepGraph}; #[derive(Serialize, Deserialize, Debug)] diff --git a/src/project/manifest.rs b/src/project/manifest.rs index c654304..ef6c694 100644 --- a/src/project/manifest.rs +++ b/src/project/manifest.rs @@ -8,17 +8,19 @@ use crate::project::overrides::ProjectOverrides; use crate::ts::package_reference::{self, PackageReference}; use crate::ts::version::Version; +use super::error::ProjectError; + #[derive(Serialize, Deserialize, Debug)] pub struct ProjectManifest { pub config: ConfigData, pub package: Option, pub build: Option, - + pub publish: Option>, - + #[serde(flatten)] pub dependencies: DependencyData, - + #[serde(skip)] pub project_dir: Option, } @@ -51,10 +53,10 @@ impl ProjectManifest { pub fn read_from_file(path: impl AsRef) -> Result { let path = path.as_ref(); - let text = fs::read_to_string(path).map_err(|_| Error::NoProjectFile(path.into()))?; - + let text = fs::read_to_string(path).map_err(|_| ProjectError::NoProjectFile(path.into()))?; + let mut manifest: ProjectManifest = toml::from_str(&text)?; - + manifest.project_dir = Some( path.parent() .map(|p| p.to_path_buf()) @@ -76,8 +78,8 @@ impl ProjectManifest { let package = self .package .as_mut() - .ok_or(Error::MissingTable("package"))?; - + .ok_or(ProjectError::MissingTable("package"))?; + if let Some(namespace) = overrides.namespace { package.namespace = namespace; } @@ -91,7 +93,7 @@ impl ProjectManifest { if let Some(output_dir) = overrides.output_dir { self.build .as_mut() - .ok_or(Error::MissingTable("build"))? + .ok_or(ProjectError::MissingTable("build"))? .outdir = output_dir; } if let Some(repository) = overrides.repository { @@ -201,7 +203,7 @@ pub struct DependencyData { #[serde(default)] #[serde(with = "package_reference::ser::table")] pub dependencies: Vec, - + #[serde(default)] #[serde(rename = "dev-dependencies")] #[serde(with = "package_reference::ser::table")] diff --git a/src/project/mod.rs b/src/project/mod.rs index 8365d2f..345ae8e 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -7,13 +7,14 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use colored::Colorize; +use error::ProjectError; use futures::future::try_join_all; pub use publish::publish; use tokio::sync::Semaphore; use zip::write::FileOptions; use self::lock::LockFile; -use crate::error::{Error, IoResultToTcli}; +use crate::error::{IoError, IoResultToTcli, Error}; use crate::game::registry::GameData; use crate::game::{proc, registry}; use crate::package::index::PackageIndex; @@ -34,6 +35,7 @@ pub mod manifest; pub mod overrides; mod publish; mod state; +pub mod error; pub enum ProjectKind { Dev(ProjectOverrides), @@ -78,7 +80,7 @@ impl Project { pub fn validate(&self) -> Result<(), Error> { // A directory without a manifest is *not* a project. if !self.manifest_path.is_file() { - Err(Error::NoProjectFile(self.manifest_path.to_path_buf()))?; + Err(ProjectError::NoProjectFile(self.manifest_path.to_path_buf()))?; } // Everything within .tcli is assumed to be replacable. Therefore we only care @@ -86,7 +88,7 @@ impl Project { let dotdir = self.base_dir.join(".tcli"); if !dotdir.is_dir() { fs::create_dir(dotdir)?; - } + } Ok(()) } @@ -118,7 +120,7 @@ impl Project { project_kind: ProjectKind, ) -> Result { if project_dir.is_file() { - return Err(Error::ProjectDirIsFile(project_dir.into())); + Err(IoError::DirectoryIsFile(project_dir.into()))?; } if !project_dir.is_dir() { @@ -146,9 +148,9 @@ impl Project { let mut manifest_file = match options.open(&manifest_path) { Ok(x) => Ok(x), Err(e) if e.kind() == ErrorKind::AlreadyExists => { - Err(Error::ProjectAlreadyExists(manifest_path.clone())) + Err(ProjectError::ProjectAlreadyExists(manifest_path.clone())) } - Err(e) => Err(Error::FileIoError(manifest_path.to_path_buf(), e)), + Err(e) => Err(IoError::Native(e, Some(manifest_path.to_path_buf())))?, }?; write!( @@ -196,7 +198,7 @@ impl Project { .write_all(include_bytes!("../../resources/icon.png")) .unwrap(), Err(e) if e.kind() == ErrorKind::AlreadyExists => {} - Err(e) => Err(Error::FileIoError(icon_path, e))?, + Err(e) => Err(IoError::Native(e, Some(icon_path)))?, } let readme_path = project_dir.join("README.md"); @@ -211,7 +213,7 @@ impl Project { package.namespace, package.name, package.description )?, Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} - Err(e) => return Err(Error::FileIoError(readme_path, e)), + Err(e) => Err(IoError::Native(e, Some(readme_path)))?, } let dist_dir = project.base_dir.join("dist"); @@ -509,7 +511,7 @@ impl Project { args: Vec, ) -> Result<(), Error> { let game_data = registry::get_game_data(&self.game_registry_path, game_id) - .ok_or_else(|| Error::InvalidGameId(game_id.to_string()))?; + .ok_or_else(|| ProjectError::InvalidGameId(game_id.to_string()))?; let game_dist = game_data.active_distribution; let game_dir = &game_dist.game_dir; @@ -567,13 +569,13 @@ impl Project { pub fn stop_game(&self, game_id: &str) -> Result<(), Error> { let game_data = registry::get_game_data(&self.game_registry_path, game_id) - .ok_or_else(|| Error::BadGameId(game_id.to_string()))?; + .ok_or_else(|| ProjectError::InvalidGameId(game_id.to_string()))?; let mut pid_file = self.base_dir.join(".tcli").join(game_data.identifier); pid_file.set_extension("pid"); if !pid_file.is_file() { - Err(Error::FileNotFound(pid_file.clone()))?; + Err(IoError::FileNotFound(pid_file.clone()))?; } let pid = fs::read_to_string(&pid_file)?.parse::().unwrap(); @@ -596,18 +598,18 @@ impl Project { let package = manifest .package .as_ref() - .ok_or(Error::MissingTable("package"))?; + .ok_or(ProjectError::MissingTable("package"))?; let build = manifest .build .as_ref() - .ok_or(Error::MissingTable("build"))?; + .ok_or(ProjectError::MissingTable("build"))?; let output_dir = project_dir.join(&build.outdir); match fs::create_dir_all(&output_dir) { Ok(_) => Ok(()), Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(Error::FileIoError(output_dir.clone(), e)), + Err(e) => Err(IoError::Native(e, Some(output_dir.clone()))), }?; let output_path = output_dir.join(format!( @@ -628,7 +630,7 @@ impl Project { // first elem is always the root, even when the path given is to a file for file in walkdir::WalkDir::new(&source_path).follow_links(true) { - let file = file?; + let file = file.map_err(IoError::DirWalker)?; let inner_path = file .path() diff --git a/src/project/publish.rs b/src/project/publish.rs index c6f9004..281be1a 100644 --- a/src/project/publish.rs +++ b/src/project/publish.rs @@ -1,10 +1,12 @@ use std::path::PathBuf; -use crate::error::Error; +use crate::error::{IoError, Error}; use crate::project::manifest::ProjectManifest; use crate::ts::experimental::models::publish::PackageSubmissionMetadata; use crate::ts::experimental::publish; +use super::error::ProjectError; + pub async fn publish( manifest: &ProjectManifest, archive_path: PathBuf, @@ -12,10 +14,10 @@ pub async fn publish( let package = manifest .package .as_ref() - .ok_or(Error::MissingTable("package"))?; + .ok_or(ProjectError::MissingTable("package"))?; if !archive_path.is_file() { - Err(Error::FileNotFound(archive_path.clone()))?; + Err(IoError::FileNotFound(archive_path.clone()))?; } let publish = manifest.publish.as_ref().unwrap(); diff --git a/src/ts/error.rs b/src/ts/error.rs new file mode 100644 index 0000000..e00f557 --- /dev/null +++ b/src/ts/error.rs @@ -0,0 +1,11 @@ +#[derive(Debug, thiserror::Error)] +pub enum ApiError { + #[error("Missing auth token.")] + MissingAuthToken, + + #[error("An API error occurred.")] + BadRequest { + source: reqwest::Error, + response_body: Option, + }, +} diff --git a/src/ts/experimental/index.rs b/src/ts/experimental/index.rs index 75b0ebe..57869a4 100644 --- a/src/ts/experimental/index.rs +++ b/src/ts/experimental/index.rs @@ -5,7 +5,7 @@ use futures::io::{self, BufReader, ErrorKind}; use futures_util::Stream; use serde::{Serialize, Deserialize}; -use crate::Error; +use crate::error::{IoError, Error}; use crate::ts::package_reference::PackageReference; use crate::ts::{CLIENT, EX}; use crate::ts::version::Version; @@ -26,7 +26,7 @@ pub async fn get_index() -> Result, Error> { .get(format!("{EX}/package-index")) .send().await? .error_for_status()?; - + let reader = response .bytes_stream() .map_err(|e| io::Error::new(ErrorKind::Other, e)) @@ -40,7 +40,7 @@ pub async fn get_index() -> Result, Error> { while let Some(line) = lines.next().await { let line = line?; let parsed = serde_json::from_str(&line)?; - + entries.push(parsed); } @@ -52,7 +52,7 @@ pub async fn get_index_streamed() -> Result Result serde_json::from_str(&x).map_err(|e| e.into()), - Err(e) => Err(Error::GenericIoError(e)) + Err(e) => Err(Error::Io(IoError::Native(e, None))) }); Ok(lines) } -pub async fn get_index_streamed_raw() -> Result>, Error> { +pub async fn get_index_streamed_raw() -> Result>, Error> { let response = CLIENT .get(format!("{EX}/package-index")) .send().await? @@ -84,7 +84,7 @@ pub async fn get_index_streamed_raw() -> Result Ok(x), - Err(e) => Err(Error::GenericIoError(e)) + Err(e) => Err(IoError::Native(e, None)), }); Ok(lines) diff --git a/src/ts/experimental/publish.rs b/src/ts/experimental/publish.rs index a9fc336..570fed9 100644 --- a/src/ts/experimental/publish.rs +++ b/src/ts/experimental/publish.rs @@ -12,6 +12,7 @@ use reqwest::{header, Body}; use tokio::io::{AsyncReadExt, AsyncSeekExt}; use crate::error::{Error, IoResultToTcli, ReqwestToTcli}; +use crate::ts::error::ApiError; use crate::ts::experimental::models::publish::*; use crate::ts::{AUTH, CLIENT, EX}; use crate::ui::PROGRESS_STYLE; @@ -23,7 +24,7 @@ pub async fn usermedia_initiate( .post(format!("{EX}/usermedia/initiate-upload/")) .header( header::AUTHORIZATION, - AUTH.get().ok_or(Error::MissingAuthToken)?, + AUTH.get().ok_or(ApiError::MissingAuthToken)?, ) .json(params) .send() @@ -42,7 +43,7 @@ pub async fn usermedia_finish( .post(format!("{EX}/usermedia/{uuid}/finish-upload/")) .header( header::AUTHORIZATION, - AUTH.get().ok_or(Error::MissingAuthToken)?, + AUTH.get().ok_or(ApiError::MissingAuthToken)?, ) .json(params) .send() @@ -57,7 +58,7 @@ pub async fn usermedia_abort(uuid: String) -> Result<(), Error> { .post(format!("{EX}/usermedia/{uuid}/abort-upload/")) .header( header::AUTHORIZATION, - AUTH.get().ok_or(Error::MissingAuthToken)?, + AUTH.get().ok_or(ApiError::MissingAuthToken)?, ) .send() .await? @@ -168,7 +169,7 @@ pub async fn package_submit( .post(format!("{EX}/submission/submit/")) .header( header::AUTHORIZATION, - AUTH.get().ok_or(Error::MissingAuthToken)?, + AUTH.get().ok_or(ApiError::MissingAuthToken)?, ) .json(params) .send() diff --git a/src/ts/mod.rs b/src/ts/mod.rs index 42ec782..a8c9ec9 100644 --- a/src/ts/mod.rs +++ b/src/ts/mod.rs @@ -9,6 +9,7 @@ pub mod package_manifest; pub mod package_reference; pub mod v1; pub mod version; +pub mod error; pub struct RepositoryUrl(OnceCell); diff --git a/src/util/file.rs b/src/util/file.rs index 21defdb..47b0bba 100644 --- a/src/util/file.rs +++ b/src/util/file.rs @@ -4,7 +4,7 @@ use std::path::Path; use md5::{Digest, Md5}; use md5::digest::FixedOutput; use walkdir::WalkDir; -use crate::error::Error; +use crate::error::{IoError, Error}; pub fn md5(file: &Path) -> Result { let mut md5 = Md5::new(); @@ -17,7 +17,7 @@ pub fn md5(file: &Path) -> Result { // Recursively remove empty directories starting at a given path. pub fn remove_empty_dirs(root: &Path, remove_root: bool) -> Result<(), Error> { if root.is_file() || !root.exists() { - Err(Error::DirectoryNotFound(root.to_path_buf()))?; + Err(IoError::DirNotFound(root.to_path_buf()))?; } let dirs = WalkDir::new(root) @@ -57,7 +57,7 @@ pub fn remove_empty_dirs(root: &Path, remove_root: bool) -> Result<(), Error> { } /// Read buf.len() bytes at the offset within the file. -/// +/// /// This function exists to ameliorate the differences in which Windows and Unix platforms /// implement file offset reads. pub fn read_offset(file: &File, buf: &mut [u8], offset: u64) -> Result { diff --git a/src/util/reg.rs b/src/util/reg.rs index f115886..6f5dc07 100644 --- a/src/util/reg.rs +++ b/src/util/reg.rs @@ -23,22 +23,24 @@ pub struct RegKeyVal { mod inner { use winreg::RegKey; - use super::{Error, HKey, RegKeyVal}; + use crate::error::IoError; - pub fn get_value_at(hkey: HKey, subkey: &str, name: &str) -> Result { + use super::{HKey, RegKeyVal}; + + pub fn get_value_at(hkey: HKey, subkey: &str, name: &str) -> Result { open_subkey(hkey, subkey)? .get_value(name) - .map_err(|_| Error::RegistryValueRead(subkey.to_string(), name.to_string())) + .map_err(|_| IoError::RegistryValueRead(subkey.to_string(), name.to_string())) } - pub fn get_keys_at(hkey: HKey, subkey: &str) -> Result, Error> { + pub fn get_keys_at(hkey: HKey, subkey: &str) -> Result, IoError> { open_subkey(hkey, subkey)? .enum_keys() .collect::, _>>() - .map_err(|_| Error::RegistrySubkeyRead(subkey.to_string())) + .map_err(|_| IoError::RegistrySubkeyRead(subkey.to_string())) } - pub fn get_values_at(hkey: HKey, subkey: &str) -> Result, Error> { + pub fn get_values_at(hkey: HKey, subkey: &str) -> Result, IoError> { open_subkey(hkey, subkey)? .enum_values() .map(|x| match x { @@ -49,14 +51,14 @@ mod inner { Err(e) => Err(e), }) .collect::, _>>() - .map_err(|_| Error::RegistrySubkeyRead(subkey.to_string())) + .map_err(|_| IoError::RegistrySubkeyRead(subkey.to_string())) } - fn open_subkey(hkey: HKey, subkey: &str) -> Result { + fn open_subkey(hkey: HKey, subkey: &str) -> Result { let local = RegKey::predef(hkey as _); local .open_subkey(subkey) - .map_err(|_| Error::RegistrySubkeyRead(subkey.to_string())) + .map_err(|_| IoError::RegistrySubkeyRead(subkey.to_string())) } } diff --git a/src/util/temp_file.rs b/src/util/temp_file.rs index f9f4fdb..f3f63ee 100644 --- a/src/util/temp_file.rs +++ b/src/util/temp_file.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; -use crate::error::{Error, IoResultToTcli}; +use crate::error::{IoResultToTcli, Error}; pub struct TempFile(Option, Option);