use serde::{Deserialize, Serialize};
use std::sync::OnceLock;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ErrorCodeInfo {
pub subsystem: String,
pub title: String,
pub message_template: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs_url: Option<String>,
pub since_version: String,
}
pub trait CatalogProvider: Send + Sync {
fn lookup(&self, code: &str) -> Option<&ErrorCodeInfo>;
}
pub struct EmptyCatalog;
impl CatalogProvider for EmptyCatalog {
fn lookup(&self, _code: &str) -> Option<&ErrorCodeInfo> {
None
}
}
static CATALOG: OnceLock<Box<dyn CatalogProvider>> = OnceLock::new();
pub fn install_catalog(provider: Box<dyn CatalogProvider>) {
let _ = CATALOG.set(provider);
}
fn catalog() -> &'static dyn CatalogProvider {
static EMPTY: EmptyCatalog = EmptyCatalog;
match CATALOG.get() {
Some(provider) => &**provider,
None => &EMPTY,
}
}
pub fn get_error_info(code: &str) -> Option<&'static ErrorCodeInfo> {
catalog().lookup(code)
}
pub fn get_docs_url(code: &str) -> Option<&'static str> {
catalog()
.lookup(code)
.and_then(|info| info.docs_url.as_deref())
}
pub fn get_subsystem(code: &str) -> Option<&'static str> {
catalog().lookup(code).map(|info| info.subsystem.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_info(subsystem: &str, docs_url: Option<&str>) -> ErrorCodeInfo {
ErrorCodeInfo {
subsystem: subsystem.to_string(),
title: "Sample".to_string(),
message_template: "sample".to_string(),
docs_url: docs_url.map(str::to_string),
since_version: "0.0.0".to_string(),
}
}
#[test]
fn empty_catalog_returns_none() {
let empty = EmptyCatalog;
assert!(empty.lookup("Q-0-1").is_none());
assert!(empty.lookup("anything").is_none());
}
struct MockCatalog {
entry: ErrorCodeInfo,
}
impl CatalogProvider for MockCatalog {
fn lookup(&self, code: &str) -> Option<&ErrorCodeInfo> {
(code == "Q-0-1").then_some(&self.entry)
}
}
#[test]
fn installed_catalog_is_used_by_lookups() {
install_catalog(Box::new(MockCatalog {
entry: sample_info("internal", Some("https://example.test/docs/Q-0-1")),
}));
assert_eq!(get_subsystem("Q-0-1"), Some("internal"));
assert_eq!(
get_docs_url("Q-0-1"),
Some("https://example.test/docs/Q-0-1")
);
assert!(get_error_info("Q-0-1").is_some());
assert!(get_subsystem("Q-9-9").is_none());
assert!(get_docs_url("Q-9-9").is_none());
assert!(get_error_info("Q-9-9").is_none());
}
}