diff --git a/Cargo.lock b/Cargo.lock index a22f1f69..c44ae9ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,7 +578,6 @@ dependencies = [ name = "libloot-ffi-errors" version = "0.26.3" dependencies = [ - "esplugin", "libloot", ] diff --git a/Cargo.toml b/Cargo.toml index 10827294..a4c7110a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "GPL-3.0-or-later" [dependencies] crc32fast = "1.4.2" +esplugin = "6.1.3" fancy-regex = "0.14.0" libloadorder = "18.4.0" log = { version = "0.4.26", features = ["std"] } @@ -16,8 +17,6 @@ rustc-hash = "2.1.1" saphyr = "0.0.4" unicase = "2.8.1" -esplugin.workspace = true - [target.'cfg(windows)'.dependencies] windows = { version = "0.61.1", features = ["Win32_Storage_FileSystem"] } @@ -28,9 +27,6 @@ tempfile = "3.17.1" [workspace] members = ["cpp", "ffi-errors", "nodejs", "parameterized-test", "python"] -[workspace.dependencies] -esplugin = "6.1.3" - [profile.release] debug = "limited" lto = "thin" diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 81a09871..f278d773 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -68,7 +68,6 @@ set(LIBLOOT_SRC_API_CPP_FILES "${CMAKE_SOURCE_DIR}/src/api/convert.cpp" "${CMAKE_SOURCE_DIR}/src/api/database.cpp" "${CMAKE_SOURCE_DIR}/src/api/exception/cyclic_interaction_error.cpp" - "${CMAKE_SOURCE_DIR}/src/api/exception/error_categories.cpp" "${CMAKE_SOURCE_DIR}/src/api/exception/exception.cpp" "${CMAKE_SOURCE_DIR}/src/api/exception/undefined_group_error.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/conditional_metadata.cpp" @@ -89,8 +88,8 @@ set(LIBLOOT_INCLUDE_H_FILES "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/include/loot/api_decorator.h" "${CMAKE_SOURCE_DIR}/include/loot/database_interface.h" - "${CMAKE_SOURCE_DIR}/include/loot/exception/error_categories.h" "${CMAKE_SOURCE_DIR}/include/loot/exception/cyclic_interaction_error.h" + "${CMAKE_SOURCE_DIR}/include/loot/exception/plugin_not_loaded_error.h" "${CMAKE_SOURCE_DIR}/include/loot/exception/undefined_group_error.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/edge_type.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/game_type.h" diff --git a/cpp/docs/api/reference.rst b/cpp/docs/api/reference.rst index fa110b1d..fbeef358 100644 --- a/cpp/docs/api/reference.rst +++ b/cpp/docs/api/reference.rst @@ -95,13 +95,8 @@ Exceptions .. doxygenclass:: loot::CyclicInteractionError :members: -.. doxygenclass:: loot::UndefinedGroupError +.. doxygenclass:: loot::PluginNotLoadedError :members: -Error Categories -================ - -LOOT uses error category objects to identify errors with codes that originate in -lower-level libraries. - -.. doxygenfunction:: loot::esplugin_category +.. doxygenclass:: loot::UndefinedGroupError + :members: diff --git a/cpp/include/loot/api.h b/cpp/include/loot/api.h index abdd10eb..a08320b1 100644 --- a/cpp/include/loot/api.h +++ b/cpp/include/loot/api.h @@ -35,7 +35,7 @@ #include "loot/enum/game_type.h" #include "loot/enum/log_level.h" #include "loot/exception/cyclic_interaction_error.h" -#include "loot/exception/error_categories.h" +#include "loot/exception/plugin_not_loaded_error.h" #include "loot/exception/undefined_group_error.h" #include "loot/game_interface.h" #include "loot/loot_version.h" diff --git a/cpp/include/loot/exception/error_categories.h b/cpp/include/loot/exception/plugin_not_loaded_error.h similarity index 67% rename from cpp/include/loot/exception/error_categories.h rename to cpp/include/loot/exception/plugin_not_loaded_error.h index d5c005c8..1743f2c2 100644 --- a/cpp/include/loot/exception/error_categories.h +++ b/cpp/include/loot/exception/plugin_not_loaded_error.h @@ -22,21 +22,20 @@ . */ -#ifndef LOOT_ERROR_CATEGORIES -#define LOOT_ERROR_CATEGORIES +#ifndef LOOT_EXCEPTION_PLUGIN_NOT_LOADED_ERROR +#define LOOT_EXCEPTION_PLUGIN_NOT_LOADED_ERROR -#include - -#include "loot/api_decorator.h" +#include namespace loot { /** - * @brief Get the error category that can be used to identify system_error - * exceptions that are due to esplugin errors. - * @returns A reference to the static object of unspecified runtime type, - * derived from std::error_category. + * @brief An exception class thrown if a plugin that must be loaded hasn't been + * loaded. */ -LOOT_API const std::error_category& esplugin_category(); +class PluginNotLoadedError : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; } #endif diff --git a/cpp/src/api/exception/error_categories.cpp b/cpp/src/api/exception/error_categories.cpp deleted file mode 100644 index c6035f07..00000000 --- a/cpp/src/api/exception/error_categories.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* LOOT - - A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and - Fallout: New Vegas. - - Copyright (C) 2012-2016 WrinklyNinja - - This file is part of LOOT. - - LOOT is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - LOOT is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with LOOT. If not, see - . - */ - -#include "loot/exception/error_categories.h" - -#include - -namespace loot { -namespace detail { -class esplugin_category : public std::error_category { - const char* name() const noexcept override { return "esplugin"; } - - std::string message(int) const override { return "esplugin error"; } - - bool equivalent(const std::error_code& code, int) const noexcept override { - return code.category().name() == name(); - } -}; -} - -LOOT_API const std::error_category& esplugin_category() { - static detail::esplugin_category instance; - return instance; -} -} diff --git a/cpp/src/api/exception/exception.cpp b/cpp/src/api/exception/exception.cpp index 3bbb7e75..e932f90c 100644 --- a/cpp/src/api/exception/exception.cpp +++ b/cpp/src/api/exception/exception.cpp @@ -3,7 +3,7 @@ #include #include "loot/exception/cyclic_interaction_error.h" -#include "loot/exception/error_categories.h" +#include "loot/exception/plugin_not_loaded_error.h" #include "loot/exception/undefined_group_error.h" #include "loot/vertex.h" @@ -15,7 +15,8 @@ using loot::Vertex; constexpr std::string_view CYCLIC_ERROR_PREFIX = "CyclicInteractionError: "sv; constexpr std::string_view UNDEFINED_GROUP_ERROR_PREFIX = "UndefinedGroupError: "sv; -constexpr std::string_view ESPLUGIN_ERROR_PREFIX = "EspluginError: "sv; +constexpr std::string_view PLUGIN_NOT_LOADED_ERROR_PREFIX = + "PluginNotLoadedError: "sv; constexpr std::string_view INVALID_ARGUMENT_PREFIX = "InvalidArgument: "sv; bool startsWith(std::string_view str, std::string_view prefix) { @@ -112,20 +113,6 @@ std::string getErrorSuffix(std::string_view what) { return std::string(what.substr(sepPos + 2)); } - -std::pair parseSystemError(std::string_view whatSuffix) { - const auto sepPos = whatSuffix.find(": "); - int code; - const auto result = - std::from_chars(whatSuffix.data(), whatSuffix.data() + sepPos, code); - if (result.ec != std::errc{}) { - std::string err = "Could not parse error code from string: "; - err += whatSuffix; - throw std::runtime_error(err); - } - - return std::make_pair(code, std::string(whatSuffix.substr(sepPos + 2))); -} } namespace loot { @@ -136,12 +123,10 @@ std::exception_ptr mapError(const ::rust::Error& error) { } else if (startsWith(error.what(), UNDEFINED_GROUP_ERROR_PREFIX)) { return std::make_exception_ptr( UndefinedGroupError(getErrorSuffix(error.what()))); - } else if (startsWith(error.what(), ESPLUGIN_ERROR_PREFIX)) { - const auto [code, details] = parseSystemError( - std::string_view(error.what()).substr(ESPLUGIN_ERROR_PREFIX.size())); - + } else if (startsWith(error.what(), PLUGIN_NOT_LOADED_ERROR_PREFIX)) { return std::make_exception_ptr( - std::system_error(code, esplugin_category(), details)); + PluginNotLoadedError("The plugin \"" + getErrorSuffix(error.what()) + + "\" has not been loaded")); } else if (startsWith(error.what(), INVALID_ARGUMENT_PREFIX)) { return std::make_exception_ptr( std::invalid_argument(getErrorSuffix(error.what()))); diff --git a/cpp/src/error.rs b/cpp/src/error.rs index d52c1f85..ccc5992b 100644 --- a/cpp/src/error.rs +++ b/cpp/src/error.rs @@ -1,8 +1,5 @@ use crate::game::NotValidUtf8; -use libloot_ffi_errors::{ - SystemError, SystemErrorCategory, UnsupportedEnumValueError, fmt_error_chain, - variant_box_from_error, -}; +use libloot_ffi_errors::{UnsupportedEnumValueError, fmt_error_chain, variant_box_from_error}; use libloot::{ error::{ @@ -19,7 +16,7 @@ use libloot::{ pub enum VerboseError { CyclicInteractionError(Vec), UndefinedGroupError(String), - SystemError(SystemError), + PluginNotLoadedError(String), InvalidArgument(String), Other(Box), } @@ -41,13 +38,7 @@ impl std::fmt::Display for VerboseError { Self::UndefinedGroupError(group) => { write!(f, "UndefinedGroupError: {group}",) } - Self::SystemError(e) => { - let prefix = match e.category() { - SystemErrorCategory::Esplugin => "EspluginError", - _ => "UnknownCategoryError", - }; - write!(f, "{}: {}: {}", prefix, e.code(), e.message()) - } + Self::PluginNotLoadedError(plugin) => write!(f, "PluginNotLoadedError: {plugin}"), Self::InvalidArgument(s) => write!(f, "InvalidArgument: {s}"), Self::Other(e) => fmt_error_chain(e.as_ref(), f), } @@ -65,6 +56,7 @@ variant_box_from_error!(ConditionEvaluationError, VerboseError::Other); variant_box_from_error!(MetadataRetrievalError, VerboseError::Other); variant_box_from_error!(LoadOrderError, VerboseError::Other); variant_box_from_error!(LoadOrderStateError, VerboseError::Other); +variant_box_from_error!(PluginDataError, VerboseError::Other); impl From for VerboseError { fn from(value: GameHandleCreationError) -> Self { @@ -78,11 +70,12 @@ impl From for VerboseError { impl From for VerboseError { fn from(value: LoadPluginsError) -> Self { match value { - LoadPluginsError::PluginDataError(e) => e.into(), + LoadPluginsError::PluginNotLoaded(p) => Self::PluginNotLoadedError(p), LoadPluginsError::PluginValidationError(_) => Self::InvalidArgument(value.to_string()), - LoadPluginsError::DatabaseLockPoisoned | LoadPluginsError::IoError(_) | _ => { - Self::Other(Box::new(value)) - } + LoadPluginsError::DatabaseLockPoisoned + | LoadPluginsError::IoError(_) + | LoadPluginsError::PluginDataError(_) + | _ => Self::Other(Box::new(value)), } } } @@ -92,11 +85,11 @@ impl From for VerboseError { match value { SortPluginsError::UndefinedGroup(g) => Self::UndefinedGroupError(g), SortPluginsError::CycleFound(cycle) => Self::CyclicInteractionError(cycle), - SortPluginsError::PluginDataError(e) => e.into(), + SortPluginsError::PluginNotLoaded(p) => Self::PluginNotLoadedError(p), SortPluginsError::DatabaseLockPoisoned - | SortPluginsError::PluginNotLoaded(_) | SortPluginsError::CycleFoundInvolving(_) | SortPluginsError::PathfindingError(_) + | SortPluginsError::PluginDataError(_) | _ => Self::Other(Box::new(value)), } } @@ -112,12 +105,6 @@ impl From for VerboseError { } } -impl From for VerboseError { - fn from(value: PluginDataError) -> Self { - Self::SystemError(SystemError::from(value)) - } -} - #[derive(Clone, Copy, Debug)] pub struct EmptyOptionalError; diff --git a/cpp/src/tests/api/interface/game_interface_test.h b/cpp/src/tests/api/interface/game_interface_test.h index b1c72236..9682deda 100644 --- a/cpp/src/tests/api/interface/game_interface_test.h +++ b/cpp/src/tests/api/interface/game_interface_test.h @@ -370,13 +370,8 @@ TEST_P( if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::starfield) { - try { - handle_->LoadPlugins({blankMasterDependentEsm}, false); - FAIL(); - } catch (const std::system_error& e) { - EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value()); - EXPECT_EQ(esplugin_category(), e.code().category()); - } + EXPECT_THROW(handle_->LoadPlugins({blankMasterDependentEsm}, false), + PluginNotLoadedError); } else { handle_->LoadPlugins({blankMasterDependentEsm}, false); @@ -389,13 +384,8 @@ TEST_P( loadPluginsShouldThrowIfAPluginHasAMasterThatIsNotInTheInputAndIsNotAlreadyLoadedAndGameIsMorrowindOrStarfield) { if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw || GetParam() == GameType::starfield) { - try { - handle_->LoadPlugins({blankMasterDependentEsm}, false); - FAIL(); - } catch (const std::system_error& e) { - EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value()); - EXPECT_EQ(esplugin_category(), e.code().category()); - } + EXPECT_THROW(handle_->LoadPlugins({blankMasterDependentEsm}, false), + PluginNotLoadedError); } else { handle_->LoadPlugins({blankMasterDependentEsm}, false); @@ -476,7 +466,7 @@ TEST_P(GameInterfaceTest, TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) { std::vector plugins{blankEsp, blankDifferentEsp}; - EXPECT_THROW(handle_->SortPlugins(plugins), std::runtime_error); + EXPECT_THROW(handle_->SortPlugins(plugins), PluginNotLoadedError); } TEST_P(GameInterfaceTest, clearLoadedPluginsShouldClearThePluginsCache) { diff --git a/ffi-errors/Cargo.toml b/ffi-errors/Cargo.toml index af1b3a93..8d1d5ed8 100644 --- a/ffi-errors/Cargo.toml +++ b/ffi-errors/Cargo.toml @@ -6,5 +6,3 @@ license = "GPL-3.0-or-later" [dependencies] libloot = { path = ".." } - -esplugin.workspace = true diff --git a/ffi-errors/src/esplugin.rs b/ffi-errors/src/esplugin.rs deleted file mode 100644 index 67e8df2f..00000000 --- a/ffi-errors/src/esplugin.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Unless otherwise noted, these constants and the mapping logic are copied from -// esplugin-ffi. - -use std::ffi::c_int; - -use esplugin::Error; - -const ESP_ERROR_PARSE_ERROR: c_int = 5; - -const ESP_ERROR_NO_FILENAME: c_int = 7; - -const ESP_ERROR_TEXT_DECODE_ERROR: c_int = 8; - -const ESP_ERROR_IO_ERROR: c_int = 10; - -const ESP_ERROR_FILE_NOT_FOUND: c_int = 11; - -const ESP_ERROR_IO_PERMISSION_DENIED: c_int = 12; - -const ESP_ERROR_UNRESOLVED_RECORD_IDS: c_int = 13; - -const ESP_ERROR_PLUGIN_METADATA_NOT_FOUND: c_int = 14; - -// This constant is not copied from esplugin-ffi, but does not conflict with any -// values defined there. -pub(crate) const ESP_ERROR_UNKNOWN: c_int = c_int::MAX; - -#[expect( - clippy::wildcard_enum_match_arm, - reason = "It doesn't matter if other I/O error kinds are added in the future" -)] -fn map_io_error(err: &std::io::Error) -> c_int { - match err.kind() { - std::io::ErrorKind::NotFound => ESP_ERROR_FILE_NOT_FOUND, - std::io::ErrorKind::PermissionDenied => ESP_ERROR_IO_PERMISSION_DENIED, - _ => ESP_ERROR_IO_ERROR, - } -} - -#[must_use] -pub(crate) fn map_error(err: &Error) -> c_int { - match err { - Error::IoError(x) => map_io_error(x), - Error::NoFilename(_) => ESP_ERROR_NO_FILENAME, - Error::ParsingIncomplete(_) | Error::ParsingError(_, _) => ESP_ERROR_PARSE_ERROR, - Error::DecodeError(_) => ESP_ERROR_TEXT_DECODE_ERROR, - Error::UnresolvedRecordIds(_) => ESP_ERROR_UNRESOLVED_RECORD_IDS, - Error::PluginMetadataNotFound(_) => ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, - } -} diff --git a/ffi-errors/src/lib.rs b/ffi-errors/src/lib.rs index 13e9567b..5372e070 100644 --- a/ffi-errors/src/lib.rs +++ b/ffi-errors/src/lib.rs @@ -92,100 +92,6 @@ clippy::verbose_file_reads, clippy::wildcard_enum_match_arm )] -use std::{error::Error, ffi::c_int}; - -use libloot::error::PluginDataError; - -mod esplugin; - -// It's important for API stability that these variants' values don't change. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[repr(u8)] -#[non_exhaustive] -pub enum SystemErrorCategory { - Esplugin = 1, -} - -impl std::fmt::Display for SystemErrorCategory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SystemErrorCategory::Esplugin => write!(f, "esplugin"), - } - } -} - -#[derive(Debug)] -pub struct SystemError { - code: c_int, - category: SystemErrorCategory, - message: String, -} - -impl SystemError { - #[must_use] - pub fn code(&self) -> c_int { - self.code - } - - #[must_use] - pub fn category(&self) -> SystemErrorCategory { - self.category - } - - #[must_use] - pub fn message(&self) -> &str { - &self.message - } -} - -impl std::fmt::Display for SystemError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} error, code {}: {}", - self.category, self.code, self.message - ) - } -} - -impl std::error::Error for SystemError {} - -impl From for SystemError { - fn from(value: PluginDataError) -> Self { - const ESPLUGIN_ERROR_UNKNOWN: (i32, &str) = ( - esplugin::ESP_ERROR_UNKNOWN, - "Could not retrieve esplugin error message", - ); - - from_error( - value, - SystemErrorCategory::Esplugin, - ESPLUGIN_ERROR_UNKNOWN, - crate::esplugin::map_error, - ) - } -} - -fn from_error c_int>( - error: U, - category: SystemErrorCategory, - default: (i32, &'static str), - to_code: F, -) -> SystemError { - let (code, message) = error - .source() - .and_then(|s| s.downcast_ref::()) - .map_or_else( - || (default.0, default.1.to_owned()), - |e| (to_code(e), e.to_string()), - ); - - SystemError { - code, - category, - message, - } -} #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct UnsupportedEnumValueError; diff --git a/nodejs/src/error.rs b/nodejs/src/error.rs index da0ba8aa..cc0ae3b3 100644 --- a/nodejs/src/error.rs +++ b/nodejs/src/error.rs @@ -8,7 +8,7 @@ use libloot::{ LoadMetadataError, MultilingualMessageContentsError, RegexError, WriteMetadataError, }, }; -use libloot_ffi_errors::{fmt_error_chain, SystemError, UnsupportedEnumValueError}; +use libloot_ffi_errors::{fmt_error_chain, UnsupportedEnumValueError}; #[derive(Debug)] pub struct VerboseError(Box); @@ -43,12 +43,7 @@ box_from_error!(GroupsPathError, VerboseError); box_from_error!(MetadataRetrievalError, VerboseError); box_from_error!(MultilingualMessageContentsError, VerboseError); box_from_error!(RegexError, VerboseError); - -impl From for VerboseError { - fn from(value: PluginDataError) -> Self { - Self(Box::new(SystemError::from(value))) - } -} +box_from_error!(PluginDataError, VerboseError); impl From for napi::Error { fn from(value: VerboseError) -> Self { diff --git a/python/README.md b/python/README.md index f939c807..5f0a51ff 100644 --- a/python/README.md +++ b/python/README.md @@ -45,6 +45,6 @@ python ## Usage notes - The Python exceptions that errors are mapped to are not the same as in the Rust or C++ interfaces: - - The API provides the custom `CyclicInteractionError`, `UndefinedGroupError`, `EspluginError` exception types. + - The API provides the custom `CyclicInteractionError`, `UndefinedGroupError`, `PluginNotLoadedError` exception types. - All other errors are raised as `ValueError` exceptions. - The `LogLevel` enum and `set_logging_callback()` and `set_log_level()` functions are not exposed because the logging is integrated with Python's `logging` module instead. diff --git a/python/src/error.rs b/python/src/error.rs index 05f4bf64..6c552fde 100644 --- a/python/src/error.rs +++ b/python/src/error.rs @@ -8,18 +8,16 @@ use libloot::{ LoadMetadataError, MultilingualMessageContentsError, RegexError, WriteMetadataError, }, }; -use libloot_ffi_errors::{ - SystemError, UnsupportedEnumValueError, fmt_error_chain, variant_box_from_error, -}; +use libloot_ffi_errors::{UnsupportedEnumValueError, fmt_error_chain, variant_box_from_error}; use pyo3::{PyErr, exceptions::PyValueError}; -use crate::{CyclicInteractionError, EspluginError, UndefinedGroupError, database::Vertex}; +use crate::{CyclicInteractionError, PluginNotLoadedError, UndefinedGroupError, database::Vertex}; #[derive(Debug)] pub enum VerboseError { CyclicInteractionError(Vec), UndefinedGroupError(String), - EspluginError(SystemError), + PluginNotLoadedError(String), Other(Box), } @@ -28,7 +26,7 @@ impl std::fmt::Display for VerboseError { match self { Self::CyclicInteractionError(c) => SortPluginsError::CycleFound(c.clone()).fmt(f), Self::UndefinedGroupError(g) => SortPluginsError::UndefinedGroup(g.clone()).fmt(f), - Self::EspluginError(e) => e.message().fmt(f), + Self::PluginNotLoadedError(p) => SortPluginsError::PluginNotLoaded(p.clone()).fmt(f), Self::Other(e) => fmt_error_chain(e.as_ref(), f), } } @@ -46,17 +44,18 @@ variant_box_from_error!(RegexError, VerboseError::Other); variant_box_from_error!(GameHandleCreationError, VerboseError::Other); variant_box_from_error!(LoadOrderStateError, VerboseError::Other); variant_box_from_error!(MetadataRetrievalError, VerboseError::Other); +variant_box_from_error!(PluginDataError, VerboseError::Other); impl From for VerboseError { fn from(value: SortPluginsError) -> Self { match value { SortPluginsError::UndefinedGroup(g) => Self::UndefinedGroupError(g), SortPluginsError::CycleFound(cycle) => Self::CyclicInteractionError(cycle), - SortPluginsError::PluginDataError(e) => e.into(), + SortPluginsError::PluginNotLoaded(n) => Self::PluginNotLoadedError(n), SortPluginsError::DatabaseLockPoisoned - | SortPluginsError::PluginNotLoaded(_) | SortPluginsError::CycleFoundInvolving(_) | SortPluginsError::PathfindingError(_) + | SortPluginsError::PluginDataError(_) | _ => Self::Other(Box::new(value)), } } @@ -72,12 +71,6 @@ impl From for VerboseError { } } -impl From for VerboseError { - fn from(value: PluginDataError) -> Self { - Self::EspluginError(SystemError::from(value)) - } -} - impl From for PyErr { fn from(value: VerboseError) -> Self { let message = value.to_string(); @@ -90,8 +83,8 @@ impl From for PyErr { VerboseError::UndefinedGroupError(g) => { PyErr::new::((g, message)) } - VerboseError::EspluginError(e) => { - PyErr::new::((e.code(), e.message().to_owned())) + VerboseError::PluginNotLoadedError(p) => { + PyErr::new::((p, message)) } VerboseError::Other(_) => PyValueError::new_err(message), } diff --git a/python/src/lib.rs b/python/src/lib.rs index 05584058..803a8ae9 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -125,7 +125,7 @@ fn libloot_version() -> String { create_exception!(loot, CyclicInteractionError, PyException); create_exception!(loot, UndefinedGroupError, PyException); -create_exception!(loot, EspluginError, PyException); +create_exception!(loot, PluginNotLoadedError, PyException); /// A Python module implemented in Rust. #[pymodule(name = "loot")] @@ -165,7 +165,10 @@ fn libloot_pyo3(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { py.get_type::(), )?; m.add("UndefinedGroupError", py.get_type::())?; - m.add("EspluginError", py.get_type::())?; + m.add( + "PluginNotLoadedError", + py.get_type::(), + )?; Ok(()) } diff --git a/src/error.rs b/src/error.rs index a0b1e707..e25a8580 100644 --- a/src/error.rs +++ b/src/error.rs @@ -98,6 +98,7 @@ pub enum LoadPluginsError { IoError(Box), PluginValidationError(Box), PluginDataError(PluginDataError), + PluginNotLoaded(String), } impl std::fmt::Display for LoadPluginsError { @@ -107,6 +108,7 @@ impl std::fmt::Display for LoadPluginsError { Self::IoError(_) => write!(f, "an I/O error occurred"), Self::PluginValidationError(_) => write!(f, "failed validation of input plugin paths"), Self::PluginDataError(_) => write!(f, "failed to read loaded plugin data"), + Self::PluginNotLoaded(n) => write!(f, "the plugin \"{n}\" has not been loaded"), } } } @@ -114,7 +116,7 @@ impl std::fmt::Display for LoadPluginsError { impl std::error::Error for LoadPluginsError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::DatabaseLockPoisoned => None, + Self::DatabaseLockPoisoned | Self::PluginNotLoaded(_) => None, Self::IoError(e) => Some(e), Self::PluginValidationError(e) => Some(e.as_ref()), Self::PluginDataError(e) => Some(e), @@ -148,7 +150,11 @@ impl From for LoadPluginsError { impl From for LoadPluginsError { fn from(value: PluginDataError) -> Self { - LoadPluginsError::PluginDataError(value) + if let Some(plugin) = value.plugin_not_loaded() { + Self::PluginNotLoaded(plugin.to_owned()) + } else { + Self::PluginDataError(value) + } } } @@ -264,7 +270,11 @@ impl From for SortPluginsError { impl From for SortPluginsError { fn from(value: PluginDataError) -> Self { - SortPluginsError::PluginDataError(value) + if let Some(plugin) = value.plugin_not_loaded() { + Self::PluginNotLoaded(plugin.to_owned()) + } else { + Self::PluginDataError(value) + } } } diff --git a/src/game.rs b/src/game.rs index 236ce575..f3adf326 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1521,8 +1521,6 @@ mod tests { } mod load_plugins { - use std::error::Error; - use crate::tests::BLANK_FULL_ESM; use super::*; @@ -1617,17 +1615,11 @@ mod tests { GameType::Morrowind | GameType::OpenMW | GameType::Starfield ) { match game.load_plugins(paths) { - Err(LoadPluginsError::PluginDataError(e)) => { - let source = e.source().unwrap(); - match source.downcast_ref::().unwrap() { - esplugin::Error::PluginMetadataNotFound(p) => { - if game_type == GameType::Starfield { - assert_eq!(BLANK_FULL_ESM, p); - } else { - assert_eq!(BLANK_ESM, p); - } - } - _ => panic!("Unexpected esplugin error: {e}"), + Err(LoadPluginsError::PluginNotLoaded(p)) => { + if game_type == GameType::Starfield { + assert_eq!(BLANK_FULL_ESM, p); + } else { + assert_eq!(BLANK_ESM, p); } } _ => panic!("Expected an error due to esplugin metadata not found"), diff --git a/src/plugin/error.rs b/src/plugin/error.rs index 376b0db2..2b2de98a 100644 --- a/src/plugin/error.rs +++ b/src/plugin/error.rs @@ -8,6 +8,15 @@ use crate::escape_ascii; #[derive(Debug)] pub struct PluginDataError(esplugin::Error); +impl PluginDataError { + pub(crate) fn plugin_not_loaded(&self) -> Option<&str> { + match &self.0 { + esplugin::Error::PluginMetadataNotFound(p) => Some(p.as_str()), + _ => None, + } + } +} + impl std::fmt::Display for PluginDataError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "failed to read plugin data")