error-path-macros 0.1.0

Attribute macros for automatically attaching error paths to Result-returning Rust functions and impl blocks.
Documentation
extern crate self as error_path;

use error_path_macros::{error_path, error_path_impl};
use std::future::Future;
use std::pin::pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};

trait WithErrorPath: Sized {
    fn with_error_path(self, path: &str) -> Self;
}

#[derive(Debug, PartialEq)]
struct TestError(Vec<String>);

impl WithErrorPath for TestError {
    fn with_error_path(mut self, path: &str) -> Self {
        self.0.insert(0, path.to_owned());
        self
    }
}

type TestResult<T> = Result<T, TestError>;

#[error_path]
fn sync_failure() -> TestResult<()> {
    Err(TestError(vec![]))
}

#[error_path("custom.path")]
async fn async_failure() -> TestResult<()> {
    Err(TestError(vec![]))
}

struct Service;

#[error_path_impl]
impl Service {
    fn failure(&self) -> TestResult<()> {
        Err(TestError(vec![]))
    }

    async fn async_failure(&self) -> TestResult<()> {
        Err(TestError(vec![]))
    }

    #[error_path_skip]
    fn skipped(&self) -> TestResult<()> {
        Err(TestError(vec![]))
    }

    fn value(&self) -> usize {
        42
    }
}

struct DelimitedService;

#[error_path_impl("login::api")]
impl DelimitedService {
    fn failure(&self) -> TestResult<()> {
        Err(TestError(vec![]))
    }
}

struct NoopWake;

impl Wake for NoopWake {
    fn wake(self: Arc<Self>) {}
}

fn block_on<F: Future>(future: F) -> F::Output {
    let waker = Waker::from(Arc::new(NoopWake));
    let mut context = Context::from_waker(&waker);
    let mut future = pin!(future);
    loop {
        if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
            return output;
        }
    }
}

#[test]
fn wraps_sync_function_with_default_path() {
    assert_eq!(sync_failure(), Err(TestError(vec!["sync_failure".into()])));
}

#[test]
fn wraps_async_function_with_custom_path() {
    assert_eq!(
        block_on(async_failure()),
        Err(TestError(vec!["custom.path".into()]))
    );
}

#[test]
fn wraps_sync_impl_method() {
    assert_eq!(
        Service.failure(),
        Err(TestError(vec!["Service".into(), "failure".into()]))
    );
}

#[test]
fn wraps_async_impl_method() {
    assert_eq!(
        block_on(Service.async_failure()),
        Err(TestError(vec!["Service".into(), "async_failure".into()]))
    );
}

#[test]
fn preserves_explicit_base_as_a_separate_literal() {
    assert_eq!(
        DelimitedService.failure(),
        Err(TestError(vec!["login::api".into(), "failure".into()]))
    );
}

#[test]
fn leaves_skipped_method_unwrapped() {
    assert_eq!(Service.skipped(), Err(TestError(vec![])));
}

#[test]
fn leaves_non_result_method_unwrapped() {
    assert_eq!(Service.value(), 42);
}