Map C++ exception types

This commit is contained in:
Oliver Hamlet
2025-03-25 22:02:33 +00:00
parent d1757df904
commit 327d9935c9
13 changed files with 905 additions and 165 deletions
Generated
+3
View File
@@ -588,7 +588,10 @@ dependencies = [
"cxx",
"cxx-build",
"delegate",
"esplugin",
"libloadorder",
"libloot",
"loot-condition-interpreter",
"unicase",
]
+2 -2
View File
@@ -18,8 +18,8 @@ Currently complete:
- [x] Metadata-related functionality (excluding writing YAML)
- [x] Sorting functionality
- [x] Unit tests
- [ ] Integration tests
- [ ] C++ FFI
- [x] Integration tests
- [x] C++ FFI
- [ ] Python FFI
The complete bits should match libloot commit [55b341fc6cbdccee52e42923c13a91eddb5ca97d](https://github.com/loot/libloot/commit/55b341fc6cbdccee52e42923c13a91eddb5ca97d), which is libloot v0.25.3 plus a few changes prompted by this translation.
+2
View File
@@ -41,6 +41,7 @@ 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/error_categories.cpp"
"${CMAKE_SOURCE_DIR}/src/api/exception.cpp"
"${CMAKE_SOURCE_DIR}/src/api/metadata/conditional_metadata.cpp"
"${CMAKE_SOURCE_DIR}/src/api/metadata/file.cpp"
"${CMAKE_SOURCE_DIR}/src/api/metadata/filename.cpp"
@@ -89,6 +90,7 @@ set(LIBLOOT_INCLUDE_H_FILES
set(LIBLOOT_SRC_API_H_FILES
"${CMAKE_SOURCE_DIR}/src/api/convert.h"
"${CMAKE_SOURCE_DIR}/src/api/database.h"
"${CMAKE_SOURCE_DIR}/src/api/exception.h"
"${CMAKE_SOURCE_DIR}/src/api/game.h"
"${CMAKE_SOURCE_DIR}/src/api/plugin.h"
)
+4
View File
@@ -10,6 +10,10 @@ delegate = "0.13.2"
libloot = { path = ".." }
unicase = "2.8.1"
esplugin = "6.1.1"
libloadorder = { git = "https://github.com/Ortham/libloadorder.git", rev = "6245dbc6824c4751daef5ab01dfe7b9497f5446f" }
loot-condition-interpreter = { git = "https://github.com/loot/loot-condition-interpreter.git", rev = "f7d9947fa434037743ce81e2d409184ec0bfb693" }
[build-dependencies]
cxx-build = "1.0"
+10 -44
View File
@@ -1,49 +1,10 @@
# libloot-rs C++ wrapper
This is an **imcomplete** and **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface that's ABI-compatible with libloot v0.25.3.
## Current status
- [x] `EdgeType`
- [x] `GameType`
- [x] `LogLevel`
- [x] `MessageType`
- [ ] `ConditionSyntaxError`
- [ ] `CyclicInteractionError`
- [ ] `FileAccessError`
- [ ] `UndefinedGroupError`
- [ ] `std::system_error` with esplugin `std::error_category`
- [ ] `std::system_error` with libloadorder `std::error_category`
- [ ] `std::system_error` with loot-condition-interpreter `std::error_category`
- [x] `ConditionalMetadata`
- [x] `File`
- [x] `Filename`
- [x] `Group`
- [x] `Location`
- [x] `MessageContent`
- [x] `SelectMessageContent`
- [x] `Message`
- [x] `PluginCleaningData`
- [x] `PluginMetadata`
- [x] `Tag`
- [x] `SetLoggingCallback`
- [x] `IsCompatible`
- [x] `CreateGameHandle`
- [x] `DatabaseInterface`
- [x] `GameInterface`
- [x] `LIBLOOT_VERSION_MAJOR`
- [x] `LIBLOOT_VERSION_MINOR`
- [x] `LIBLOOT_VERSION_PATCH`
- [x] `GetLiblootVersion`
- [x] `GetLiblootRevision`
- [x] `PluginInterface`
- [x] `Vertex`
The error types are defined but currently unused, causing behavioural differences when errors occur - see the implementation notes below for details.
This is an **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface that's ABI-compatible with libloot v0.25.3.
## Building
The wrapper is currently built in two layers:
The wrapper has two layers:
- a static library built using Cargo, which provides a C++ interface
- a shared library built using CMake, which wraps that C++ interface to provide another that is ABI-compatible with C++ libloot.
@@ -62,8 +23,6 @@ This also builds a copy of the public API tests from C++ libloot v0.25.3, which
ctest --test-dir build --output-on-failure -V
```
Some of the tests currently fail: they are all expecting the exception classes that aren't yet used.
To package the build:
```
@@ -74,9 +33,16 @@ This repository (and so the created package) doesn't currently include any of li
## Usage notes
For the first layer of the wrapper, built using Cargo:
- `LIBLOOT_VERSION_MAJOR`, `LIBLOOT_VERSION_MINOR` and `LIBLOOT_VERSION_PATCH` are exposed as `extern "C"` `static unsigned int` globals.
- CXX doesn't provide integration between Rust's `Option<_>` and C++'s `std::optional<T>`, so sentinel values are used to communicate the absence of a value in the wrapper's API:
- For `Option<&str>`, `None` is represented using an empty string, which is how it's already done for many metadata object fields in the C++ implementation of libloot.
- For `Option<f32>`, `None` is represented using `NaN`, since the C++ implementation of libloot's public API already says that `NaN` values get converted to `None`.
- Plugin CRCs (which are `Option<u32>` in Rust) are represented as `int64_t`, with `-1` representing `None`, since all `uint32_t` values are possibly valid CRCs.
- All Rust errors are converted to `::rust::Error` exceptions that have a `what()` string that is the concatenation of the error's display string and all its recursive source error display strings.
- All Rust errors are converted to `::rust::Error` exceptions that have a `what()` string that is the concatenation of the error's display string and all its recursive source error display strings. For some errors the `what()` string also includes some data that is parsed by the wrapper's second layer to differentiate certain error types.
For the ABI-compatible second layer of the wrapper, built using CMake:
- Some exceptions have changed type: they all still derive from `std::exception`, but for example some `std::logic_error` and `std::invalid_argument` exceptions have become `std::runtime_error` and `FileAccessError` exceptions, some `YAML::RepresentationException` exceptions have become `FileAccessError` exceptions, etc.
- Exception messages are generally not expected to be the same between the two implementations.
+72 -40
View File
@@ -1,5 +1,7 @@
#include "api/convert.h"
#include "api/exception.h"
namespace loot {
// To public types
/////////////////////
@@ -118,11 +120,15 @@ std::optional<loot::EdgeType> convert(uint8_t edgeType) {
}
loot::Vertex convert(const loot::rust::Vertex& vertex) {
const auto outEdgeType = convert(vertex.out_edge_type());
if (outEdgeType.has_value()) {
return loot::Vertex(std::string(vertex.name()), outEdgeType.value());
} else {
return loot::Vertex(std::string(vertex.name()));
try {
const auto outEdgeType = convert(vertex.out_edge_type());
if (outEdgeType.has_value()) {
return loot::Vertex(std::string(vertex.name()), outEdgeType.value());
} else {
return loot::Vertex(std::string(vertex.name()));
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
@@ -140,8 +146,14 @@ loot::Vertex convert(const loot::rust::Vertex& vertex) {
::rust::Box<loot::rust::File> convert(const loot::File& file) {
auto output = loot::rust::new_file(std::string(file.GetName()));
output->set_display_name(file.GetDisplayName());
output->set_detail(
::rust::Slice(convert<loot::rust::MessageContent>(file.GetDetail())));
try {
output->set_detail(
::rust::Slice(convert<loot::rust::MessageContent>(file.GetDetail())));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
output->set_condition(file.GetCondition());
return output;
@@ -169,29 +181,44 @@ loot::rust::MessageType convert(loot::MessageType messageType) {
}
::rust::Box<loot::rust::Message> convert(const loot::Message& message) {
auto output = loot::rust::multilingual_message(
convert(message.GetType()),
::rust::Slice(convert<loot::rust::MessageContent>(message.GetContent())));
output->set_condition(message.GetCondition());
try {
auto output = loot::rust::multilingual_message(
convert(message.GetType()),
::rust::Slice(
convert<loot::rust::MessageContent>(message.GetContent())));
output->set_condition(message.GetCondition());
return output;
return output;
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
::rust::Box<loot::rust::Tag> convert(const loot::Tag& tag) {
const auto suggestion = tag.IsAddition() ? loot::rust::TagSuggestion::Addition
: loot::rust::TagSuggestion::Removal;
auto output = loot::rust::new_tag(tag.GetName(), suggestion);
output->set_condition(tag.GetCondition());
try {
const auto suggestion = tag.IsAddition()
? loot::rust::TagSuggestion::Addition
: loot::rust::TagSuggestion::Removal;
auto output = loot::rust::new_tag(tag.GetName(), suggestion);
output->set_condition(tag.GetCondition());
return output;
return output;
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
::rust::Box<loot::rust::PluginCleaningData> convert(
const loot::PluginCleaningData& data) {
auto output = loot::rust::new_plugin_cleaning_data(data.GetCRC(),
data.GetCleaningUtility());
output->set_detail(
::rust::Slice(convert<loot::rust::MessageContent>(data.GetDetail())));
try {
output->set_detail(
::rust::Slice(convert<loot::rust::MessageContent>(data.GetDetail())));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
output->set_itm_count(data.GetITMCount());
output->set_deleted_reference_count(data.GetDeletedReferenceCount());
output->set_deleted_navmesh_count(data.GetDeletedNavmeshCount());
@@ -208,29 +235,34 @@ loot::rust::MessageType convert(loot::MessageType messageType) {
::rust::Box<loot::rust::PluginMetadata> convert(
const loot::PluginMetadata& metadata) {
auto output = loot::rust::new_plugin_metadata(metadata.GetName());
try {
auto output = loot::rust::new_plugin_metadata(metadata.GetName());
if (metadata.GetGroup().has_value()) {
output->set_group(metadata.GetGroup().value());
if (metadata.GetGroup().has_value()) {
output->set_group(metadata.GetGroup().value());
}
output->set_load_after_files(
::rust::Slice(convert<loot::rust::File>(metadata.GetLoadAfterFiles())));
output->set_requirements(
::rust::Slice(convert<loot::rust::File>(metadata.GetRequirements())));
output->set_incompatibilities(::rust::Slice(
convert<loot::rust::File>(metadata.GetIncompatibilities())));
output->set_messages(
::rust::Slice(convert<loot::rust::Message>(metadata.GetMessages())));
output->set_tags(
::rust::Slice(convert<loot::rust::Tag>(metadata.GetTags())));
output->set_dirty_info(::rust::Slice(
convert<loot::rust::PluginCleaningData>(metadata.GetDirtyInfo())));
output->set_clean_info(::rust::Slice(
convert<loot::rust::PluginCleaningData>(metadata.GetCleanInfo())));
output->set_locations(
::rust::Slice(convert<loot::rust::Location>(metadata.GetLocations())));
return output;
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
output->set_load_after_files(
::rust::Slice(convert<loot::rust::File>(metadata.GetLoadAfterFiles())));
output->set_requirements(
::rust::Slice(convert<loot::rust::File>(metadata.GetRequirements())));
output->set_incompatibilities(::rust::Slice(
convert<loot::rust::File>(metadata.GetIncompatibilities())));
output->set_messages(
::rust::Slice(convert<loot::rust::Message>(metadata.GetMessages())));
output->set_tags(
::rust::Slice(convert<loot::rust::Tag>(metadata.GetTags())));
output->set_dirty_info(
::rust::Slice(convert<loot::rust::PluginCleaningData>(metadata.GetDirtyInfo())));
output->set_clean_info(
::rust::Slice(convert<loot::rust::PluginCleaningData>(metadata.GetCleanInfo())));
output->set_locations(::rust::Slice(convert<loot::rust::Location>(metadata.GetLocations())));
return output;
}
// Between containers
+90 -32
View File
@@ -2,6 +2,7 @@
#include "api/database.h"
#include "api/convert.h"
#include "api/exception.h"
namespace loot {
Database::Database(::rust::Box<loot::rust::Database>&& database) :
@@ -10,91 +11,148 @@ Database::Database(::rust::Box<loot::rust::Database>&& database) :
void Database::LoadLists(const std::filesystem::path& masterlistPath,
const std::filesystem::path& userlistPath,
const std::filesystem::path& masterlistPreludePath) {
if (!masterlistPath.empty()) {
if (!masterlistPreludePath.empty()) {
database_->load_masterlist_with_prelude(masterlistPath.u8string(),
masterlistPreludePath.u8string());
} else {
database_->load_masterlist(masterlistPath.u8string());
try {
if (!masterlistPath.empty()) {
if (!masterlistPreludePath.empty()) {
database_->load_masterlist_with_prelude(
masterlistPath.u8string(), masterlistPreludePath.u8string());
} else {
database_->load_masterlist(masterlistPath.u8string());
}
}
}
if (!userlistPath.empty()) {
database_->load_userlist(userlistPath.u8string());
if (!userlistPath.empty()) {
database_->load_userlist(userlistPath.u8string());
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::WriteUserMetadata(const std::filesystem::path& outputFile,
const bool overwrite) const {
database_->write_user_metadata(outputFile.u8string(), overwrite);
try {
database_->write_user_metadata(outputFile.u8string(), overwrite);
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<std::string> Database::GetKnownBashTags() const {
return convert<std::string>(database_->known_bash_tags());
try {
return convert<std::string>(database_->known_bash_tags());
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<Message> Database::GetGeneralMessages(
bool evaluateConditions) const {
return convert<Message>(database_->general_messages(evaluateConditions));
try {
return convert<Message>(database_->general_messages(evaluateConditions));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<Group> Database::GetGroups(bool includeUserMetadata) const {
return convert<Group>(database_->groups(includeUserMetadata));
try {
return convert<Group>(database_->groups(includeUserMetadata));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<Group> Database::GetUserGroups() const {
return convert<Group>(database_->user_groups());
try {
return convert<Group>(database_->user_groups());
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::SetUserGroups(const std::vector<Group>& groups) {
database_->set_user_groups(::rust::Slice(convert<loot::rust::Group>(groups)));
try {
database_->set_user_groups(
::rust::Slice(convert<loot::rust::Group>(groups)));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<Vertex> Database::GetGroupsPath(
const std::string& fromGroupName,
const std::string& toGroupName) const {
return convert<Vertex>(database_->groups_path(fromGroupName, toGroupName));
try {
return convert<Vertex>(database_->groups_path(fromGroupName, toGroupName));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::optional<PluginMetadata> Database::GetPluginMetadata(
const std::string& plugin,
bool includeUserMetadata,
bool evaluateConditions) const {
const auto metadata = database_->plugin_metadata(
plugin, includeUserMetadata, evaluateConditions);
if (metadata->is_some()) {
return convert(metadata->as_ref());
} else {
return std::nullopt;
try {
const auto metadata = database_->plugin_metadata(
plugin, includeUserMetadata, evaluateConditions);
if (metadata->is_some()) {
return convert(metadata->as_ref());
} else {
return std::nullopt;
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::optional<PluginMetadata> Database::GetPluginUserMetadata(
const std::string& plugin,
bool evaluateConditions) const {
const auto metadata =
database_->plugin_user_metadata(plugin, evaluateConditions);
if (metadata->is_some()) {
return convert(metadata->as_ref());
} else {
return std::nullopt;
try {
const auto metadata =
database_->plugin_user_metadata(plugin, evaluateConditions);
if (metadata->is_some()) {
return convert(metadata->as_ref());
} else {
return std::nullopt;
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::SetPluginUserMetadata(const PluginMetadata& pluginMetadata) {
database_->set_plugin_user_metadata(convert(pluginMetadata));
try {
database_->set_plugin_user_metadata(convert(pluginMetadata));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::DiscardPluginUserMetadata(const std::string& plugin) {
database_->discard_plugin_user_metadata(plugin);
try {
database_->discard_plugin_user_metadata(plugin);
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::DiscardAllUserMetadata() {
database_->discard_all_user_metadata();
try {
database_->discard_all_user_metadata();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Database::WriteMinimalList(const std::filesystem::path& outputFile,
const bool overwrite) const {
database_->write_minimal_list(outputFile.u8string(), overwrite);
try {
database_->write_minimal_list(outputFile.u8string(), overwrite);
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
}
+177
View File
@@ -0,0 +1,177 @@
#include "api/exception.h"
#include <charconv>
#include "loot/exception/condition_syntax_error.h"
#include "loot/exception/cyclic_interaction_error.h"
#include "loot/exception/error_categories.h"
#include "loot/exception/file_access_error.h"
#include "loot/exception/undefined_group_error.h"
#include "loot/vertex.h"
namespace {
using loot::EdgeType;
using loot::Vertex;
constexpr std::string_view CYCLIC_ERROR_PREFIX = "CyclicInteractionError: ";
constexpr std::string_view UNDEFINED_GROUP_ERROR_PREFIX =
"UndefinedGroupError: ";
constexpr std::string_view ESPLUGIN_ERROR_PREFIX = "EspluginError: ";
constexpr std::string_view LIBLOADORDER_ERROR_PREFIX = "LibloadorderError: ";
constexpr std::string_view LCI_ERROR_PREFIX = "LciError: ";
constexpr std::string_view FILE_ACCESS_ERROR_PREFIX = "FileAccessError: ";
constexpr std::string_view INVALID_ARGUMENT_PREFIX = "InvalidArgument: ";
bool startsWith(const std::string_view& str, const std::string_view& prefix) {
if (str.size() < prefix.size()) {
return false;
}
return str.substr(0, prefix.size()) == prefix;
}
std::string replace(const std::string_view& str,
const std::string_view& from,
const std::string_view& to) {
std::string out;
out.reserve(str.size());
size_t i = 0;
while (i < str.size()) {
if (i + from.size() <= str.size() && str.substr(i, from.size()) == from) {
out.append(to);
i += from.size();
} else {
out.push_back(str[i]);
i += 1;
}
}
return out;
}
EdgeType toEdgeType(const std::string_view& edgeTypeDisplay) {
if (edgeTypeDisplay == "Hardcoded") {
return EdgeType::hardcoded;
} else if (edgeTypeDisplay == "Master Flag") {
return EdgeType::masterFlag;
} else if (edgeTypeDisplay == "Master") {
return EdgeType::master;
} else if (edgeTypeDisplay == "Masterlist Requirement") {
return EdgeType::masterlistRequirement;
} else if (edgeTypeDisplay == "User Requirement") {
return EdgeType::userRequirement;
} else if (edgeTypeDisplay == "Masterlist Load After") {
return EdgeType::masterlistLoadAfter;
} else if (edgeTypeDisplay == "User Load After") {
return EdgeType::userLoadAfter;
} else if (edgeTypeDisplay == "Masterlist Group") {
return EdgeType::masterlistGroup;
} else if (edgeTypeDisplay == "User Group") {
return EdgeType::userGroup;
} else if (edgeTypeDisplay == "Record Overlap") {
return EdgeType::recordOverlap;
} else if (edgeTypeDisplay == "Asset Overlap") {
return EdgeType::assetOverlap;
} else if (edgeTypeDisplay == "Tie Break") {
return EdgeType::tieBreak;
} else if (edgeTypeDisplay == "Blueprint Master") {
return EdgeType::blueprintMaster;
} else {
std::string what("Unrecognised edge type: ");
what += edgeTypeDisplay;
throw std::logic_error(what);
}
}
std::vector<Vertex> parseCyclicError(const std::string_view& what) {
const auto suffix = what.substr(0, CYCLIC_ERROR_PREFIX.size());
std::vector<Vertex> vertices;
size_t pos = 0;
while (pos < suffix.size()) {
const auto sepPos = suffix.find("--", pos);
const auto escapedName = suffix.substr(pos, sepPos);
const auto name = replace(replace(escapedName, "\\-", "-"), "\\\\", "\\");
if (sepPos != std::string::npos) {
const auto secondSepPos = suffix.find("--", sepPos + 2);
const auto escapedEdgeName =
suffix.substr(sepPos + 2, secondSepPos - (sepPos + 2));
vertices.push_back(Vertex(name, toEdgeType(escapedEdgeName)));
pos = secondSepPos + 2;
} else {
vertices.push_back(Vertex(name));
pos = suffix.size();
}
}
return vertices;
}
std::string getErrorSuffix(const std::string_view& what) {
const auto sepPos = what.find(": ");
return std::string(what.substr(sepPos + 2));
}
std::pair<int, std::string> parseSystemError(
const 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 {
std::exception_ptr mapError(const ::rust::Error& error) {
if (startsWith(error.what(), CYCLIC_ERROR_PREFIX)) {
return std::make_exception_ptr(
CyclicInteractionError(parseCyclicError(error.what())));
} 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(0, ESPLUGIN_ERROR_PREFIX.size()));
return std::make_exception_ptr(
std::system_error(code, esplugin_category(), details));
} else if (startsWith(error.what(), LIBLOADORDER_ERROR_PREFIX)) {
const auto [code, details] =
parseSystemError(std::string_view(error.what())
.substr(0, LIBLOADORDER_ERROR_PREFIX.size()));
return std::make_exception_ptr(
std::system_error(code, libloadorder_category(), details));
} else if (startsWith(error.what(), LCI_ERROR_PREFIX)) {
const auto [code, details] = parseSystemError(
std::string_view(error.what()).substr(0, LCI_ERROR_PREFIX.size()));
return std::make_exception_ptr(ConditionSyntaxError(
code, loot_condition_interpreter_category(), details));
} else if (startsWith(error.what(), FILE_ACCESS_ERROR_PREFIX)) {
return std::make_exception_ptr(
FileAccessError(getErrorSuffix(error.what())));
} else if (startsWith(error.what(), FILE_ACCESS_ERROR_PREFIX)) {
return std::make_exception_ptr(
FileAccessError(getErrorSuffix(error.what())));
} else if (startsWith(error.what(), INVALID_ARGUMENT_PREFIX)) {
return std::make_exception_ptr(
std::invalid_argument(getErrorSuffix(error.what())));
} else {
return std::make_exception_ptr(std::runtime_error(error.what()));
}
}
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef LOOT_API_EXCEPTION
#define LOOT_API_EXCEPTION
#include "rust/cxx.h"
namespace loot {
std::exception_ptr mapError(const ::rust::Error& error);
}
#endif
+76 -29
View File
@@ -2,6 +2,7 @@
#include "api/game.h"
#include "api/convert.h"
#include "api/exception.h"
namespace {
loot::GameType convert(loot::rust::GameType gameType) {
@@ -66,12 +67,16 @@ rust::Box<loot::rust::Game> constructGame(
const loot::GameType gameType,
const std::filesystem::path& gamePath,
const std::filesystem::path& localDataPath) {
if (localDataPath.empty()) {
return std::move(
loot::rust::new_game(convert(gameType), gamePath.u8string()));
} else {
return std::move(loot::rust::new_game_with_local_path(
convert(gameType), gamePath.u8string(), localDataPath.u8string()));
try {
if (localDataPath.empty()) {
return std::move(
loot::rust::new_game(convert(gameType), gamePath.u8string()));
} else {
return std::move(loot::rust::new_game_with_local_path(
convert(gameType), gamePath.u8string(), localDataPath.u8string()));
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(loot::mapError(e));
}
}
@@ -92,19 +97,30 @@ Game::Game(const GameType gameType,
game_(constructGame(gameType, gamePath, localDataPath)),
database_(game_->database()) {}
GameType Game::GetType() const { return ::convert(game_->game_type()); }
GameType Game::GetType() const {
try {
return ::convert(game_->game_type());
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
const DatabaseInterface& Game::GetDatabase() const { return database_; }
DatabaseInterface& Game::GetDatabase() { return database_; }
std::vector<std::filesystem::path> Game::GetAdditionalDataPaths() const {
std::vector<std::filesystem::path> paths;
for (const auto& path_str : game_->additional_data_paths()) {
paths.push_back(std::filesystem::u8path(path_str.begin(), path_str.end()));
}
try {
std::vector<std::filesystem::path> paths;
for (const auto& path_str : game_->additional_data_paths()) {
paths.push_back(
std::filesystem::u8path(path_str.begin(), path_str.end()));
}
return paths;
return paths;
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Game::SetAdditionalDataPaths(
@@ -115,8 +131,12 @@ void Game::SetAdditionalDataPaths(
path_strings.push_back(path.u8string());
path_strs.push_back(path_strings.back());
}
game_->set_additional_data_paths(::rust::Slice<const ::rust::Str>(path_strs));
try {
game_->set_additional_data_paths(
::rust::Slice<const ::rust::Str>(path_strs));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Game::IsValidPlugin(const std::filesystem::path& pluginPath) const {
@@ -132,10 +152,14 @@ void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
path_strs.push_back(path_strings.back());
}
if (loadHeadersOnly) {
game_->load_plugin_headers(::rust::Slice<const ::rust::Str>(path_strs));
} else {
game_->load_plugins(::rust::Slice<const ::rust::Str>(path_strs));
try {
if (loadHeadersOnly) {
game_->load_plugin_headers(::rust::Slice<const ::rust::Str>(path_strs));
} else {
game_->load_plugins(::rust::Slice<const ::rust::Str>(path_strs));
}
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
for (const auto& path : pluginPaths) {
@@ -164,10 +188,14 @@ const PluginInterface* Game::GetPlugin(const std::string& pluginName) const {
return it->second.get();
}
auto plugin = std::make_shared<Plugin>(std::move(pluginOpt->as_ref()));
const auto result = plugins_.emplace(key, plugin);
try {
auto plugin = std::make_shared<Plugin>(std::move(pluginOpt->as_ref()));
const auto result = plugins_.emplace(key, plugin);
return result.first->second.get();
return result.first->second.get();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<const PluginInterface*> Game::GetLoadedPlugins() const {
@@ -188,23 +216,38 @@ std::vector<std::string> Game::SortPlugins(
const std::vector<std::string>& pluginFilenames) {
const auto strs = as_str_refs(pluginFilenames);
const auto results =
game_->sort_plugins(::rust::Slice(strs));
try {
const auto results = game_->sort_plugins(::rust::Slice(strs));
return convert<std::string>(results);
return convert<std::string>(results);
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
void Game::LoadCurrentLoadOrderState() {
game_->load_current_load_order_state();
try {
game_->load_current_load_order_state();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Game::IsLoadOrderAmbiguous() const {
return game_->is_load_order_ambiguous();
try {
return game_->is_load_order_ambiguous();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::filesystem::path Game::GetActivePluginsFilePath() const {
const auto path_string = game_->active_plugins_file_path();
return std::filesystem::u8path(path_string.begin(), path_string.end());
try {
const auto path_string = game_->active_plugins_file_path();
return std::filesystem::u8path(path_string.begin(), path_string.end());
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Game::IsPluginActive(const std::string& pluginName) const {
@@ -218,6 +261,10 @@ std::vector<std::string> Game::GetLoadOrder() const {
void Game::SetLoadOrder(const std::vector<std::string>& loadOrder) {
const auto strs = as_str_refs(loadOrder);
game_->set_load_order(::rust::Slice(strs));
try {
game_->set_load_order(::rust::Slice(strs));
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
}
+23 -4
View File
@@ -4,6 +4,7 @@
#include <typeinfo>
#include "api/convert.h"
#include "api/exception.h"
namespace loot {
Plugin::Plugin(::rust::Box<loot::rust::PluginRef> plugin) :
@@ -30,7 +31,11 @@ std::optional<std::string> Plugin::GetVersion() const {
}
std::vector<std::string> Plugin::GetMasters() const {
return convert<std::string>(plugin_->masters());
try {
return convert<std::string>(plugin_->masters());
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<Tag> Plugin::GetBashTags() const {
@@ -64,15 +69,27 @@ bool Plugin::IsBlueprintPlugin() const {
}
bool Plugin::IsValidAsLightPlugin() const {
return plugin_->is_valid_as_light_plugin();
try {
return plugin_->is_valid_as_light_plugin();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Plugin::IsValidAsMediumPlugin() const {
return plugin_->is_valid_as_medium_plugin();
try {
return plugin_->is_valid_as_medium_plugin();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Plugin::IsValidAsUpdatePlugin() const {
return plugin_->is_valid_as_update_plugin();
try {
return plugin_->is_valid_as_update_plugin();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
bool Plugin::IsEmpty() const { return plugin_->is_empty(); }
@@ -86,6 +103,8 @@ bool Plugin::DoRecordsOverlap(const PluginInterface& plugin) const {
return plugin_->do_records_overlap(*otherPlugin.plugin_);
} catch (std::bad_cast&) {
throw std::invalid_argument("Tried to check if records overlapped with a different concrete type implementing PluginInterface");
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
}
+236
View File
@@ -0,0 +1,236 @@
pub(crate) mod lci {
use loot_condition_interpreter::Error;
use std::ffi::c_int;
/// Invalid arguments were given for the function.
#[unsafe(no_mangle)]
pub static LCI_ERROR_INVALID_ARGS: c_int = -1;
/// Something went wrong while parsing the condition expression.
#[unsafe(no_mangle)]
pub static LCI_ERROR_PARSING_ERROR: c_int = -2;
/// Something went wrong while getting the version of an executable.
#[unsafe(no_mangle)]
pub static LCI_ERROR_PE_PARSING_ERROR: c_int = -3;
/// Some sort of I/O error occurred.
#[unsafe(no_mangle)]
pub static LCI_ERROR_IO_ERROR: c_int = -4;
/// Something panicked.
#[unsafe(no_mangle)]
pub static LCI_ERROR_PANICKED: c_int = -5;
/// A thread lock was poisoned.
#[unsafe(no_mangle)]
pub static LCI_ERROR_POISONED_THREAD_LOCK: c_int = -6;
/// Failed to encode string as a C string, e.g. because there was a nul present.
#[unsafe(no_mangle)]
pub static LCI_ERROR_TEXT_ENCODE_FAIL: c_int = -7;
/// The library encountered an error that should not have been possible to encounter.
#[unsafe(no_mangle)]
pub static LCI_ERROR_INTERNAL_LOGIC_ERROR: c_int = -8;
pub(crate) fn map_error(err: &Error) -> c_int {
match err {
Error::ParsingIncomplete(_) => LCI_ERROR_PARSING_ERROR,
Error::UnconsumedInput(_) => LCI_ERROR_PARSING_ERROR,
Error::ParsingError(_, _) => LCI_ERROR_PARSING_ERROR,
Error::PeParsingError(_, _) => LCI_ERROR_PE_PARSING_ERROR,
Error::IoError(_, _) => LCI_ERROR_IO_ERROR,
_ => LCI_ERROR_INTERNAL_LOGIC_ERROR,
}
}
}
pub(crate) mod libloadorder {
use loadorder::Error;
use std::ffi::c_uint;
/// There is a mismatch between the files used to keep track of load order.
///
/// This warning can only occur when using libloadorder with a game that uses the textfile-based
/// load order system. The load order in the active plugins list file (`plugins.txt`) does not
/// match the load order in the full load order file (`loadorder.txt`). Synchronisation between
/// the two is automatic when load order is managed through libloadorder. It is left to the client
/// to decide how best to restore synchronisation.
#[unsafe(no_mangle)]
pub static LIBLO_WARN_LO_MISMATCH: c_uint = 2;
/// The specified file could not be found.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_NOT_FOUND: c_uint = 6;
/// A file could not be renamed.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_RENAME_FAIL: c_uint = 7;
/// There was an error parsing a plugin file.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_PARSE_FAIL: c_uint = 10;
/// Invalid arguments were given for the function.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_INVALID_ARGS: c_uint = 12;
/// A thread lock was poisoned.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_POISONED_THREAD_LOCK: c_uint = 14;
/// An unknown I/O error occurred. This is used when the I/O error kind doesn't fit another error
/// code.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_IO_ERROR: c_uint = 15;
/// Permission denied while trying to access a filesystem path.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_IO_PERMISSION_DENIED: c_uint = 16;
/// A plugin filename contains characters that do not have Windows-1252 code points, or a character
/// string contains a null character.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_TEXT_ENCODE_FAIL: c_uint = 17;
/// Text expected to be encoded in Windows-1252 could not be decoded to UTF-8.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_TEXT_DECODE_FAIL: c_uint = 18;
/// The library encountered an error that should not have been possible to encounter.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_INTERNAL_LOGIC_ERROR: c_uint = 19;
/// Something panicked.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_PANICKED: c_uint = 20;
/// A path cannot be encoded in UTF-8.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_PATH_ENCODE_FAIL: c_uint = 21;
/// An unknown operating system error occurred.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_SYSTEM_ERROR: c_uint = 22;
/// A system path definition (e.g. for local app data on Windows, or $HOME on Linux) could not be
/// found.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_NO_PATH: c_uint = 23;
/// Matches the value of the highest-numbered return code.
///
/// Provided in case clients wish to incorporate additional return codes in their implementation
/// and desire some method of avoiding value conflicts.
#[unsafe(no_mangle)]
pub static LIBLO_RETURN_MAX: c_uint = 23;
fn map_io_error(err: &std::io::Error) -> c_uint {
use std::io::ErrorKind::*;
match err.kind() {
NotFound => LIBLO_ERROR_FILE_NOT_FOUND,
AlreadyExists => LIBLO_ERROR_FILE_RENAME_FAIL,
PermissionDenied => LIBLO_ERROR_IO_PERMISSION_DENIED,
_ => LIBLO_ERROR_IO_ERROR,
}
}
pub(crate) fn map_error(err: &Error) -> c_uint {
use Error::*;
match *err {
InvalidPath(_) => LIBLO_ERROR_FILE_NOT_FOUND,
IoError(_, ref x) => map_io_error(x),
NoFilename(_) => LIBLO_ERROR_FILE_PARSE_FAIL,
DecodeError(_) => LIBLO_ERROR_TEXT_DECODE_FAIL,
EncodeError(_) => LIBLO_ERROR_TEXT_ENCODE_FAIL,
PluginParsingError(_, _) => LIBLO_ERROR_FILE_PARSE_FAIL,
PluginNotFound(_) => LIBLO_ERROR_INVALID_ARGS,
TooManyActivePlugins { .. } => LIBLO_ERROR_INVALID_ARGS,
DuplicatePlugin(_) => LIBLO_ERROR_INVALID_ARGS,
NonMasterBeforeMaster { .. } => LIBLO_ERROR_INVALID_ARGS,
InvalidEarlyLoadingPluginPosition { .. } => LIBLO_ERROR_INVALID_ARGS,
ImplicitlyActivePlugin(_) => LIBLO_ERROR_INVALID_ARGS,
NoLocalAppData => LIBLO_ERROR_INVALID_ARGS,
NoDocumentsPath => LIBLO_ERROR_INVALID_ARGS,
NoUserConfigPath => LIBLO_ERROR_NO_PATH,
NoUserDataPath => LIBLO_ERROR_NO_PATH,
NoProgramFilesPath => LIBLO_ERROR_NO_PATH,
UnrepresentedHoist { .. } => LIBLO_ERROR_INVALID_ARGS,
InstalledPlugin(_) => LIBLO_ERROR_INVALID_ARGS,
IniParsingError { .. } => LIBLO_ERROR_FILE_PARSE_FAIL,
VdfParsingError(_, _) => LIBLO_ERROR_FILE_PARSE_FAIL,
SystemError(_, _) => LIBLO_ERROR_SYSTEM_ERROR,
InvalidBlueprintPluginPosition { .. } => LIBLO_ERROR_INVALID_ARGS,
_ => LIBLO_ERROR_INTERNAL_LOGIC_ERROR,
}
}
}
pub(crate) mod esplugin {
use std::ffi::c_uint;
use esplugin::Error;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NULL_POINTER: u32 = 1;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NOT_UTF8: u32 = 2;
#[unsafe(no_mangle)]
pub static ESP_ERROR_STRING_CONTAINS_NUL: u32 = 3;
#[unsafe(no_mangle)]
pub static ESP_ERROR_INVALID_GAME_ID: u32 = 4;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PARSE_ERROR: u32 = 5;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PANICKED: u32 = 6;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NO_FILENAME: u32 = 7;
#[unsafe(no_mangle)]
pub static ESP_ERROR_TEXT_DECODE_ERROR: u32 = 8;
#[unsafe(no_mangle)]
pub static ESP_ERROR_TEXT_ENCODE_ERROR: u32 = 9;
#[unsafe(no_mangle)]
pub static ESP_ERROR_IO_ERROR: u32 = 10;
#[unsafe(no_mangle)]
pub static ESP_ERROR_FILE_NOT_FOUND: u32 = 11;
#[unsafe(no_mangle)]
pub static ESP_ERROR_IO_PERMISSION_DENIED: u32 = 12;
#[unsafe(no_mangle)]
pub static ESP_ERROR_UNRESOLVED_RECORD_IDS: u32 = 13;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PLUGIN_METADATA_NOT_FOUND: u32 = 14;
fn map_io_error(err: &std::io::Error) -> c_uint {
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,
}
}
pub(crate) fn map_error(err: &Error) -> c_uint {
match *err {
Error::IoError(ref x) => map_io_error(x),
Error::NoFilename(_) => ESP_ERROR_NO_FILENAME,
Error::ParsingIncomplete(_) => ESP_ERROR_PARSE_ERROR,
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,
}
}
}
+200 -14
View File
@@ -1,10 +1,11 @@
mod database;
mod error_codes;
mod game;
mod metadata;
mod plugin;
use database::{Database, Vertex, new_vertex};
use game::{Game, new_game, new_game_with_local_path};
use game::{Game, NotValidUtf8, new_game, new_game_with_local_path};
use metadata::{
File, Filename, Group, Location, Message, MessageContent, OptionalMessageContentRef,
OptionalPluginMetadata, PluginCleaningData, PluginMetadata, Tag, group_default_name,
@@ -14,32 +15,217 @@ use metadata::{
};
use plugin::{OptionalPluginRef, PluginRef};
use std::{
ffi::{c_char, c_uchar, c_uint, c_void, CString},
sync::{atomic::AtomicPtr, Mutex},
error::Error,
ffi::{CString, c_char, c_uchar, c_uint, c_void},
sync::{Mutex, atomic::AtomicPtr},
};
use unicase::UniCase;
use libloot::set_logging_callback;
use libloot::{
error::{
ConditionEvaluationError, DatabaseLockPoisonError, GameHandleCreationError,
GroupsPathError, LoadOrderError, LoadOrderStateError, LoadPluginsError,
MetadataRetrievalError, PluginDataError, SortPluginsError,
},
metadata::error::{
LoadMetadataError, MultilingualMessageContentsError, RegexError, WriteMetadataError,
},
set_logging_callback,
};
pub use libloot::{is_compatible, libloot_revision, libloot_version};
#[derive(Debug)]
pub struct VerboseError(Box<dyn std::error::Error>);
pub enum VerboseError {
CyclicInteractionError(Vec<libloot::Vertex>),
UndefinedGroupError(String),
EspluginError(u32, String),
LibloadorderError(u32, String),
LciError(i32, String),
FileAccessError(String),
InvalidArgument(String),
Other(Box<dyn std::error::Error>),
}
impl std::fmt::Display for VerboseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)?;
let mut error = self.0.as_ref();
while let Some(source) = error.source() {
write!(f, ": {}", source)?;
error = source;
match self {
Self::CyclicInteractionError(cycle) => {
write!(f, "CyclicInteractionError: ")?;
for vertex in cycle {
let name = vertex.name().replace("\\", "\\\\").replace("-", "\\-");
match vertex.out_edge_type() {
Some(e) => write!(f, "{}--{}--", name, e)?,
None => write!(f, "{}", name)?,
}
}
Ok(())
}
Self::UndefinedGroupError(group) => {
write!(f, "UndefinedGroupError: {}", group)
}
Self::EspluginError(c, s) => {
write!(f, "EspluginError: {}: {}", c, s)
}
Self::LibloadorderError(c, s) => {
write!(f, "LibloadorderError: {}: {}", c, s)
}
Self::LciError(c, s) => {
write!(f, "LciError: {}: {}", c, s)
}
Self::FileAccessError(s) => write!(f, "FileAccessError: {}", s),
Self::InvalidArgument(s) => write!(f, "InvalidArgument: {}", s),
Self::Other(e) => {
write!(f, "{}", e)?;
let mut error = e.as_ref();
while let Some(source) = error.source() {
write!(f, ": {}", source)?;
error = source;
}
Ok(())
}
}
Ok(())
}
}
impl<E: std::error::Error + 'static> From<E> for VerboseError {
fn from(value: E) -> Self {
VerboseError(Box::new(value))
impl From<GameHandleCreationError> for VerboseError {
fn from(value: GameHandleCreationError) -> Self {
match value {
GameHandleCreationError::LoadOrderError(e) => e.into(),
GameHandleCreationError::NotADirectory(_) => {
Self::InvalidArgument(value.to_string())
}
_ => Self::Other(Box::new(value)),
}
}
}
impl From<UnsupportedEnumValueError> for VerboseError {
fn from(value: UnsupportedEnumValueError) -> Self {
Self::Other(Box::new(value))
}
}
impl From<NotValidUtf8> for VerboseError {
fn from(value: NotValidUtf8) -> Self {
Self::Other(Box::new(value))
}
}
impl From<DatabaseLockPoisonError> for VerboseError {
fn from(value: DatabaseLockPoisonError) -> Self {
Self::Other(Box::new(value))
}
}
impl From<LoadPluginsError> for VerboseError {
fn from(value: LoadPluginsError) -> Self {
match value {
LoadPluginsError::PluginDataError(e) => e.into(),
LoadPluginsError::PluginValidationError(_) => {
Self::InvalidArgument(value.to_string())
}
_ => Self::Other(Box::new(value)),
}
}
}
impl From<SortPluginsError> for VerboseError {
fn from(value: SortPluginsError) -> Self {
match value {
SortPluginsError::MetadataRetrievalError(e) => e.into(),
SortPluginsError::UndefinedGroup(g) => Self::UndefinedGroupError(g),
SortPluginsError::CycleFound(cycle) => Self::CyclicInteractionError(cycle),
SortPluginsError::PluginDataError(e) => e.into(),
_ => Self::Other(Box::new(value)),
}
}
}
impl From<LoadOrderStateError> for VerboseError {
fn from(value: LoadOrderStateError) -> Self {
match value {
LoadOrderStateError::LoadOrderError(e) => e.into(),
_ => Self::Other(Box::new(value)),
}
}
}
impl From<LoadOrderError> for VerboseError {
fn from(value: LoadOrderError) -> Self {
let error = value
.source()
.expect("LoadOrderError has source")
.downcast_ref::<loadorder::Error>()
.expect("LoadOrderError source is a loadorder::Error");
let error_code = error_codes::libloadorder::map_error(error);
Self::LibloadorderError(error_code, error.to_string())
}
}
impl From<LoadMetadataError> for VerboseError {
fn from(value: LoadMetadataError) -> Self {
Self::FileAccessError(value.to_string())
}
}
impl From<WriteMetadataError> for VerboseError {
fn from(value: WriteMetadataError) -> Self {
Self::FileAccessError(value.to_string())
}
}
impl From<ConditionEvaluationError> for VerboseError {
fn from(value: ConditionEvaluationError) -> Self {
let error = value
.source()
.expect("LoadOrderError has source")
.downcast_ref::<loot_condition_interpreter::Error>()
.expect("LoadOrderError source is a loot_condition_interpreter::Error");
let error_code = error_codes::lci::map_error(error);
Self::LciError(error_code, error.to_string())
}
}
impl From<GroupsPathError> for VerboseError {
fn from(value: GroupsPathError) -> Self {
match value {
GroupsPathError::UndefinedGroup(g) => Self::UndefinedGroupError(g),
GroupsPathError::CycleFound(cycle) => Self::CyclicInteractionError(cycle),
_ => Self::Other(Box::new(value)),
}
}
}
impl From<MetadataRetrievalError> for VerboseError {
fn from(value: MetadataRetrievalError) -> Self {
match value {
MetadataRetrievalError::ConditionEvaluationError(e) => e.into(),
_ => Self::Other(Box::new(value)),
}
}
}
impl From<PluginDataError> for VerboseError {
fn from(value: PluginDataError) -> Self {
let error = value
.source()
.expect("LoadOrderError has source")
.downcast_ref::<esplugin::Error>()
.expect("LoadOrderError source is an esplugin::Error");
let error_code = error_codes::esplugin::map_error(error);
Self::EspluginError(error_code, error.to_string())
}
}
impl From<MultilingualMessageContentsError> for VerboseError {
fn from(value: MultilingualMessageContentsError) -> Self {
Self::Other(Box::new(value))
}
}
impl From<RegexError> for VerboseError {
fn from(value: RegexError) -> Self {
Self::Other(Box::new(value))
}
}