Files
orchid/orchid-base/src/error.rs
Lawrence Bethlenfalvy 1a7230ce9b Traditional route appears to work
Beginnings of dylib extensions, entirely untestted
2026-01-12 01:38:10 +01:00

282 lines
8.3 KiB
Rust

use std::cell::RefCell;
use std::ffi::OsStr;
use std::fmt;
use std::ops::Add;
use std::rc::Rc;
use std::sync::Arc;
use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use task_local::task_local;
use crate::api;
use crate::interner::{IStr, es, is};
use crate::location::Pos;
/// A point of interest in resolving the error, such as the point where
/// processing got stuck, a command that is likely to be incorrect
#[derive(Clone, Debug)]
pub struct ErrPos {
/// The suspected origin
pub position: Pos,
/// Any information about the role of this origin
pub message: Option<Arc<String>>,
}
impl ErrPos {
pub fn new(msg: &str, position: Pos) -> Self {
Self { message: Some(Arc::new(msg.to_string())), position }
}
async fn from_api(api: &api::ErrLocation) -> Self {
Self {
message: Some(api.message.clone()).filter(|s| !s.is_empty()),
position: Pos::from_api(&api.location).await,
}
}
fn to_api(&self) -> api::ErrLocation {
api::ErrLocation {
message: self.message.clone().unwrap_or_default(),
location: self.position.to_api(),
}
}
}
impl From<Pos> for ErrPos {
fn from(origin: Pos) -> Self { Self { position: origin, message: None } }
}
impl fmt::Display for ErrPos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.message {
Some(msg) => write!(f, "{}: {}", self.position, msg),
None => write!(f, "{}", self.position),
}
}
}
#[derive(Clone, Debug)]
pub struct OrcErr {
pub description: IStr,
pub message: Arc<String>,
pub positions: Vec<ErrPos>,
}
impl OrcErr {
fn to_api(&self) -> api::OrcError {
api::OrcError {
description: self.description.to_api(),
message: self.message.clone(),
locations: self.positions.iter().map(ErrPos::to_api).collect(),
}
}
async fn from_api(api: &api::OrcError) -> Self {
Self {
description: es(api.description).await,
message: api.message.clone(),
positions: join_all(api.locations.iter().map(ErrPos::from_api)).await,
}
}
}
impl PartialEq<IStr> for OrcErr {
fn eq(&self, other: &IStr) -> bool { self.description == *other }
}
impl From<OrcErr> for Vec<OrcErr> {
fn from(value: OrcErr) -> Self { vec![value] }
}
impl fmt::Display for OrcErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pstr = self.positions.iter().map(|p| format!("{p}")).join("; ");
write!(f, "{}: {} @ {}", self.description, self.message, pstr)
}
}
#[derive(Clone, Debug)]
pub struct EmptyErrv;
impl fmt::Display for EmptyErrv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "OrcErrv must not be empty")
}
}
#[derive(Clone, Debug)]
pub struct OrcErrv(Vec<OrcErr>);
impl OrcErrv {
pub fn new(errors: impl IntoIterator<Item = OrcErr>) -> Result<Self, EmptyErrv> {
let v = errors.into_iter().collect_vec();
if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) }
}
#[must_use]
pub fn extended<T>(mut self, errors: impl IntoIterator<Item = T>) -> Self
where Self: Extend<T> {
self.extend(errors);
self
}
#[must_use]
pub fn len(&self) -> usize { self.0.len() }
#[must_use]
pub fn is_empty(&self) -> bool { self.len() == 0 }
#[must_use]
pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) }
#[must_use]
pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option<Self> {
let v = self.0.into_iter().filter(f).collect_vec();
if v.is_empty() { None } else { Some(Self(v)) }
}
#[must_use]
pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) }
pub fn pos_iter(&self) -> impl Iterator<Item = ErrPos> + '_ {
self.0.iter().flat_map(|e| e.positions.iter().cloned())
}
pub fn to_api(&self) -> Vec<api::OrcError> { self.0.iter().map(OrcErr::to_api).collect() }
pub async fn from_api<'a>(api: impl IntoIterator<Item = &'a api::OrcError>) -> Self {
Self(join_all(api.into_iter().map(OrcErr::from_api)).await)
}
pub fn iter(&self) -> impl Iterator<Item = OrcErr> + '_ { self.0.iter().cloned() }
}
impl From<OrcErr> for OrcErrv {
fn from(value: OrcErr) -> Self { Self(vec![value]) }
}
impl Add for OrcErrv {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output { Self(self.0.into_iter().chain(rhs.0).collect_vec()) }
}
impl Extend<OrcErr> for OrcErrv {
fn extend<T: IntoIterator<Item = OrcErr>>(&mut self, iter: T) { self.0.extend(iter) }
}
impl Extend<OrcErrv> for OrcErrv {
fn extend<T: IntoIterator<Item = OrcErrv>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().flatten())
}
}
impl IntoIterator for OrcErrv {
type IntoIter = std::vec::IntoIter<OrcErr>;
type Item = OrcErr;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
impl fmt::Display for OrcErrv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.iter().join("\n"))
}
}
pub type OrcRes<T> = Result<T, OrcErrv>;
pub fn join_ok<T, U>(left: OrcRes<T>, right: OrcRes<U>) -> OrcRes<(T, U)> {
match (left, right) {
(Ok(t), Ok(u)) => Ok((t, u)),
(Err(e), Ok(_)) | (Ok(_), Err(e)) => Err(e),
(Err(e1), Err(e2)) => Err(e1 + e2),
}
}
#[macro_export]
macro_rules! join_ok {
($($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
let $crate::join_ok!(@NAMES $($names $(: $tys)? = $vals;)*)
:
$crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*)
=
$crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*)?;
};
(@NAMES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
($name, $crate::join_ok!(@NAMES $($names $(: $tys)? = $vals;)*))
};
(@NAMES) => { _ };
(@TYPES $name:ident : $ty:ty = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
($ty, $crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*))
};
(@TYPES $name:ident = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
(_, $crate::join_ok!(@TYPES $($names $(: $tys)? = $vals;)*))
};
(@TYPES) => { () };
(@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
$crate::error::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
};
(@VALUES) => { Ok(()) };
}
pub fn mk_errv_floating(description: IStr, message: impl AsRef<str>) -> OrcErrv {
mk_errv::<Pos>(description, message, [])
}
pub fn mk_errv<I: Into<ErrPos>>(
description: IStr,
message: impl AsRef<str>,
posv: impl IntoIterator<Item = I>,
) -> OrcErrv {
OrcErr {
description,
message: Arc::new(message.as_ref().to_string()),
positions: posv.into_iter().map_into().collect(),
}
.into()
}
pub async fn async_io_err<I: Into<ErrPos>>(
err: std::io::Error,
posv: impl IntoIterator<Item = I>,
) -> OrcErrv {
mk_errv(is(&err.kind().to_string()).await, err.to_string(), posv)
}
pub async fn os_str_to_string<I: Into<ErrPos>>(
str: &OsStr,
posv: impl IntoIterator<Item = I>,
) -> OrcRes<&str> {
match str.to_str() {
Some(str) => Ok(str),
None => Err(mk_errv(
is("Non-unicode string").await,
format!("{str:?} is not representable as unicode"),
posv,
)),
}
}
#[derive(Clone, Default)]
struct Reporter {
errors: Rc<RefCell<Vec<OrcErr>>>,
}
task_local! {
static REPORTER: Reporter;
}
/// Run the future with a new reporter, and return all errors reported within.
///
/// If your future returns [OrcRes], see [try_with_reporter]
pub async fn with_reporter<T>(fut: impl Future<Output = T>) -> OrcRes<T> {
try_with_reporter(fut.map(Ok)).await
}
/// Run the future with a new reporter, and return all errors either returned or
/// reported by it
///
/// If your future may report errors but always returns an approximate value,
/// see [with_reporter]
pub async fn try_with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRes<T> {
let rep = Reporter::default();
let res = REPORTER.scope(rep.clone(), fut).await;
let errors = rep.errors.take();
match (res, &errors[..]) {
(Ok(t), []) => Ok(t),
(Ok(_), [_, ..]) => Err(OrcErrv::new(errors).unwrap()),
(Err(e), _) => Err(e.extended(errors)),
}
}
pub async fn is_erroring() -> bool {
(REPORTER.try_with(|r| !r.errors.borrow().is_empty()))
.expect("Sidechannel errors must be caught by a reporter")
}
/// Report an error that is fatal and prevents a correct output, but
/// still allows the current task to continue and produce an approximate output.
/// This can be used for
pub fn report(e: impl Into<OrcErrv>) {
let errv = e.into();
REPORTER.try_with(|r| r.errors.borrow_mut().extend(errv.clone())).unwrap_or_else(|_| {
panic!(
"Unhandled error! Sidechannel errors must be caught by an enclosing call to with_reporter.\n\
Error: {errv}"
)
})
}