use std::fmt;
#[derive(Debug)]
pub enum BrowserError {
NoCommandFound,
Failed(String),
}
impl fmt::Display for BrowserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoCommandFound => write!(f, "no command found to open the browser"),
Self::Failed(msg) => write!(f, "{msg}"),
}
}
}
impl std::error::Error for BrowserError {}
pub trait Browser: Send + Sync {
fn open(&self, url: &str) -> Result<(), BrowserError>;
}
pub struct DefaultBrowser;
impl Browser for DefaultBrowser {
fn open(&self, url: &str) -> Result<(), BrowserError> {
open::that(url).map_err(|e| BrowserError::Failed(format!("open browser: {e}")))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use super::*;
#[test]
fn default_browser_trait_is_object_safe() {
fn _assert_object_safe(_b: &dyn Browser) {}
let browser = DefaultBrowser;
_assert_object_safe(&browser);
}
struct MockBrowser {
called: Arc<AtomicBool>,
should_fail: bool,
}
impl Browser for MockBrowser {
fn open(&self, _url: &str) -> Result<(), BrowserError> {
self.called.store(true, Ordering::SeqCst);
if self.should_fail {
Err(BrowserError::NoCommandFound)
} else {
Ok(())
}
}
}
#[test]
fn custom_browser_implementation() {
let called = Arc::new(AtomicBool::new(false));
let browser = MockBrowser {
called: Arc::clone(&called),
should_fail: false,
};
let result = browser.open("https://example.com");
assert!(result.is_ok());
assert!(called.load(Ordering::SeqCst));
}
#[test]
fn custom_browser_can_return_no_command_found() {
let called = Arc::new(AtomicBool::new(false));
let browser = MockBrowser {
called: Arc::clone(&called),
should_fail: true,
};
let result = browser.open("https://example.com");
assert!(result.is_err());
assert!(called.load(Ordering::SeqCst));
let err = result.unwrap_err();
assert!(
matches!(err, BrowserError::NoCommandFound),
"expected NoCommandFound, got: {err}"
);
}
#[test]
fn browser_error_variants() {
let not_found = BrowserError::NoCommandFound;
assert_eq!(
not_found.to_string(),
"no command found to open the browser"
);
let failed = BrowserError::Failed("open browser: permission denied".into());
assert_eq!(failed.to_string(), "open browser: permission denied");
}
#[test]
fn browser_error_is_debug() {
let err = BrowserError::NoCommandFound;
let debug = format!("{err:?}");
assert!(debug.contains("NoCommandFound"));
let err = BrowserError::Failed("test".into());
let debug = format!("{err:?}");
assert!(debug.contains("Failed"));
}
#[test]
fn custom_browser_as_boxed_trait_object() {
let called = Arc::new(AtomicBool::new(false));
let browser: Box<dyn Browser> = Box::new(MockBrowser {
called: Arc::clone(&called),
should_fail: false,
});
let result = browser.open("https://github.com");
assert!(result.is_ok());
assert!(called.load(Ordering::SeqCst));
}
}