use std::fmt::{self, Display};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GoError {
msg: String,
source: Option<Box<GoError>>,
}
impl GoError {
fn new(msg: impl Into<String>) -> Self {
GoError { msg: msg.into(), source: None }
}
}
impl Display for GoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.msg)?;
if let Some(ref src) = self.source {
write!(f, ": {}", src)?;
}
Ok(())
}
}
impl std::error::Error for GoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref().map(|s| s as &dyn std::error::Error)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct error(Option<GoError>);
impl error {
pub fn is_nil(&self) -> bool { self.0.is_none() }
pub fn Error(&self) -> String {
match &self.0 {
Some(e) => format!("{}", e),
None => panic!("runtime error: invalid memory address or nil pointer dereference"),
}
}
}
impl Display for error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Some(e) => Display::fmt(e, f),
None => write!(f, "<nil>"),
}
}
}
#[allow(non_upper_case_globals)]
pub const nil: error = error(None);
pub fn New(msg: &str) -> error {
error(Some(GoError::new(msg)))
}
pub fn Wrap(err: error, msg: &str) -> error {
match err.0 {
Some(inner) => error(Some(GoError {
msg: msg.to_string(),
source: Some(Box::new(inner)),
})),
None => nil,
}
}
pub fn Is(err: &error, target: &error) -> bool {
let target_msg = match &target.0 {
Some(t) => &t.msg,
None => return err.0.is_none(),
};
let mut cur = err.0.as_ref();
while let Some(e) = cur {
if &e.msg == target_msg {
return true;
}
cur = e.source.as_deref();
}
false
}
pub fn Unwrap(err: error) -> error {
match err.0 {
Some(e) => match e.source {
Some(src) => error(Some(*src)),
None => nil,
},
None => nil,
}
}
pub fn Join(errs: &[error]) -> error {
let msgs: Vec<&String> = errs
.iter()
.filter_map(|e| e.0.as_ref().map(|g| &g.msg))
.collect();
if msgs.is_empty() {
return nil;
}
let joined: String = msgs
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join("\n");
error(Some(GoError::new(joined)))
}
pub fn As(err: &error, target: &mut error) -> bool {
let target_msg = match &target.0 {
Some(t) => t.msg.clone(),
None => return false,
};
let mut cur = err.0.as_ref();
while let Some(e) = cur {
if e.msg == target_msg {
*target = error(Some(e.clone()));
return true;
}
cur = e.source.as_deref();
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_displays_message() {
let e = New("boom");
assert_eq!(format!("{}", e), "boom");
}
#[test]
fn nil_displays_as_nil() {
assert_eq!(format!("{}", nil), "<nil>");
}
#[test]
fn nil_equality() {
let n: error = nil;
assert!(n == nil);
assert!(New("x") != nil);
}
#[test]
fn wrap_chains() {
let inner = New("disk full");
let outer = Wrap(inner, "save failed");
assert_eq!(format!("{}", outer), "save failed: disk full");
}
#[test]
fn wrap_nil_returns_nil() {
assert!(Wrap(nil, "ctx") == nil);
}
#[test]
fn is_walks_chain() {
let sentinel = New("not found");
let wrapped = Wrap(sentinel.clone(), "lookup");
assert!(Is(&wrapped, &sentinel));
assert!(!Is(&wrapped, &New("other")));
}
#[test]
fn unwrap_returns_inner_or_nil() {
let inner = New("inner");
let outer = Wrap(inner, "outer");
assert_eq!(format!("{}", Unwrap(outer)), "inner");
assert!(Unwrap(New("solo")) == nil);
}
#[test]
fn join_combines_messages() {
let e = Join(&[New("a"), New("b"), nil, New("c")]);
assert_eq!(format!("{}", e), "a\nb\nc");
}
#[test]
fn join_of_nils_is_nil() {
assert!(Join(&[nil, nil]) == nil);
assert!(Join(&[]) == nil);
}
#[test]
fn as_finds_wrapped_sentinel() {
let sentinel = New("not found");
let wrapped = Wrap(sentinel.clone(), "lookup");
let mut target = New("not found");
assert!(As(&wrapped, &mut target));
assert_eq!(format!("{}", target), "not found");
let mut target = New("other");
assert!(!As(&wrapped, &mut target));
}
}