use std::collections::HashMap;
use std::sync::{Arc, LazyLock, Mutex, PoisonError};
pub type CrateStorage<T> = LazyLock<Mutex<HashMap<String, Arc<Vec<T>>>>>;
pub trait SourceIdentified {
fn fn_name(&self) -> &str;
fn file_path(&self) -> Option<&str>;
}
fn same_source<T: SourceIdentified>(left: &T, right: &T) -> bool {
left.fn_name() == right.fn_name()
&& crate::file_utils::paths_equal_normalized(left.file_path(), right.file_path())
}
pub fn register<T: Clone + SourceIdentified>(storage: &CrateStorage<T>, info: T) {
let mut guard = storage.lock().unwrap_or_else(PoisonError::into_inner);
let bucket = Arc::make_mut(
guard
.entry(crate::schema_impl::current_crate_key())
.or_insert_with(|| Arc::new(Vec::new())),
);
if let Some(existing) = bucket
.iter_mut()
.find(|existing| same_source(&**existing, &info))
{
*existing = info;
} else {
bucket.push(info);
}
}
#[must_use]
pub fn current_crate_items<T>(storage: &CrateStorage<T>) -> Arc<Vec<T>> {
storage
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get(&crate::schema_impl::current_crate_key())
.cloned()
.unwrap_or_else(|| Arc::new(Vec::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
struct TestEntry {
fn_name: String,
file_path: Option<String>,
payload: &'static str,
}
impl SourceIdentified for TestEntry {
fn fn_name(&self) -> &str {
&self.fn_name
}
fn file_path(&self) -> Option<&str> {
self.file_path.as_deref()
}
}
static TEST_STORAGE: CrateStorage<TestEntry> = LazyLock::new(|| Mutex::new(HashMap::new()));
fn entry(fn_name: &str, file_path: &str, payload: &'static str) -> TestEntry {
TestEntry {
fn_name: fn_name.to_string(),
file_path: Some(file_path.to_string()),
payload,
}
}
fn payloads_for(fn_name: &str) -> Vec<&'static str> {
current_crate_items(&TEST_STORAGE)
.iter()
.filter(|found| found.fn_name == fn_name)
.map(|found| found.payload)
.collect()
}
#[test]
fn register_replaces_same_fn_with_separator_equivalent_path() {
register(
&TEST_STORAGE,
entry("__replace_me", r"C:\vespera\routes\a.rs", "before"),
);
register(
&TEST_STORAGE,
entry("__replace_me", "C:/vespera/routes/a.rs", "after"),
);
assert_eq!(payloads_for("__replace_me"), vec!["after"]);
}
#[test]
fn register_pushes_different_fn_name_at_same_path() {
register(
&TEST_STORAGE,
entry("__push_first", "C:/vespera/routes/b.rs", "first"),
);
register(
&TEST_STORAGE,
entry("__push_second", "C:/vespera/routes/b.rs", "second"),
);
assert_eq!(payloads_for("__push_first"), vec!["first"]);
assert_eq!(payloads_for("__push_second"), vec!["second"]);
}
#[test]
fn current_crate_items_is_empty_for_unused_storage() {
static EMPTY_STORAGE: CrateStorage<TestEntry> =
LazyLock::new(|| Mutex::new(HashMap::new()));
assert!(current_crate_items(&EMPTY_STORAGE).is_empty());
}
}