extern crate alloc;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicPtr, Ordering};
use hashbrown::HashMap;
pub struct JsonAnyEntry {
pub type_url: &'static str,
pub to_json: fn(&[u8]) -> Result<serde_json::Value, String>,
pub from_json: fn(serde_json::Value) -> Result<Vec<u8>, String>,
pub is_wkt: bool,
}
#[deprecated(since = "0.3.0", note = "renamed to JsonAnyEntry")]
pub type AnyTypeEntry = JsonAnyEntry;
pub struct AnyRegistry {
entries: HashMap<String, JsonAnyEntry>,
}
impl AnyRegistry {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn register(&mut self, entry: JsonAnyEntry) {
self.entries.insert(entry.type_url.to_owned(), entry);
}
pub fn lookup(&self, type_url: &str) -> Option<&JsonAnyEntry> {
self.entries.get(type_url)
}
}
impl core::fmt::Debug for AnyRegistry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AnyRegistry")
.field("type_urls", &self.entries.keys().collect::<Vec<_>>())
.finish()
}
}
impl Default for AnyRegistry {
fn default() -> Self {
Self::new()
}
}
static ANY_REGISTRY: AtomicPtr<AnyRegistry> = AtomicPtr::new(core::ptr::null_mut());
#[deprecated(since = "0.3.0", note = "use buffa::type_registry::set_type_registry")]
pub fn set_any_registry(registry: Box<AnyRegistry>) {
let new_ptr = Box::into_raw(registry);
ANY_REGISTRY.swap(new_ptr, Ordering::AcqRel);
}
#[doc(hidden)]
pub fn clear_any_registry() {
ANY_REGISTRY.swap(core::ptr::null_mut(), Ordering::AcqRel);
}
pub fn with_any_registry<R>(f: impl FnOnce(Option<&AnyRegistry>) -> R) -> R {
let ptr = ANY_REGISTRY.load(Ordering::Acquire);
if ptr.is_null() {
f(None)
} else {
f(Some(unsafe { &*ptr }))
}
}
#[cfg(test)]
mod tests {
#![allow(deprecated)]
use super::*;
fn dummy_to_json(_bytes: &[u8]) -> Result<serde_json::Value, String> {
Ok(serde_json::Value::Null)
}
fn dummy_from_json(_value: serde_json::Value) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
macro_rules! entry {
($url:expr, $wkt:expr) => {
JsonAnyEntry {
type_url: $url,
to_json: dummy_to_json,
from_json: dummy_from_json,
is_wkt: $wkt,
}
};
}
#[test]
fn register_and_lookup() {
let mut registry = AnyRegistry::new();
registry.register(entry!("type.googleapis.com/test.Message", false));
assert!(registry
.lookup("type.googleapis.com/test.Message")
.is_some());
assert!(registry.lookup("type.googleapis.com/test.Other").is_none());
}
#[test]
fn lookup_returns_correct_entry() {
let mut registry = AnyRegistry::new();
registry.register(entry!("type.googleapis.com/test.Wkt", true));
registry.register(entry!("type.googleapis.com/test.Regular", false));
let wkt = registry.lookup("type.googleapis.com/test.Wkt").unwrap();
assert!(wkt.is_wkt);
let regular = registry.lookup("type.googleapis.com/test.Regular").unwrap();
assert!(!regular.is_wkt);
}
static REGISTRY_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn global_registry() {
let _guard = REGISTRY_TEST_LOCK.lock().unwrap();
let mut registry = AnyRegistry::new();
registry.register(entry!("type.googleapis.com/test.Global", false));
set_any_registry(Box::new(registry));
with_any_registry(|reg| {
let reg = reg.expect("registry should be set");
assert!(reg.lookup("type.googleapis.com/test.Global").is_some());
});
clear_any_registry();
with_any_registry(|reg| {
assert!(reg.is_none());
});
}
#[test]
fn set_registry_twice_supersedes_first() {
let _guard = REGISTRY_TEST_LOCK.lock().unwrap();
let mut first = AnyRegistry::new();
first.register(entry!("type.googleapis.com/test.First", false));
set_any_registry(Box::new(first));
let mut second = AnyRegistry::new();
second.register(entry!("type.googleapis.com/test.Second", true));
set_any_registry(Box::new(second));
with_any_registry(|reg| {
let reg = reg.expect("registry should be set");
assert!(
reg.lookup("type.googleapis.com/test.First").is_none(),
"first registry should be superseded"
);
assert!(
reg.lookup("type.googleapis.com/test.Second").is_some(),
"second registry should be active"
);
});
clear_any_registry();
}
#[test]
fn default_registry_is_empty() {
let registry = AnyRegistry::default();
assert!(registry.lookup("anything").is_none());
}
#[test]
fn debug_shows_type_urls() {
let mut registry = AnyRegistry::new();
registry.register(entry!("type.googleapis.com/test.Debug", false));
let debug = format!("{:?}", registry);
assert!(
debug.contains("test.Debug"),
"Debug output should contain the type URL: {debug}"
);
assert!(
debug.contains("AnyRegistry"),
"Debug output should contain struct name: {debug}"
);
}
}