Throw esplugin errors as system_error exceptions

Instead of FileAccessError, so that callers can use the exception's code to distinguish between different causes.
This commit is contained in:
Oliver Hamlet
2024-08-24 10:02:35 +01:00
parent 575455b08c
commit 945d6b5a7a
5 changed files with 84 additions and 21 deletions
@@ -30,6 +30,14 @@
#include "loot/api_decorator.h"
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.
*/
LOOT_API const std::error_category& esplugin_category();
/**
* @brief Get the error category that can be used to identify system_error
* exceptions that are due to libloadorder errors.
+15
View File
@@ -28,6 +28,16 @@
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();
}
};
class libloadorder_category : public std::error_category {
const char* name() const noexcept override { return "libloadorder"; }
@@ -39,6 +49,11 @@ class libloadorder_category : public std::error_category {
};
}
LOOT_API const std::error_category& esplugin_category() {
static detail::esplugin_category instance;
return instance;
}
LOOT_API const std::error_category& libloadorder_category() {
static detail::libloadorder_category instance;
return instance;
+14 -1
View File
@@ -33,6 +33,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/file_access_error.h"
namespace loot {
@@ -201,7 +202,7 @@ void HandleEspluginError(const std::string& operation,
logger->error(err);
}
throw FileAccessError(err);
throw std::system_error(returnCode, esplugin_category(), err);
}
Plugin::Plugin(const GameType gameType,
@@ -239,6 +240,18 @@ Plugin::Plugin(const GameType gameType,
}
tags_ = ExtractBashTags(GetDescription());
} catch (const std::system_error& e) {
if (e.code().category() == esplugin_category()) {
throw;
}
if (logger) {
logger->error("Cannot read plugin file \"{}\". Details: {}",
pluginPath.u8string(),
e.what());
}
throw FileAccessError("Cannot read \"" + pluginPath.u8string() +
"\". Details: " + e.what());
} catch (const std::exception& e) {
if (logger) {
logger->error("Cannot read plugin file \"{}\". Details: {}",
+26
View File
@@ -26,6 +26,7 @@ along with LOOT. If not, see
#define LOOT_TESTS_API_INTERNALS_GAME_GAME_TEST
#include "api/game/game.h"
#include "loot/exception/error_categories.h"
#include "tests/common_game_test_fixture.h"
namespace loot {
@@ -363,6 +364,31 @@ TEST_P(GameTest, loadPluginsShouldUseAbsolutePathsAsGiven) {
EXPECT_NE(nullptr, game.GetPlugin(blankEsm));
}
TEST_P(
GameTest,
loadPluginsShouldThrowIfFullyLoadingAPluginWithAMissingMasterIfGameIsMorrowindOrStarfield) {
Game game = Game(GetParam(), dataPath.parent_path(), localPath);
const auto pluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
std::filesystem::remove(dataPath / pluginName);
if (GetParam() == GameType::tes3 || GetParam() == GameType::starfield) {
try {
game.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());
}
} else {
game.LoadPlugins({blankMasterDependentEsm}, false);
EXPECT_NE(nullptr, game.GetPlugin(blankMasterDependentEsm));
}
}
TEST_P(GameTest, sortPluginsShouldHandlePluginPathsThatAreNotJustFilenames) {
Game game = Game(GetParam(), dataPath.parent_path(), localPath);
+21 -20
View File
@@ -27,6 +27,7 @@ along with LOOT. If not, see
#include "api/game/game.h"
#include "api/plugin.h"
#include "loot/exception/error_categories.h"
#include "tests/common_game_test_fixture.h"
namespace loot {
@@ -337,8 +338,7 @@ TEST_P(
plugin3.IsLightPlugin());
}
TEST_P(
PluginTest,
TEST_P(PluginTest,
isMediumPluginShouldBeTrueForAMediumFlaggedPluginForStarfield) {
if (GetParam() != GameType::starfield) {
auto bytes = ReadFile(dataPath / blankEsm);
@@ -348,14 +348,13 @@ TEST_P(
const auto pluginName =
GetParam() == GameType::starfield ? blankMediumEsm : blankEsm;
Plugin plugin(game_.GetType(), game_.GetCache(), game_.DataPath() / pluginName,
true);
Plugin plugin(
game_.GetType(), game_.GetCache(), game_.DataPath() / pluginName, true);
EXPECT_EQ(GetParam() == GameType::starfield, plugin.IsMediumPlugin());
}
TEST_P(PluginTest,
isUpdatePluginShouldOnlyBeTrueForAStarfieldUpdatePlugin) {
TEST_P(PluginTest, isUpdatePluginShouldOnlyBeTrueForAStarfieldUpdatePlugin) {
auto bytes = ReadFile(dataPath / blankMasterDependentEsp);
bytes[9] = 0x2;
WriteFile(dataPath / blankMasterDependentEsp, bytes);
@@ -385,11 +384,16 @@ TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) {
}
TEST_P(PluginTest, loadingAPluginThatDoesNotExistShouldThrow) {
EXPECT_THROW(Plugin(game_.GetType(),
game_.GetCache(),
game_.DataPath() / "Blank\\.esp",
true),
FileAccessError);
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());
}
}
TEST_P(
@@ -545,8 +549,7 @@ TEST_P(
PluginTest,
isValidAsMediumPluginShouldReturnTrueOnlyForAStarfieldPluginWithNewFormIdsBetween0And0xFFFFInclusive) {
bool valid =
Plugin(
game_.GetType(), game_.GetCache(), dataPath / blankEsm, true)
Plugin(game_.GetType(), game_.GetCache(), dataPath / blankEsm, true)
.IsValidAsMediumPlugin();
if (GetParam() == GameType::starfield) {
EXPECT_TRUE(valid);
@@ -558,12 +561,11 @@ TEST_P(
TEST_P(
PluginTest,
IsValidAsUpdatePluginShouldOnlyReturnTrueForAStarfieldPluginWithNoNewRecords) {
const auto sourcePluginName = GetParam() == GameType::starfield
? blankFullEsm
: blankEsp;
const auto sourcePluginName =
GetParam() == GameType::starfield ? blankFullEsm : blankEsp;
const auto updatePluginName = GetParam() == GameType::starfield
? blankMasterDependentEsp
: blankDifferentPluginDependentEsp;
? blankMasterDependentEsp
: blankDifferentPluginDependentEsp;
Plugin plugin1(game_.GetType(),
game_.GetCache(),
@@ -583,8 +585,7 @@ TEST_P(
}
EXPECT_FALSE(plugin1.IsValidAsUpdatePlugin());
EXPECT_EQ(GetParam() == GameType::starfield,
plugin2.IsValidAsUpdatePlugin());
EXPECT_EQ(GetParam() == GameType::starfield, plugin2.IsValidAsUpdatePlugin());
}
TEST_P(PluginTest,