diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 91773e4452..55013f331d 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -216,36 +216,46 @@ echo testoutput' > "$CACHE_DIR"/extensions/test_extension/test_extension chmod +x "$CACHE_DIR"/extensions/test_extension/test_extension assert_command_fail dfx extension list - assert_match "Error.*Cannot load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" + assert_match "Error.*Failed to load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" assert_command_fail dfx extension run test_extension - assert_match "Error.*Cannot load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" + assert_match "Error.*Failed to load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" assert_command_fail dfx test_extension - assert_match "Error.*Cannot load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" + assert_match "Error.*Failed to load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" assert_command_fail dfx --help - assert_match "Error.*Cannot load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" + assert_match "Error.*Failed to load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" assert_command_fail dfx test_extension --help - assert_match "Error.*Cannot load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" + assert_match "Error.*Failed to load extension manifest.*Failed to read JSON file.*Failed to read .*extensions/test_extension/extension.json.*No such file or directory" echo "{}" > "$CACHE_DIR"/extensions/test_extension/extension.json assert_command_fail dfx extension list - assert_match "Error.*Cannot load extension manifest.*Failed to parse contents of .*extensions/test_extension/extension.json as json.* missing field .* at line .* column .*" + assert_contains "Failed to load extension manifest" + assert_match "Failed to parse contents of .*extensions/test_extension/extension.json as json" + assert_match "missing field .* at line .* column .*" assert_command_fail dfx extension run test_extension - assert_match "Error.*Cannot load extension manifest.*Failed to parse contents of .*extensions/test_extension/extension.json as json.* missing field .* at line .* column .*" + assert_contains "Failed to load extension manifest" + assert_match "Failed to parse contents of .*extensions/test_extension/extension.json as json.*" + assert_match "missing field .* at line .* column .*" assert_command_fail dfx test_extension - assert_match "Error.*Cannot load extension manifest.*Failed to parse contents of .*extensions/test_extension/extension.json as json.* missing field .* at line .* column .*" + assert_contains "Failed to load extension manifest" + assert_match "Failed to parse contents of .*extensions/test_extension/extension.json as json.*" + assert_match "missing field .* at line .* column .*" assert_command_fail dfx --help - assert_match "Error.*Cannot load extension manifest.*Failed to parse contents of .*extensions/test_extension/extension.json as json.* missing field .* at line .* column .*" + assert_contains "Failed to load extension manifest" + assert_match "Failed to parse contents of .*extensions/test_extension/extension.json as json.*" + assert_match "missing field .* at line .* column .*" assert_command_fail dfx test_extension --help - assert_match "Error.*Cannot load extension manifest.*Failed to parse contents of .*extensions/test_extension/extension.json as json.* missing field .* at line .* column .*" + assert_contains "Failed to load extension manifest" + assert_match "Failed to parse contents of .*extensions/test_extension/extension.json as json.*" + assert_match "missing field .* at line .* column .*" echo '{ "name": "test_extension", diff --git a/src/dfx-core/src/error/config.rs b/src/dfx-core/src/error/config.rs index 17eef4e364..b74eb229a6 100644 --- a/src/dfx-core/src/error/config.rs +++ b/src/dfx-core/src/error/config.rs @@ -1,4 +1,4 @@ -use crate::error::extension::ExtensionError; +use crate::error::extension::LoadExtensionManifestError; use crate::error::fs::FsError; use crate::error::get_user_home::GetUserHomeError; use handlebars::RenderError; @@ -79,7 +79,7 @@ pub enum ApplyExtensionCanisterTypeError { NoExtensionForUnknownCanisterType { canister: String, extension: String }, #[error(transparent)] - LoadExtensionManifest(ExtensionError), + LoadExtensionManifest(LoadExtensionManifestError), #[error("canister '{canister}' has type '{extension}', but that extension does not define a canister type")] ExtensionDoesNotDefineCanisterType { canister: String, extension: String }, diff --git a/src/dfx-core/src/error/extension.rs b/src/dfx-core/src/error/extension.rs index 0d6c6e075e..4d7fe5b9f1 100644 --- a/src/dfx-core/src/error/extension.rs +++ b/src/dfx-core/src/error/extension.rs @@ -1,97 +1,165 @@ #![allow(dead_code)] +use crate::error::structured_file::StructuredFileError; use thiserror::Error; #[derive(Error, Debug)] -pub enum ExtensionError { - // errors related to extension directory management - #[error("Cannot find cache directory")] - FindCacheDirectoryFailed(#[source] crate::error::cache::CacheError), +#[error("Failed to load extension manifest")] +pub struct LoadExtensionManifestError(#[from] StructuredFileError); - #[error("Cannot get extensions directory")] - EnsureExtensionDirExistsFailed(#[source] crate::error::fs::FsError), +#[derive(Error, Debug)] +pub enum ConvertExtensionIntoClapCommandError { + #[error(transparent)] + LoadExtensionManifest(#[from] LoadExtensionManifestError), - #[error("Extension directory '{0}' does not exist.")] - ExtensionDirDoesNotExist(std::path::PathBuf), + #[error(transparent)] + ListInstalledExtensionsError(#[from] ListInstalledExtensionsError), - #[error("Extension '{0}' not installed.")] - ExtensionNotInstalled(String), + #[error(transparent)] + ConvertExtensionSubcommandIntoClapCommandError( + #[from] ConvertExtensionSubcommandIntoClapCommandError, + ), +} - // errors related to installing extensions - #[error("Extension '{0}' is already installed.")] - ExtensionAlreadyInstalled(String), +#[derive(Error, Debug)] +pub enum ConvertExtensionSubcommandIntoClapCommandError { + #[error(transparent)] + ConvertExtensionSubcommandIntoClapArgError(#[from] ConvertExtensionSubcommandIntoClapArgError), +} - #[error("Extension '{0}' cannot be installed because it conflicts with an existing command. Consider using '--install-as' flag to install this extension under different name.")] - CommandAlreadyExists(String), +#[derive(Error, Debug)] +pub enum ListInstalledExtensionsError { + #[error(transparent)] + ExtensionsDirectoryIsNotReadable(#[from] crate::error::fs::FsError), +} - #[error("Cannot fetch compatibility.json from '{0}'")] - CompatibilityMatrixFetchError(String, #[source] reqwest::Error), +#[derive(Error, Debug)] +pub enum ConvertExtensionSubcommandIntoClapArgError { + #[error("Extension's subcommand argument '{0}' is missing description.")] + ExtensionSubcommandArgMissingDescription(String), +} - #[error("Cannot parse compatibility.json")] - MalformedCompatibilityMatrix(#[source] reqwest::Error), +#[derive(Error, Debug)] +pub enum RunExtensionError { + #[error("Invalid extension name '{0:?}'.")] + InvalidExtensionName(std::ffi::OsString), - #[error("Cannot parse compatibility.json due to malformed semver '{0}'")] - MalformedVersionsEntryForExtensionInCompatibilityMatrix(String, #[source] semver::Error), + #[error("Cannot find cache directory")] + FindCacheDirectoryFailed(#[source] crate::error::cache::CacheError), - #[error("Cannot find compatible extension for dfx version '{1}': compatibility.json (downloaded from '{0}') has empty list of extension versions.")] - ListOfVersionsForExtensionIsEmpty(String, semver::Version), + #[error("Failed to run extension '{0}'")] + FailedToLaunchExtension(String, #[source] std::io::Error), - #[error("Cannot parse extension manifest URL '{0}'")] - MalformedExtensionDownloadUrl(String, #[source] url::ParseError), + #[error("Extension '{0}' never finished")] + ExtensionNeverFinishedExecuting(String, #[source] std::io::Error), - #[error("DFX version '{0}' is not supported.")] - DfxVersionNotFoundInCompatibilityJson(semver::Version), + #[error("Extension terminated by signal.")] + ExtensionExecutionTerminatedViaSignal, - #[error("Extension '{0}' (version '{1}') not found for DFX version {2}.")] - ExtensionVersionNotFoundInRepository(String, semver::Version, String), + #[error("Extension exited with non-zero status code '{0}'.")] + ExtensionExitedWithNonZeroStatus(i32), + + #[error(transparent)] + GetExtensionBinaryError(#[from] GetExtensionBinaryError), +} + +#[derive(Error, Debug)] +pub enum GetExtensionBinaryError { + #[error("Extension '{0}' not installed.")] + ExtensionNotInstalled(String), + + #[error("Cannot find extension binary at '{0}'.")] + ExtensionBinaryDoesNotExist(std::path::PathBuf), + + #[error("Extension binary at {0} is not an executable file.")] + ExtensionBinaryIsNotAFile(std::path::PathBuf), +} + +#[derive(Error, Debug)] +pub enum NewExtensionManagerError { + #[error("Cannot find cache directory")] + FindCacheDirectoryFailed(#[source] crate::error::cache::CacheError), +} +#[derive(Error, Debug)] +pub enum DownloadAndInstallExtensionToTempdirError { #[error("Downloading extension from '{0}' failed")] ExtensionDownloadFailed(url::Url, #[source] reqwest::Error), - #[error("Cannot decompress extension archive (downloaded from: '{0}')")] - DecompressFailed(url::Url, #[source] std::io::Error), + #[error("Cannot get extensions directory")] + EnsureExtensionDirExistsFailed(#[source] crate::error::fs::FsError), #[error("Cannot create temporary directory at '{0}'")] CreateTemporaryDirectoryFailed(std::path::PathBuf, #[source] std::io::Error), + #[error("Cannot decompress extension archive (downloaded from: '{0}')")] + DecompressFailed(url::Url, #[source] std::io::Error), +} + +#[derive(Error, Debug)] +pub enum InstallExtensionError { + #[error("Extension '{0}' is already installed.")] + ExtensionAlreadyInstalled(String), + #[error(transparent)] - Io(#[from] crate::error::fs::FsError), + GetExtensionArchiveName(#[from] GetExtensionArchiveNameError), - #[error("Platform '{0}' is not supported.")] - PlatformNotSupported(String), + #[error(transparent)] + FindLatestExtensionCompatibleVersion(#[from] FindLatestExtensionCompatibleVersionError), - // errors related to uninstalling extensions - #[error("Cannot uninstall extension")] - InsufficientPermissionsToDeleteExtensionDirectory(#[source] crate::error::fs::FsError), + #[error(transparent)] + GetExtensionDownloadUrl(#[from] GetExtensionDownloadUrlError), - // errors related to listing extensions - #[error("Cannot list extensions")] - ExtensionsDirectoryIsNotReadable(#[source] crate::error::fs::FsError), + #[error(transparent)] + DownloadAndInstallExtensionToTempdir(#[from] DownloadAndInstallExtensionToTempdirError), - #[error("Cannot load extension manifest")] - LoadExtensionManifestFailed(#[source] crate::error::structured_file::StructuredFileError), + #[error(transparent)] + FinalizeInstallation(#[from] FinalizeInstallationError), +} - // errors related to executing extensions - #[error("Invalid extension name '{0:?}'.")] - InvalidExtensionName(std::ffi::OsString), +#[derive(Error, Debug)] +pub enum GetExtensionArchiveNameError { + #[error("Platform '{0}' is not supported.")] + PlatformNotSupported(String), +} - #[error("Extension's subcommand argument '{0}' is missing description.")] - ExtensionSubcommandArgMissingDescription(String), +#[derive(Error, Debug)] +pub enum FindLatestExtensionCompatibleVersionError { + #[error("DFX version '{0}' is not supported.")] + DfxVersionNotFoundInCompatibilityJson(semver::Version), - #[error("Cannot find extension binary at '{0}'.")] - ExtensionBinaryDoesNotExist(std::path::PathBuf), + #[error("Extension '{0}' (version '{1}') not found for DFX version {2}.")] + ExtensionVersionNotFoundInRepository(String, semver::Version, String), - #[error("Extension binary at {0} is not an executable file.")] - ExtensionBinaryIsNotAFile(std::path::PathBuf), + #[error("Cannot parse compatibility.json due to malformed semver '{0}'")] + MalformedVersionsEntryForExtensionInCompatibilityMatrix(String, #[source] semver::Error), - #[error("Failed to run extension '{0}'")] - FailedToLaunchExtension(String, #[source] std::io::Error), + #[error("Cannot find compatible extension for dfx version '{1}': compatibility.json (downloaded from '{0}') has empty list of extension versions.")] + ListOfVersionsForExtensionIsEmpty(String, semver::Version), - #[error("Extension '{0}' never finished")] - ExtensionNeverFinishedExecuting(String, #[source] std::io::Error), + #[error(transparent)] + FetchExtensionCompatibilityMatrix(#[from] FetchExtensionCompatibilityMatrixError), +} - #[error("Extension terminated by signal.")] - ExtensionExecutionTerminatedViaSignal, +#[derive(Error, Debug)] +#[error("Failed to parse extension manifest URL '{url}'")] +pub struct GetExtensionDownloadUrlError { + pub url: String, + pub source: url::ParseError, +} - #[error("Extension exited with non-zero status code '{0}'.")] - ExtensionExitedWithNonZeroStatus(i32), +#[derive(Error, Debug)] +#[error(transparent)] +pub struct FinalizeInstallationError(#[from] crate::error::fs::FsError); + +#[derive(Error, Debug)] +pub enum FetchExtensionCompatibilityMatrixError { + #[error("Cannot fetch compatibility.json from '{0}'")] + CompatibilityMatrixFetchError(String, #[source] reqwest::Error), + + #[error("Cannot parse compatibility.json")] + MalformedCompatibilityMatrix(#[source] reqwest::Error), } + +#[derive(Error, Debug)] +#[error(transparent)] +pub struct UninstallExtensionError(#[from] crate::error::fs::FsError); diff --git a/src/dfx-core/src/extension/manager/execute.rs b/src/dfx-core/src/extension/manager/execute.rs index 5e91608de5..68629d6a7f 100644 --- a/src/dfx-core/src/extension/manager/execute.rs +++ b/src/dfx-core/src/extension/manager/execute.rs @@ -1,6 +1,6 @@ use super::ExtensionManager; use crate::config::cache::get_bin_cache; -use crate::error::extension::ExtensionError; +use crate::error::extension::RunExtensionError; use std::ffi::OsString; impl ExtensionManager { @@ -8,32 +8,32 @@ impl ExtensionManager { &self, extension_name: OsString, mut params: Vec, - ) -> Result<(), ExtensionError> { + ) -> Result<(), RunExtensionError> { let extension_name = extension_name .into_string() - .map_err(ExtensionError::InvalidExtensionName)?; + .map_err(RunExtensionError::InvalidExtensionName)?; let mut extension_binary = self.get_extension_binary(&extension_name)?; let dfx_cache = get_bin_cache(self.dfx_version.to_string().as_str()) - .map_err(ExtensionError::FindCacheDirectoryFailed)?; + .map_err(RunExtensionError::FindCacheDirectoryFailed)?; params.extend(["--dfx-cache-path".into(), dfx_cache.into_os_string()]); let mut child = extension_binary .args(¶ms) .spawn() - .map_err(|e| ExtensionError::FailedToLaunchExtension(extension_name.clone(), e))?; + .map_err(|e| RunExtensionError::FailedToLaunchExtension(extension_name.clone(), e))?; let exit_status = child.wait().map_err(|e| { - ExtensionError::ExtensionNeverFinishedExecuting(extension_name.clone(), e) + RunExtensionError::ExtensionNeverFinishedExecuting(extension_name.clone(), e) })?; let code = exit_status .code() - .ok_or(ExtensionError::ExtensionExecutionTerminatedViaSignal)?; + .ok_or(RunExtensionError::ExtensionExecutionTerminatedViaSignal)?; if code != 0 { - Err(ExtensionError::ExtensionExitedWithNonZeroStatus(code)) + Err(RunExtensionError::ExtensionExitedWithNonZeroStatus(code)) } else { Ok(()) } diff --git a/src/dfx-core/src/extension/manager/install.rs b/src/dfx-core/src/extension/manager/install.rs index f25b550d2d..0bb6321729 100644 --- a/src/dfx-core/src/extension/manager/install.rs +++ b/src/dfx-core/src/extension/manager/install.rs @@ -1,4 +1,8 @@ -use crate::error::extension::ExtensionError; +use crate::error::extension::{ + DownloadAndInstallExtensionToTempdirError, FinalizeInstallationError, + FindLatestExtensionCompatibleVersionError, GetExtensionArchiveNameError, + GetExtensionDownloadUrlError, InstallExtensionError, +}; use crate::extension::{manager::ExtensionManager, manifest::ExtensionCompatibilityMatrix}; use flate2::read::GzDecoder; use reqwest::Url; @@ -18,14 +22,14 @@ impl ExtensionManager { extension_name: &str, install_as: Option<&str>, version: Option<&Version>, - ) -> Result<(), ExtensionError> { + ) -> Result<(), InstallExtensionError> { let effective_extension_name = install_as.unwrap_or(extension_name); if self .get_extension_directory(effective_extension_name) .exists() { - return Err(ExtensionError::ExtensionAlreadyInstalled( + return Err(InstallExtensionError::ExtensionAlreadyInstalled( effective_extension_name.to_string(), )); } @@ -63,7 +67,7 @@ impl ExtensionManager { fn get_extension_compatible_version( &self, extension_name: &str, - ) -> Result { + ) -> Result { let manifest = ExtensionCompatibilityMatrix::fetch()?; let dfx_version = self.dfx_version_strip_semver(); manifest.find_latest_compatible_extension_version(extension_name, &dfx_version) @@ -72,25 +76,35 @@ impl ExtensionManager { fn download_and_unpack_extension_to_tempdir( &self, download_url: Url, - ) -> Result { - let response = reqwest::blocking::get(download_url.clone()) - .map_err(|e| ExtensionError::ExtensionDownloadFailed(download_url.clone(), e))?; + ) -> Result { + let response = reqwest::blocking::get(download_url.clone()).map_err(|e| { + DownloadAndInstallExtensionToTempdirError::ExtensionDownloadFailed( + download_url.clone(), + e, + ) + })?; - let bytes = response - .bytes() - .map_err(|e| ExtensionError::ExtensionDownloadFailed(download_url.clone(), e))?; + let bytes = response.bytes().map_err(|e| { + DownloadAndInstallExtensionToTempdirError::ExtensionDownloadFailed( + download_url.clone(), + e, + ) + })?; crate::fs::composite::ensure_dir_exists(&self.dir) - .map_err(ExtensionError::EnsureExtensionDirExistsFailed)?; + .map_err(DownloadAndInstallExtensionToTempdirError::EnsureExtensionDirExistsFailed)?; let temp_dir = tempdir_in(&self.dir).map_err(|e| { - ExtensionError::CreateTemporaryDirectoryFailed(self.dir.to_path_buf(), e) + DownloadAndInstallExtensionToTempdirError::CreateTemporaryDirectoryFailed( + self.dir.to_path_buf(), + e, + ) })?; let mut archive = Archive::new(GzDecoder::new(Cursor::new(bytes))); - archive - .unpack(temp_dir.path()) - .map_err(|e| ExtensionError::DecompressFailed(download_url, e))?; + archive.unpack(temp_dir.path()).map_err(|e| { + DownloadAndInstallExtensionToTempdirError::DecompressFailed(download_url, e) + })?; Ok(temp_dir) } @@ -101,7 +115,7 @@ impl ExtensionManager { effective_extension_name: &str, extension_unarchived_dir_name: &str, temp_dir: TempDir, - ) -> Result<(), ExtensionError> { + ) -> Result<(), FinalizeInstallationError> { let effective_extension_dir = &self.get_extension_directory(effective_extension_name); crate::fs::rename( &temp_dir.path().join(extension_unarchived_dir_name), @@ -126,17 +140,21 @@ impl ExtensionManager { fn get_extension_download_url( github_release_tag: &str, extension_archive_name: &str, -) -> Result { +) -> Result { let download_url = format!("{DFINITY_DFX_EXTENSIONS_RELEASES_URL}/{github_release_tag}/{extension_archive_name}.tar.gz",); - Url::parse(&download_url) - .map_err(|e| ExtensionError::MalformedExtensionDownloadUrl(download_url, e)) + Url::parse(&download_url).map_err(|source| GetExtensionDownloadUrlError { + url: download_url, + source, + }) } fn get_git_release_tag(extension_name: &str, extension_verion: &Version) -> String { format!("{extension_name}-v{extension_verion}",) } -fn get_extension_archive_name(extension_name: &str) -> Result { +fn get_extension_archive_name( + extension_name: &str, +) -> Result { Ok(format!( "{extension_name}-{arch}-{platform}", platform = match std::env::consts::OS { @@ -144,7 +162,7 @@ fn get_extension_archive_name(extension_name: &str) -> Result "apple-darwin", // "windows" => "pc-windows-msvc", unsupported_platform => - return Err(ExtensionError::PlatformNotSupported( + return Err(GetExtensionArchiveNameError::PlatformNotSupported( unsupported_platform.to_string() )), }, diff --git a/src/dfx-core/src/extension/manager/list.rs b/src/dfx-core/src/extension/manager/list.rs index cd30fa037c..7b358136a0 100644 --- a/src/dfx-core/src/extension/manager/list.rs +++ b/src/dfx-core/src/extension/manager/list.rs @@ -1,13 +1,16 @@ use super::ExtensionManager; -use crate::{error::extension::ExtensionError, extension::Extension}; +use crate::error::extension::{ConvertExtensionIntoClapCommandError, ListInstalledExtensionsError}; +use crate::extension::Extension; impl ExtensionManager { - pub fn list_installed_extensions(&self) -> Result, ExtensionError> { + pub fn list_installed_extensions( + &self, + ) -> Result, ListInstalledExtensionsError> { if !&self.dir.exists() { return Ok(vec![]); } let dir_content = crate::fs::read_dir(&self.dir) - .map_err(ExtensionError::ExtensionsDirectoryIsNotReadable)?; + .map_err(ListInstalledExtensionsError::ExtensionsDirectoryIsNotReadable)?; Ok(dir_content .filter_map(|v| { @@ -25,7 +28,7 @@ impl ExtensionManager { pub fn installed_extensions_as_clap_commands( &self, - ) -> Result, ExtensionError> { + ) -> Result, ConvertExtensionIntoClapCommandError> { let mut extensions = vec![]; for ext in self.list_installed_extensions()? { extensions.push(ext.into_clap_command(self)?); diff --git a/src/dfx-core/src/extension/manager/mod.rs b/src/dfx-core/src/extension/manager/mod.rs index 9a0f446343..60ba8ee9d4 100644 --- a/src/dfx-core/src/extension/manager/mod.rs +++ b/src/dfx-core/src/extension/manager/mod.rs @@ -1,5 +1,5 @@ use crate::config::cache::get_cache_path_for_version; -use crate::error::extension::ExtensionError; +use crate::error::extension::{GetExtensionBinaryError, NewExtensionManagerError}; use semver::Version; use std::path::PathBuf; @@ -14,9 +14,9 @@ pub struct ExtensionManager { } impl ExtensionManager { - pub fn new(version: &Version) -> Result { + pub fn new(version: &Version) -> Result { let extensions_dir = get_cache_path_for_version(&version.to_string()) - .map_err(ExtensionError::FindCacheDirectoryFailed)? + .map_err(NewExtensionManagerError::FindCacheDirectoryFailed)? .join("extensions"); Ok(Self { @@ -32,18 +32,18 @@ impl ExtensionManager { pub fn get_extension_binary( &self, extension_name: &str, - ) -> Result { + ) -> Result { let dir = self.get_extension_directory(extension_name); if !dir.exists() { - return Err(ExtensionError::ExtensionNotInstalled( + return Err(GetExtensionBinaryError::ExtensionNotInstalled( extension_name.to_string(), )); } let bin = dir.join(extension_name); if !bin.exists() { - Err(ExtensionError::ExtensionBinaryDoesNotExist(bin)) + Err(GetExtensionBinaryError::ExtensionBinaryDoesNotExist(bin)) } else if !bin.is_file() { - Err(ExtensionError::ExtensionBinaryIsNotAFile(bin)) + Err(GetExtensionBinaryError::ExtensionBinaryIsNotAFile(bin)) } else { Ok(std::process::Command::new(bin)) } diff --git a/src/dfx-core/src/extension/manager/uninstall.rs b/src/dfx-core/src/extension/manager/uninstall.rs index f6ba6c02ee..d2d2538bc1 100644 --- a/src/dfx-core/src/extension/manager/uninstall.rs +++ b/src/dfx-core/src/extension/manager/uninstall.rs @@ -1,11 +1,10 @@ use super::ExtensionManager; -use crate::error::extension::ExtensionError; +use crate::error::extension::UninstallExtensionError; impl ExtensionManager { - pub fn uninstall_extension(&self, extension_name: &str) -> Result<(), ExtensionError> { + pub fn uninstall_extension(&self, extension_name: &str) -> Result<(), UninstallExtensionError> { let path = self.get_extension_directory(extension_name); - crate::fs::remove_dir_all(&path) - .map_err(ExtensionError::InsufficientPermissionsToDeleteExtensionDirectory)?; + crate::fs::remove_dir_all(&path)?; Ok(()) } } diff --git a/src/dfx-core/src/extension/manifest/compatibility_matrix.rs b/src/dfx-core/src/extension/manifest/compatibility_matrix.rs index 33c979dd85..d83257f0bf 100644 --- a/src/dfx-core/src/extension/manifest/compatibility_matrix.rs +++ b/src/dfx-core/src/extension/manifest/compatibility_matrix.rs @@ -1,4 +1,14 @@ -use crate::error::extension::ExtensionError; +use crate::error::extension::{ + FetchExtensionCompatibilityMatrixError, + FetchExtensionCompatibilityMatrixError::{ + CompatibilityMatrixFetchError, MalformedCompatibilityMatrix, + }, + FindLatestExtensionCompatibleVersionError, + FindLatestExtensionCompatibleVersionError::{ + DfxVersionNotFoundInCompatibilityJson, ExtensionVersionNotFoundInRepository, + ListOfVersionsForExtensionIsEmpty, MalformedVersionsEntryForExtensionInCompatibilityMatrix, + }, +}; use schemars::JsonSchema; use semver::Version; use serde::Deserialize; @@ -21,29 +31,26 @@ pub struct ExtensionCompatibleVersions { } impl ExtensionCompatibilityMatrix { - pub fn fetch() -> Result { + pub fn fetch() -> Result { let resp = reqwest::blocking::get(COMMON_EXTENSIONS_MANIFEST_LOCATION).map_err(|e| { - ExtensionError::CompatibilityMatrixFetchError( - COMMON_EXTENSIONS_MANIFEST_LOCATION.to_string(), - e, - ) + CompatibilityMatrixFetchError(COMMON_EXTENSIONS_MANIFEST_LOCATION.to_string(), e) })?; - resp.json() - .map_err(ExtensionError::MalformedCompatibilityMatrix) + resp.json().map_err(MalformedCompatibilityMatrix) } pub fn find_latest_compatible_extension_version( &self, extension_name: &str, dfx_version: &Version, - ) -> Result { - let manifests = self.0.get(dfx_version).ok_or_else(|| { - ExtensionError::DfxVersionNotFoundInCompatibilityJson(dfx_version.clone()) - })?; + ) -> Result { + let manifests = self + .0 + .get(dfx_version) + .ok_or_else(|| DfxVersionNotFoundInCompatibilityJson(dfx_version.clone()))?; let extension_location = manifests.get(extension_name).ok_or_else(|| { - ExtensionError::ExtensionVersionNotFoundInRepository( + ExtensionVersionNotFoundInRepository( extension_name.to_string(), dfx_version.clone(), COMMON_EXTENSIONS_MANIFEST_LOCATION.to_string(), @@ -52,17 +59,14 @@ impl ExtensionCompatibilityMatrix { let mut extension_versions = vec![]; for ext_verion in extension_location.versions.iter().rev() { let version = Version::parse(ext_verion).map_err(|e| { - ExtensionError::MalformedVersionsEntryForExtensionInCompatibilityMatrix( - ext_verion.to_string(), - e, - ) + MalformedVersionsEntryForExtensionInCompatibilityMatrix(ext_verion.to_string(), e) })?; extension_versions.push(version); } extension_versions.sort(); extension_versions.reverse(); extension_versions.first().cloned().ok_or_else(|| { - ExtensionError::ListOfVersionsForExtensionIsEmpty( + ListOfVersionsForExtensionIsEmpty( COMMON_EXTENSIONS_MANIFEST_LOCATION.to_string(), dfx_version.clone(), ) diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index 7b9678efe8..cd79ee3266 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -1,4 +1,7 @@ -use crate::error::extension::ExtensionError; +use crate::error::extension::{ + ConvertExtensionSubcommandIntoClapArgError, ConvertExtensionSubcommandIntoClapCommandError, + LoadExtensionManifestError, +}; use serde::{Deserialize, Deserializer}; use serde_json::Value; use std::path::PathBuf; @@ -29,10 +32,12 @@ pub struct ExtensionManifest { } impl ExtensionManifest { - pub fn load(name: &str, extensions_root_dir: &Path) -> Result { + pub fn load( + name: &str, + extensions_root_dir: &Path, + ) -> Result { let manifest_path = Self::manifest_path(name, extensions_root_dir); - let mut m: ExtensionManifest = crate::json::load_json_file(&manifest_path) - .map_err(ExtensionError::LoadExtensionManifestFailed)?; + let mut m: ExtensionManifest = crate::json::load_json_file(&manifest_path)?; m.name = name.to_string(); Ok(m) } @@ -45,7 +50,9 @@ impl ExtensionManifest { extensions_root_dir.join(name).join(MANIFEST_FILE_NAME) } - pub fn into_clap_commands(self) -> Result, ExtensionError> { + pub fn into_clap_commands( + self, + ) -> Result, ConvertExtensionSubcommandIntoClapCommandError> { self.subcommands .unwrap_or_default() .0 @@ -152,12 +159,15 @@ impl<'de> Deserialize<'de> for ArgNumberOfValues { } impl ExtensionSubcommandArgOpts { - pub fn into_clap_arg(self, name: String) -> Result { + pub fn into_clap_arg( + self, + name: String, + ) -> Result { let mut arg = clap::Arg::new(name.clone()); if let Some(about) = self.about { arg = arg.help(about); } else { - return Err(ExtensionError::ExtensionSubcommandArgMissingDescription( + return Err(ConvertExtensionSubcommandIntoClapArgError::ExtensionSubcommandArgMissingDescription( name, )); } @@ -186,7 +196,10 @@ impl ExtensionSubcommandArgOpts { } impl ExtensionSubcommandOpts { - pub fn into_clap_command(self, name: String) -> Result { + pub fn into_clap_command( + self, + name: String, + ) -> Result { let mut cmd = clap::Command::new(name); if let Some(about) = self.about { diff --git a/src/dfx-core/src/extension/mod.rs b/src/dfx-core/src/extension/mod.rs index 25d6c4188b..2b283274c7 100644 --- a/src/dfx-core/src/extension/mod.rs +++ b/src/dfx-core/src/extension/mod.rs @@ -1,6 +1,6 @@ pub mod manager; pub mod manifest; -use crate::error::extension::ExtensionError; +use crate::error::extension::ConvertExtensionIntoClapCommandError; use crate::extension::{manager::ExtensionManager, manifest::ExtensionManifest}; use clap::Command; use std::{ @@ -27,7 +27,10 @@ impl Display for Extension { } impl Extension { - pub fn into_clap_command(self, manager: &ExtensionManager) -> Result { + pub fn into_clap_command( + self, + manager: &ExtensionManager, + ) -> Result { let manifest = ExtensionManifest::load(&self.name, &manager.dir)?; let cmd = Command::new(&self.name) // don't accept unknown options diff --git a/src/dfx/src/commands/extension/install.rs b/src/dfx/src/commands/extension/install.rs index 357da8fd71..9024209e94 100644 --- a/src/dfx/src/commands/extension/install.rs +++ b/src/dfx/src/commands/extension/install.rs @@ -2,9 +2,9 @@ use crate::commands::DfxCommand; use crate::config::cache::DiskBasedCache; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use anyhow::bail; use clap::Parser; use clap::Subcommand; -use dfx_core::error::extension::ExtensionError; use semver::Version; #[derive(Parser)] @@ -27,7 +27,7 @@ pub fn exec(env: &dyn Environment, opts: InstallOpts) -> DfxResult<()> { let mgr = env.get_extension_manager(); let effective_extension_name = opts.install_as.clone().unwrap_or_else(|| opts.name.clone()); if DfxCommand::has_subcommand(&effective_extension_name) { - return Err(ExtensionError::CommandAlreadyExists(opts.name).into()); + bail!("Extension '{}' cannot be installed because it conflicts with an existing command. Consider using '--install-as' flag to install this extension under different name.", opts.name) } mgr.install_extension(