use std::sync::OnceLock;
use crate::Diagnostic;
use crate::protocol::DiagnosticLevel;
pub type DiagnosticHook = dyn Fn(DiagnosticLevel, &Diagnostic) + Send + Sync + 'static;
static HOOK: OnceLock<Box<DiagnosticHook>> = OnceLock::new();
pub fn set_hook(hook: Box<DiagnosticHook>) {
if HOOK.set(hook).is_err() {
log::warn!("diagnostics::set_hook called twice; keeping the first hook");
}
}
pub fn emit(level: DiagnosticLevel, diagnostic: Diagnostic) {
match level {
DiagnosticLevel::Info => log::info!("{diagnostic}"),
DiagnosticLevel::Warn => log::warn!("{diagnostic}"),
DiagnosticLevel::Error => log::error!("{diagnostic}"),
}
if let Some(hook) = HOOK.get() {
hook(level, &diagnostic);
}
}
pub fn warn(diagnostic: Diagnostic) {
emit(DiagnosticLevel::Warn, diagnostic);
}
pub fn info(diagnostic: Diagnostic) {
emit(DiagnosticLevel::Info, diagnostic);
}
pub fn error(diagnostic: Diagnostic) {
emit(DiagnosticLevel::Error, diagnostic);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emit_without_hook_logs_without_panicking() {
emit(
DiagnosticLevel::Warn,
Diagnostic::FontFamilyNotFound {
family: "NeverLoaded".into(),
},
);
}
#[test]
fn level_display_is_snake_case() {
assert_eq!(DiagnosticLevel::Info.to_string(), "info");
assert_eq!(DiagnosticLevel::Warn.to_string(), "warn");
assert_eq!(DiagnosticLevel::Error.to_string(), "error");
}
}