Rework handling of esplugin errors

The error scenario represented by PluginNotLoadedError is distinguished from all other esplugin errors by LOOT, so it's worth handling separately.
This commit is contained in:
Oliver Hamlet
2025-06-08 20:26:07 +01:00
parent 05c984c769
commit 11bd321861
20 changed files with 81 additions and 321 deletions
-50
View File
@@ -1,50 +0,0 @@
// Unless otherwise noted, these constants and the mapping logic are copied from
// esplugin-ffi.
use std::ffi::c_int;
use esplugin::Error;
const ESP_ERROR_PARSE_ERROR: c_int = 5;
const ESP_ERROR_NO_FILENAME: c_int = 7;
const ESP_ERROR_TEXT_DECODE_ERROR: c_int = 8;
const ESP_ERROR_IO_ERROR: c_int = 10;
const ESP_ERROR_FILE_NOT_FOUND: c_int = 11;
const ESP_ERROR_IO_PERMISSION_DENIED: c_int = 12;
const ESP_ERROR_UNRESOLVED_RECORD_IDS: c_int = 13;
const ESP_ERROR_PLUGIN_METADATA_NOT_FOUND: c_int = 14;
// This constant is not copied from esplugin-ffi, but does not conflict with any
// values defined there.
pub(crate) const ESP_ERROR_UNKNOWN: c_int = c_int::MAX;
#[expect(
clippy::wildcard_enum_match_arm,
reason = "It doesn't matter if other I/O error kinds are added in the future"
)]
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,
}
}
#[must_use]
pub(crate) fn map_error(err: &Error) -> c_int {
match err {
Error::IoError(x) => map_io_error(x),
Error::NoFilename(_) => ESP_ERROR_NO_FILENAME,
Error::ParsingIncomplete(_) | 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,
}
}
-94
View File
@@ -92,100 +92,6 @@
clippy::verbose_file_reads,
clippy::wildcard_enum_match_arm
)]
use std::{error::Error, ffi::c_int};
use libloot::error::PluginDataError;
mod esplugin;
// It's important for API stability that these variants' values don't change.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum SystemErrorCategory {
Esplugin = 1,
}
impl std::fmt::Display for SystemErrorCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SystemErrorCategory::Esplugin => write!(f, "esplugin"),
}
}
}
#[derive(Debug)]
pub struct SystemError {
code: c_int,
category: SystemErrorCategory,
message: String,
}
impl SystemError {
#[must_use]
pub fn code(&self) -> c_int {
self.code
}
#[must_use]
pub fn category(&self) -> SystemErrorCategory {
self.category
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for SystemError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} error, code {}: {}",
self.category, self.code, self.message
)
}
}
impl std::error::Error for SystemError {}
impl From<PluginDataError> for SystemError {
fn from(value: PluginDataError) -> Self {
const ESPLUGIN_ERROR_UNKNOWN: (i32, &str) = (
esplugin::ESP_ERROR_UNKNOWN,
"Could not retrieve esplugin error message",
);
from_error(
value,
SystemErrorCategory::Esplugin,
ESPLUGIN_ERROR_UNKNOWN,
crate::esplugin::map_error,
)
}
}
fn from_error<U: Error, V: Error + 'static, F: Fn(&V) -> c_int>(
error: U,
category: SystemErrorCategory,
default: (i32, &'static str),
to_code: F,
) -> SystemError {
let (code, message) = error
.source()
.and_then(|s| s.downcast_ref::<V>())
.map_or_else(
|| (default.0, default.1.to_owned()),
|e| (to_code(e), e.to_string()),
);
SystemError {
code,
category,
message,
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct UnsupportedEnumValueError;