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::types::output_format::OutputFormatArg;
pub use crate::reporter::events::{
AccountBackendEvent, AccountInfoEvent, AccountStatusEvent, BleStatusEvent, BusyProfileEvent,
BusySnapshotEvent, CliEvent, FramePayload, OkEvent, Payload, SettingsAccessEvent,
SettingsBrightnessEvent, SettingsNameEvent, SettingsVolumeEvent, SmartHomePairingEvent,
SmartHomeStartPairingEvent, SmartHomeSwitchEvent, StorageListEvent, StorageReadEvent,
StorageStatusEvent, StreamingScreenEvent, StreamingStatusEvent, SystemLogDumpEvent,
SystemStatusDeviceEvent, SystemStatusEvent, SystemStatusFirmwareEvent, SystemStatusPowerEvent,
SystemStatusSystemEvent, SystemTransportEvent, SystemVersionEvent, TimeNowEvent,
TimeTimezoneEvent, TimeTzlistEvent, UpdaterAutoupdateEvent, UpdaterChangelogEvent,
UpdaterStatusEvent, WifiStatusEvent,
};
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: OutputFormatArg) -> Self {
Self::with_output(format, Arc::new(Output::stdout()))
}
pub fn with_output(format: OutputFormatArg, output: Arc<Output>) -> Self {
let (sender, receiver) = event_channel::<CliEvent>();
let listener = ChannelEventListener::new(receiver);
let guard = match format {
OutputFormatArg::Text => {
listener.run_handler(Arc::new(HumanHandler::new(Arc::clone(&output))))
}
OutputFormatArg::Json => {
listener.run_handler(Arc::new(JsonHandler::new(Arc::clone(&output))))
}
};
Self {
reporter: ChannelReporter::new(sender),
guard,
output,
}
}
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 busylib::model::wifi::{StatusResponse, WifiState};
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(OutputFormatArg::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(OutputFormatArg::Json, output);
reporter.finish().unwrap();
assert!(buffer.contents().is_empty());
}
fn sample_event() -> CliEvent {
CliEvent::from(WifiStatusEvent::new(StatusResponse {
state: WifiState::Disconnected,
ssid: None,
bssid: None,
channel: None,
rssi: None,
security: None,
ip_config: None,
}))
}
fn render(format: OutputFormatArg, event: CliEvent) -> String {
let buffer = SharedBuffer::default();
let output = Arc::new(Output::new(buffer.clone()));
let reporter = Reporter::with_output(format, output);
reporter.report(event).unwrap();
reporter.finish().unwrap();
String::from_utf8(buffer.contents()).unwrap()
}
#[test]
fn human_reporter_writes_events_as_they_display() {
let event = sample_event();
assert_eq!(
render(OutputFormatArg::Text, event.clone()),
format!("{event}\n")
);
}
#[test]
fn json_reporter_writes_events_as_a_line_of_json() {
let event = sample_event();
assert_eq!(
render(OutputFormatArg::Json, event.clone()),
format!("{}\n", serde_json::to_string(&event).unwrap())
);
}
}