Replace how esplugin errors are thrown

Throw std::runtime_error for all esplugin errors except ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, which causes a PluginNotLoadedError to be thrown, since LOOT needs to be able to distinguish that error.
This commit is contained in:
Oliver Hamlet
2025-06-07 17:05:48 +01:00
parent ce729f8d76
commit e90dffd518
8 changed files with 36 additions and 104 deletions
+1 -2
View File
@@ -200,7 +200,6 @@ set(LIBLOOT_SRC_API_CPP_FILES
"${CMAKE_SOURCE_DIR}/src/api/api_database.cpp"
"${CMAKE_SOURCE_DIR}/src/api/bsa.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/undefined_group_error.cpp"
"${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.cpp"
"${CMAKE_SOURCE_DIR}/src/api/metadata/conditional_metadata.cpp"
@@ -231,8 +230,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"
+3 -8
View File
@@ -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:
+1 -1
View File
@@ -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"
@@ -22,21 +22,20 @@
<https://www.gnu.org/licenses/>.
*/
#ifndef LOOT_ERROR_CATEGORIES
#define LOOT_ERROR_CATEGORIES
#ifndef LOOT_EXCEPTION_PLUGIN_NOT_LOADED_ERROR
#define LOOT_EXCEPTION_PLUGIN_NOT_LOADED_ERROR
#include <system_error>
#include "loot/api_decorator.h"
#include <stdexcept>
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
-46
View File
@@ -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
<https://www.gnu.org/licenses/>.
*/
#include "loot/exception/error_categories.h"
#include <string>
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;
}
}
+11 -11
View File
@@ -34,7 +34,7 @@
#include "api/helpers/crc.h"
#include "api/helpers/logging.h"
#include "api/helpers/text.h"
#include "loot/exception/error_categories.h"
#include "loot/exception/plugin_not_loaded_error.h"
namespace {
using loot::BSA_FILE_EXTENSION;
@@ -170,14 +170,14 @@ void HandleEspluginError(unsigned int returnCode, std::string_view operation) {
}
auto err = fmt::format(
"esplugin failed to {}. Error code: {}", operation, returnCode);
"Failed to {}. esplugin error code: {}", operation, returnCode);
const char* e = nullptr;
esp_get_error_message(&e);
if (e == nullptr) {
const char* message = nullptr;
esp_get_error_message(&message);
if (message == nullptr) {
err += ". Details could not be fetched.";
} else {
err += ". Details: " + std::string(e);
err += ". Details: " + std::string(message);
}
auto logger = loot::getLogger();
@@ -185,7 +185,11 @@ void HandleEspluginError(unsigned int returnCode, std::string_view operation) {
logger->error(err);
}
throw std::system_error(returnCode, loot::esplugin_category(), err);
if (returnCode == ESP_ERROR_PLUGIN_METADATA_NOT_FOUND) {
throw loot::PluginNotLoadedError(err);
}
throw std::runtime_error(err);
}
template<typename... Args>
@@ -268,10 +272,6 @@ Plugin::Plugin(const GameType gameType,
tags_ = ExtractBashTags(description);
version_ = ExtractVersion(description);
} catch (const std::system_error& e) {
if (e.code().category() == esplugin_category()) {
throw;
}
if (logger) {
logger->error("Cannot read plugin file \"{}\". Details: {}",
pluginPath.u8string(),
+4 -14
View File
@@ -371,13 +371,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);
@@ -390,13 +385,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);
+7 -12
View File
@@ -27,7 +27,7 @@ along with LOOT. If not, see
#include "api/game/game.h"
#include "api/plugin.h"
#include "loot/exception/error_categories.h"
#include "loot/exception/plugin_not_loaded_error.h"
#include "tests/common_game_test_fixture.h"
namespace loot {
@@ -328,7 +328,7 @@ TEST_P(PluginTest, loadingWholePluginShouldSucceedForOpenMWPlugins) {
} else {
EXPECT_THROW(
Plugin(game_.GetType(), game_.GetCache(), dataPath / omwscripts, false),
std::system_error);
std::runtime_error);
}
}
@@ -354,16 +354,11 @@ TEST_P(
}
TEST_P(PluginTest, loadingAPluginThatDoesNotExistShouldThrow) {
try {
Plugin(game_.GetType(),
game_.GetCache(),
game_.DataPath() / "Blank\\.esp",
true);
FAIL();
} catch (const std::system_error& e) {
EXPECT_EQ(ESP_ERROR_FILE_NOT_FOUND, e.code().value());
EXPECT_EQ(esplugin_category(), e.code().category());
}
EXPECT_THROW(Plugin(game_.GetType(),
game_.GetCache(),
game_.DataPath() / "Blank\\.esp",
true),
std::runtime_error);
}
TEST_P(PluginTest, isValidShouldReturnTrueForAValidPlugin) {