mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
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:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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,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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user