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>, } 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 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, pub positions: Vec, } 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 for OrcErr { fn eq(&self, other: &IStr) -> bool { self.description == *other } } impl From for Vec { 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); impl OrcErrv { pub fn new(errors: impl IntoIterator) -> Result { let v = errors.into_iter().collect_vec(); if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) } } #[must_use] pub fn extended(mut self, errors: impl IntoIterator) -> Self where Self: Extend { 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 { 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 + '_ { self.0.iter().flat_map(|e| e.positions.iter().cloned()) } pub fn to_api(&self) -> Vec { self.0.iter().map(OrcErr::to_api).collect() } pub async fn from_api<'a>(api: impl IntoIterator) -> Self { Self(join_all(api.into_iter().map(OrcErr::from_api)).await) } pub fn iter(&self) -> impl Iterator + '_ { self.0.iter().cloned() } } impl From 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 for OrcErrv { fn extend>(&mut self, iter: T) { self.0.extend(iter) } } impl Extend for OrcErrv { fn extend>(&mut self, iter: T) { self.0.extend(iter.into_iter().flatten()) } } impl IntoIterator for OrcErrv { type IntoIter = std::vec::IntoIter; 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 = Result; pub fn join_ok(left: OrcRes, right: OrcRes) -> 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) -> OrcErrv { mk_errv::(description, message, []) } pub fn mk_errv>( description: IStr, message: impl AsRef, posv: impl IntoIterator, ) -> 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>( err: std::io::Error, posv: impl IntoIterator, ) -> OrcErrv { mk_errv(is(&err.kind().to_string()).await, err.to_string(), posv) } pub async fn os_str_to_string>( str: &OsStr, posv: impl IntoIterator, ) -> 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>>, } 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(fut: impl Future) -> OrcRes { 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(fut: impl Future>) -> OrcRes { 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) { 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}" ) }) }