Improve Python wrapper error handling

The new libloot-ffi-errors crate is for sharing code between the CXX and PyO3 wrappers.
This commit is contained in:
Oliver Hamlet
2025-03-25 22:02:33 +00:00
parent 3c02bfd1da
commit de97b90618
18 changed files with 605 additions and 324 deletions
+65
View File
@@ -0,0 +1,65 @@
use std::ffi::c_int;
use esplugin::Error;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NULL_POINTER: c_int = 1;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NOT_UTF8: c_int = 2;
#[unsafe(no_mangle)]
pub static ESP_ERROR_STRING_CONTAINS_NUL: c_int = 3;
#[unsafe(no_mangle)]
pub static ESP_ERROR_INVALID_GAME_ID: c_int = 4;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PARSE_ERROR: c_int = 5;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PANICKED: c_int = 6;
#[unsafe(no_mangle)]
pub static ESP_ERROR_NO_FILENAME: c_int = 7;
#[unsafe(no_mangle)]
pub static ESP_ERROR_TEXT_DECODE_ERROR: c_int = 8;
#[unsafe(no_mangle)]
pub static ESP_ERROR_TEXT_ENCODE_ERROR: c_int = 9;
#[unsafe(no_mangle)]
pub static ESP_ERROR_IO_ERROR: c_int = 10;
#[unsafe(no_mangle)]
pub static ESP_ERROR_FILE_NOT_FOUND: c_int = 11;
#[unsafe(no_mangle)]
pub static ESP_ERROR_IO_PERMISSION_DENIED: c_int = 12;
#[unsafe(no_mangle)]
pub static ESP_ERROR_UNRESOLVED_RECORD_IDS: c_int = 13;
#[unsafe(no_mangle)]
pub static ESP_ERROR_PLUGIN_METADATA_NOT_FOUND: c_int = 14;
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,
}
}
pub fn map_error(err: &Error) -> c_int {
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,
}
}
+45
View File
@@ -0,0 +1,45 @@
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 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,
}
}
+99
View File
@@ -0,0 +1,99 @@
use std::{
error::Error,
ffi::{c_int, c_uchar},
};
use libloot::error::{ConditionEvaluationError, LoadOrderError, PluginDataError};
pub mod esplugin;
pub mod lci;
pub mod libloadorder;
/// An error from esplugin.
#[unsafe(no_mangle)]
pub static LIBLOOT_SYSTEM_ERROR_CATEGORY_ESPLUGIN: c_uchar = 1;
/// An error from libloadorder.
#[unsafe(no_mangle)]
pub static LIBLOOT_SYSTEM_ERROR_CATEGORY_LIBLOADORDER: c_uchar = 2;
/// An error from loot-condition-interpreter.
#[unsafe(no_mangle)]
pub static LIBLOOT_SYSTEM_ERROR_CATEGORY_LCI: c_uchar = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum SystemErrorCategory {
Esplugin = LIBLOOT_SYSTEM_ERROR_CATEGORY_ESPLUGIN,
Libloadorder = LIBLOOT_SYSTEM_ERROR_CATEGORY_LIBLOADORDER,
LootConditionInterpreter = LIBLOOT_SYSTEM_ERROR_CATEGORY_LCI,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SystemError {
code: c_int,
category: SystemErrorCategory,
message: String,
}
impl SystemError {
pub fn code(&self) -> c_int {
self.code
}
pub fn category(&self) -> SystemErrorCategory {
self.category
}
pub fn message(&self) -> &str {
&self.message
}
}
impl From<PluginDataError> for SystemError {
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 = crate::esplugin::map_error(error);
SystemError {
code: error_code,
category: SystemErrorCategory::Esplugin,
message: error.to_string(),
}
}
}
impl From<LoadOrderError> for SystemError {
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 = crate::libloadorder::map_error(error);
SystemError {
code: error_code,
category: SystemErrorCategory::Libloadorder,
message: error.to_string(),
}
}
}
impl From<ConditionEvaluationError> for SystemError {
fn from(value: ConditionEvaluationError) -> Self {
let error = value
.source()
.expect("ConditionEvaluationError has source")
.downcast_ref::<loot_condition_interpreter::Error>()
.expect("ConditionEvaluationError source is a loot_condition_interpreter::Error");
let error_code = crate::lci::map_error(error);
SystemError {
code: error_code,
category: SystemErrorCategory::LootConditionInterpreter,
message: error.to_string(),
}
}
}
+118
View File
@@ -0,0 +1,118 @@
use loadorder::Error;
use std::ffi::c_int;
/// 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_int = 2;
/// The specified file could not be found.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_NOT_FOUND: c_int = 6;
/// A file could not be renamed.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_RENAME_FAIL: c_int = 7;
/// There was an error parsing a plugin file.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_FILE_PARSE_FAIL: c_int = 10;
/// Invalid arguments were given for the function.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_INVALID_ARGS: c_int = 12;
/// A thread lock was poisoned.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_POISONED_THREAD_LOCK: c_int = 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_int = 15;
/// Permission denied while trying to access a filesystem path.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_IO_PERMISSION_DENIED: c_int = 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_int = 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_int = 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_int = 19;
/// Something panicked.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_PANICKED: c_int = 20;
/// A path cannot be encoded in UTF-8.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_PATH_ENCODE_FAIL: c_int = 21;
/// An unknown operating system error occurred.
#[unsafe(no_mangle)]
pub static LIBLO_ERROR_SYSTEM_ERROR: c_int = 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_int = 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_int = 23;
fn map_io_error(err: &std::io::Error) -> c_int {
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 fn map_error(err: &Error) -> c_int {
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,
}
}