use dashmap::DashMap;
use std::sync::Arc;
use tower_lsp_server::ls_types::Uri;
use crate::Ecosystem;
pub struct EcosystemRegistry {
ecosystems: DashMap<&'static str, Arc<dyn Ecosystem>>,
filename_map: DashMap<&'static str, &'static str>,
}
impl EcosystemRegistry {
pub fn new() -> Self {
Self {
ecosystems: DashMap::new(),
filename_map: DashMap::new(),
}
}
pub fn register(&self, ecosystem: Arc<dyn Ecosystem>) {
let id = ecosystem.id();
for filename in ecosystem.manifest_filenames() {
self.filename_map.insert(*filename, id);
}
self.ecosystems.insert(id, ecosystem);
}
pub fn get(&self, id: &str) -> Option<Arc<dyn Ecosystem>> {
self.ecosystems.get(id).map(|e| Arc::clone(&e))
}
pub fn get_for_filename(&self, filename: &str) -> Option<Arc<dyn Ecosystem>> {
let id = self.filename_map.get(filename)?;
self.get(*id)
}
pub fn get_for_uri(&self, uri: &Uri) -> Option<Arc<dyn Ecosystem>> {
let path = uri.path().as_str();
let filename = path.rsplit('/').next()?;
self.get_for_filename(filename)
}
pub fn ecosystem_ids(&self) -> Vec<&'static str> {
self.ecosystems.iter().map(|e| *e.key()).collect()
}
pub fn get_for_lockfile(&self, filename: &str) -> Option<Arc<dyn Ecosystem>> {
for entry in self.ecosystems.iter() {
let ecosystem = entry.value();
if ecosystem.lockfile_filenames().contains(&filename) {
return Some(Arc::clone(ecosystem));
}
}
None
}
pub fn all_lockfile_patterns(&self) -> Vec<String> {
let mut patterns = Vec::new();
for entry in self.ecosystems.iter() {
let ecosystem = entry.value();
for filename in ecosystem.lockfile_filenames() {
patterns.push(format!("**/{}", filename));
}
}
patterns
}
}
impl Default for EcosystemRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::any::Any;
use tower_lsp_server::ls_types::{CompletionItem, Position};
use crate::{ParseResult, Registry, lsp_helpers::EcosystemFormatter};
struct MockFormatter;
impl EcosystemFormatter for MockFormatter {
fn format_version_for_text_edit(&self, version: &str) -> String {
version.to_string()
}
fn package_url(&self, name: &str) -> String {
format!("https://example.com/{name}")
}
}
struct MockEcosystem {
id: &'static str,
display_name: &'static str,
filenames: &'static [&'static str],
lockfiles: &'static [&'static str],
}
impl crate::ecosystem::private::Sealed for MockEcosystem {}
impl Ecosystem for MockEcosystem {
fn id(&self) -> &'static str {
self.id
}
fn display_name(&self) -> &'static str {
self.display_name
}
fn manifest_filenames(&self) -> &[&'static str] {
self.filenames
}
fn lockfile_filenames(&self) -> &[&'static str] {
self.lockfiles
}
fn parse_manifest<'a>(
&'a self,
_content: &'a str,
_uri: &'a Uri,
) -> crate::ecosystem::BoxFuture<'a, crate::error::Result<Box<dyn ParseResult>>> {
Box::pin(async move { unimplemented!() })
}
fn registry(&self) -> Arc<dyn Registry> {
unimplemented!()
}
fn formatter(&self) -> &dyn EcosystemFormatter {
&MockFormatter
}
fn generate_completions<'a>(
&'a self,
_parse_result: &'a dyn ParseResult,
_position: Position,
_content: &'a str,
) -> crate::ecosystem::BoxFuture<'a, Vec<CompletionItem>> {
Box::pin(async move { vec![] })
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_new_registry_is_empty() {
let registry = EcosystemRegistry::new();
assert_eq!(registry.ecosystem_ids().len(), 0);
}
#[test]
fn test_register_ecosystem() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "test",
display_name: "Test Ecosystem",
filenames: &["test.toml"],
lockfiles: &[],
});
registry.register(ecosystem);
assert_eq!(registry.ecosystem_ids().len(), 1);
assert!(registry.get("test").is_some());
}
#[test]
fn test_get_by_id() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "test",
display_name: "Test Ecosystem",
filenames: &["test.toml"],
lockfiles: &[],
});
registry.register(ecosystem);
let retrieved = registry.get("test").unwrap();
assert_eq!(retrieved.id(), "test");
assert_eq!(retrieved.display_name(), "Test Ecosystem");
}
#[test]
fn test_get_by_filename() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "test",
display_name: "Test Ecosystem",
filenames: &["test.toml", "test.json"],
lockfiles: &[],
});
registry.register(ecosystem);
let retrieved1 = registry.get_for_filename("test.toml").unwrap();
assert_eq!(retrieved1.id(), "test");
let retrieved2 = registry.get_for_filename("test.json").unwrap();
assert_eq!(retrieved2.id(), "test");
assert!(registry.get_for_filename("unknown.toml").is_none());
}
#[test]
fn test_get_by_uri() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "test",
display_name: "Test Ecosystem",
filenames: &["test.toml"],
lockfiles: &[],
});
registry.register(ecosystem);
let uri = Uri::from_file_path("/home/user/project/test.toml").unwrap();
let retrieved = registry.get_for_uri(&uri).unwrap();
assert_eq!(retrieved.id(), "test");
let unknown_uri = Uri::from_file_path("/home/user/project/unknown.toml").unwrap();
assert!(registry.get_for_uri(&unknown_uri).is_none());
}
#[test]
fn test_multiple_ecosystems() {
let registry = EcosystemRegistry::new();
let eco1 = Arc::new(MockEcosystem {
id: "cargo",
display_name: "Cargo",
filenames: &["Cargo.toml"],
lockfiles: &["Cargo.lock"],
});
let eco2 = Arc::new(MockEcosystem {
id: "npm",
display_name: "npm",
filenames: &["package.json"],
lockfiles: &["package-lock.json"],
});
registry.register(eco1);
registry.register(eco2);
assert_eq!(registry.ecosystem_ids().len(), 2);
assert_eq!(
registry.get_for_filename("Cargo.toml").unwrap().id(),
"cargo"
);
assert_eq!(
registry.get_for_filename("package.json").unwrap().id(),
"npm"
);
}
#[test]
fn test_get_for_lockfile() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "cargo",
display_name: "Cargo",
filenames: &["Cargo.toml"],
lockfiles: &["Cargo.lock"],
});
registry.register(ecosystem);
let retrieved = registry.get_for_lockfile("Cargo.lock").unwrap();
assert_eq!(retrieved.id(), "cargo");
assert_eq!(retrieved.display_name(), "Cargo");
assert!(registry.get_for_lockfile("unknown.lock").is_none());
}
#[test]
fn test_get_for_lockfile_multiple_lockfiles() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "pypi",
display_name: "PyPI",
filenames: &["pyproject.toml"],
lockfiles: &["poetry.lock", "uv.lock"],
});
registry.register(ecosystem);
let retrieved1 = registry.get_for_lockfile("poetry.lock").unwrap();
assert_eq!(retrieved1.id(), "pypi");
let retrieved2 = registry.get_for_lockfile("uv.lock").unwrap();
assert_eq!(retrieved2.id(), "pypi");
}
#[test]
fn test_all_lockfile_patterns_empty() {
let registry = EcosystemRegistry::new();
assert!(registry.all_lockfile_patterns().is_empty());
}
#[test]
fn test_all_lockfile_patterns_single_ecosystem() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "cargo",
display_name: "Cargo",
filenames: &["Cargo.toml"],
lockfiles: &["Cargo.lock"],
});
registry.register(ecosystem);
let patterns = registry.all_lockfile_patterns();
assert_eq!(patterns.len(), 1);
assert_eq!(patterns[0], "**/Cargo.lock");
}
#[test]
fn test_all_lockfile_patterns_multiple_ecosystems() {
let registry = EcosystemRegistry::new();
let eco1 = Arc::new(MockEcosystem {
id: "cargo",
display_name: "Cargo",
filenames: &["Cargo.toml"],
lockfiles: &["Cargo.lock"],
});
let eco2 = Arc::new(MockEcosystem {
id: "npm",
display_name: "npm",
filenames: &["package.json"],
lockfiles: &["package-lock.json"],
});
let eco3 = Arc::new(MockEcosystem {
id: "pypi",
display_name: "PyPI",
filenames: &["pyproject.toml"],
lockfiles: &["poetry.lock", "uv.lock"],
});
registry.register(eco1);
registry.register(eco2);
registry.register(eco3);
let patterns = registry.all_lockfile_patterns();
assert_eq!(patterns.len(), 4);
assert!(patterns.contains(&"**/Cargo.lock".to_string()));
assert!(patterns.contains(&"**/package-lock.json".to_string()));
assert!(patterns.contains(&"**/poetry.lock".to_string()));
assert!(patterns.contains(&"**/uv.lock".to_string()));
}
#[test]
fn test_all_lockfile_patterns_no_lockfiles() {
let registry = EcosystemRegistry::new();
let ecosystem = Arc::new(MockEcosystem {
id: "test",
display_name: "Test",
filenames: &["test.toml"],
lockfiles: &[],
});
registry.register(ecosystem);
let patterns = registry.all_lockfile_patterns();
assert!(patterns.is_empty());
}
}