mod events;
mod human;
mod json;
mod output;
use std::sync::Arc;
use storyteller::{
ChannelEventListener, ChannelHandlerGuard, ChannelReporter, EventListener, EventReporter,
HandlerGuard, event_channel,
};
use crate::reporter::human::HumanHandler;
use crate::reporter::json::JsonHandler;
use crate::values::OutputFormat;
pub use crate::reporter::events::CliEvent;
pub use crate::reporter::output::{Output, OutputError};
#[derive(Debug, thiserror::Error)]
pub enum ReporterError {
#[error("the output handler disconnected before the event was reported")]
Disconnected,
#[error("the output handler panicked")]
Panicked,
#[error(transparent)]
Output(#[from] OutputError),
}
pub struct Reporter {
reporter: ChannelReporter<CliEvent>,
guard: ChannelHandlerGuard,
output: Arc<Output>,
}
impl Reporter {
pub fn new(format: OutputFormat) -> Self {
Self::with_output(format, Arc::new(Output::stdout()))
}
pub fn with_output(format: OutputFormat, output: Arc<Output>) -> Self {
let (sender, receiver) = event_channel::<CliEvent>();
let listener = ChannelEventListener::new(receiver);
let guard = match format {
OutputFormat::Text => {
listener.run_handler(Arc::new(HumanHandler::new(Arc::clone(&output))))
}
OutputFormat::Json => {
listener.run_handler(Arc::new(JsonHandler::new(Arc::clone(&output))))
}
};
Self {
reporter: ChannelReporter::new(sender),
guard,
output,
}
}
#[allow(unused)] pub fn report(&self, event: impl Into<CliEvent>) -> Result<(), ReporterError> {
self.reporter
.report_event(event)
.map_err(|_| ReporterError::Disconnected)
}
pub fn finish(self) -> Result<(), ReporterError> {
let Self {
reporter,
guard,
output,
} = self;
let token = reporter
.disconnect()
.map_err(|_| ReporterError::Disconnected)?;
guard.join(token).map_err(|()| ReporterError::Panicked)?;
match output.take_error() {
Some(error) => Err(ReporterError::Output(error)),
None => Ok(()),
}
}
}
impl std::fmt::Debug for Reporter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Reporter")
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::sync::Mutex;
use super::*;
#[derive(Clone, Default)]
struct SharedBuffer(Arc<Mutex<Vec<u8>>>);
impl SharedBuffer {
fn contents(&self) -> Vec<u8> {
self.0.lock().unwrap().clone()
}
}
impl Write for SharedBuffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn human_reporter_finishes_without_events() {
let buffer = SharedBuffer::default();
let output = Arc::new(Output::new(buffer.clone()));
let reporter = Reporter::with_output(OutputFormat::Text, output);
reporter.finish().unwrap();
assert!(buffer.contents().is_empty());
}
#[test]
fn json_reporter_finishes_without_events() {
let buffer = SharedBuffer::default();
let output = Arc::new(Output::new(buffer.clone()));
let reporter = Reporter::with_output(OutputFormat::Json, output);
reporter.finish().unwrap();
assert!(buffer.contents().is_empty());
}
}