use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use crate::generation::manifest::{CatalogManifest, ManifestTable};
pub fn table_for_message<'a>(
manifest: &'a CatalogManifest,
message_type: &str,
) -> Option<&'a ManifestTable> {
static INDEX: OnceLock<Mutex<HashMap<String, HashMap<String, usize>>>> = OnceLock::new();
let leaf = message_type
.rsplit('.')
.next()
.unwrap_or(message_type)
.to_ascii_lowercase();
let exact = message_type.to_ascii_lowercase();
let cache_key = if manifest.checksum_sha256.is_empty() {
format!("ptr:{:p}:{}", manifest, manifest.tables.len())
} else {
manifest.checksum_sha256.clone()
};
let map = INDEX.get_or_init(|| Mutex::new(HashMap::new()));
if let Ok(mut guard) = map.lock() {
const INDEX_CACHE_CAP: usize = 64;
if guard.len() >= INDEX_CACHE_CAP && !guard.contains_key(&cache_key) {
guard.clear();
}
let index = guard.entry(cache_key).or_insert_with(|| {
let mut built = HashMap::new();
for (idx, table) in manifest.tables.iter().enumerate() {
if !table.message_name.trim().is_empty() {
built
.entry(table.message_fqn().to_ascii_lowercase())
.or_insert(idx);
built.insert(table.message_name.to_ascii_lowercase(), idx);
if let Some(short) = table.message_name.rsplit('.').next() {
built.entry(short.to_ascii_lowercase()).or_insert(idx);
}
}
if !table.table.trim().is_empty() {
built.insert(table.table.to_ascii_lowercase(), idx);
}
}
built
});
if let Some(idx) = index.get(&exact).or_else(|| index.get(&leaf)) {
return manifest.tables.get(*idx);
}
}
manifest.tables.iter().find(|table| {
table.message_name.eq_ignore_ascii_case(message_type)
|| table.message_name.eq_ignore_ascii_case(&leaf)
|| table.table.eq_ignore_ascii_case(message_type)
|| table.table.eq_ignore_ascii_case(&leaf)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn table(proto_package: &str, message: &str, schema: &str, name: &str) -> ManifestTable {
ManifestTable {
message_name: message.to_string(),
proto_package: proto_package.to_string(),
schema: schema.to_string(),
table: name.to_string(),
..ManifestTable::default()
}
}
#[test]
fn exact_fqn_disambiguates_shared_short_name() {
let manifest = CatalogManifest {
checksum_sha256: "srv003-fqn-index".to_string(),
tables: vec![
table("ambulife.authn.entity.v1", "User", "authn", "users"),
table("udb.core.authn.entity.v1", "User", "udb_authn", "users"),
],
..CatalogManifest::default()
};
let consumer = table_for_message(&manifest, "ambulife.authn.entity.v1.User")
.expect("consumer FQN must resolve");
assert_eq!(consumer.schema, "authn");
let embedded = table_for_message(&manifest, "udb.core.authn.entity.v1.User")
.expect("embedded FQN must resolve");
assert_eq!(embedded.schema, "udb_authn");
}
}