1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! The context of a single execution.
//!
//! Is used to accumulate errors.
//!
//! This is preferred over results, since it permits reporting complex errors and their
//! corresponding locations.

use error_pos::ErrorPos;
use errors::{Error, ErrorKind};
use std::cell::{BorrowError, Ref, RefCell};
use std::fmt;

pub enum ContextItem {
    /// A positional error.
    ErrorPos(ErrorPos, String),
    /// A positional information string.
    InfoPos(ErrorPos, String),
}

#[derive(Default)]
pub struct Context {
    errors: RefCell<Vec<ContextItem>>,
}

/// A reporter that processes the given error for the context.
///
/// Converting the reporter into an `ErrorKind` causes it to accumulate the errors to the `Context`.
pub struct Reporter<'a> {
    ctx: &'a Context,
    errors: Vec<ContextItem>,
}

impl<'a> Reporter<'a> {
    pub fn err<P: Into<ErrorPos>, E: fmt::Display>(mut self, pos: P, error: E) -> Self {
        self.errors.push(ContextItem::ErrorPos(
            pos.into(),
            error.to_string(),
        ));

        self
    }

    pub fn info<P: Into<ErrorPos>, I: fmt::Display>(mut self, pos: P, info: I) -> Self {
        self.errors.push(ContextItem::InfoPos(
            pos.into(),
            info.to_string(),
        ));

        self
    }

    /// Close the reporter, saving any reported errors to the context.
    pub fn close(self) -> Option<Error> {
        if self.errors.is_empty() {
            return None;
        }

        let ctx = self.ctx;

        let mut errors = ctx.errors.try_borrow_mut().expect(
            "exclusive mutable access",
        );

        errors.extend(self.errors);
        Some(ErrorKind::Context.into())
    }
}

impl<'a> From<Reporter<'a>> for Error {
    fn from(reporter: Reporter<'a>) -> Error {
        reporter.close();
        ErrorKind::Context.into()
    }
}

impl Context {
    /// Build a handle that can be used in conjunction with Result#map_err.
    pub fn report(&self) -> Reporter {
        Reporter {
            ctx: self,
            errors: Vec::new(),
        }
    }

    /// Iterate over all reporter errors.
    pub fn errors(&self) -> Result<Ref<Vec<ContextItem>>, BorrowError> {
        self.errors.try_borrow()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use errors::*;
    use object::{BytesObject, Object};
    use pos::Pos;
    use std::rc::Rc;
    use std::result;
    use std::sync::Arc;

    #[test]
    fn test_handle() {
        let object = BytesObject::new("test".to_string(), Arc::new(Vec::new()));

        let pos: Pos = (Rc::new(object.clone_object()), 0usize, 0usize).into();
        let other_pos: Pos = (Rc::new(object.clone_object()), 0usize, 0usize).into();

        let ctx = Context::default();

        let result: result::Result<(), &str> = Err("nope");

        let a: Result<()> = result.map_err(|e| {
            ctx.report()
                .err(pos, e)
                .err(other_pos, "previously reported here")
                .into()
        });

        let e = a.unwrap_err();

        match e.kind() {
            &ErrorKind::Context => {}
            other => panic!("unexpected: {:?}", other),
        }

        assert_eq!(2, ctx.errors().unwrap().len());
    }
}