use std::collections::{BTreeMap, HashSet};
use crate::types::{DiscoveredPlugin, PluginSource, TrustTier};
pub fn merge(
items: Vec<DiscoveredPlugin>,
official_owners: &[String],
indexed_names: &HashSet<String>,
) -> Vec<DiscoveredPlugin> {
let official_set: HashSet<String> = official_owners
.iter()
.map(|o| o.to_ascii_lowercase())
.collect();
let mut by_name: BTreeMap<String, DiscoveredPlugin> = BTreeMap::new();
for item in items.into_iter() {
match by_name.get_mut(&item.name) {
Some(existing) => {
for src in item.sources.iter() {
if !existing.sources.iter().any(|s| same_source_kind(s, src)) {
existing.sources.push(src.clone());
}
}
if existing.description.is_none() && item.description.is_some() {
existing.description = item.description;
}
if existing.homepage.is_none() && item.homepage.is_some() {
existing.homepage = item.homepage;
}
if existing.repo_url.is_none() && item.repo_url.is_some() {
existing.repo_url = item.repo_url;
}
if existing.manifest_url.is_none() && item.manifest_url.is_some() {
existing.manifest_url = item.manifest_url;
}
if existing.version.is_none() && item.version.is_some() {
existing.version = item.version.clone();
if let Some(v) = item.version.as_deref() {
existing.install_params.version = Some(v.to_string());
existing.install_cmd =
format!("cargo install {} --version {v}", existing.name);
}
}
for tag in item.tags.into_iter() {
if !existing.tags.iter().any(|t| t == &tag) {
existing.tags.push(tag);
}
}
}
None => {
by_name.insert(item.name.clone(), item);
}
}
}
for plugin in by_name.values_mut() {
let owner_lower = plugin.owner.to_ascii_lowercase();
if official_set.contains(&owner_lower) {
plugin.trust_tier = TrustTier::Official;
} else if indexed_names.contains(&plugin.name) {
if matches!(plugin.trust_tier, TrustTier::Unverified) {
plugin.trust_tier = TrustTier::CommunityIndexed;
}
}
}
by_name.into_values().collect()
}
fn same_source_kind(a: &PluginSource, b: &PluginSource) -> bool {
matches!(
(a, b),
(PluginSource::CratesIo, PluginSource::CratesIo)
| (PluginSource::CuratedIndex, PluginSource::CuratedIndex)
| (
PluginSource::GithubTopic { .. },
PluginSource::GithubTopic { .. }
)
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{CompatStatus, PluginCategory};
use nexo_tool_meta::admin::plugin_install::{InstallSource, PluginsInstallParams};
fn stub(name: &str, owner: &str, source: PluginSource, trust: TrustTier) -> DiscoveredPlugin {
DiscoveredPlugin {
name: name.into(),
version: Some("0.1.0".into()),
description: None,
owner: owner.into(),
sources: vec![source],
repo_url: None,
homepage: None,
tags: vec![],
category: PluginCategory::Unknown,
trust_tier: trust,
compat: CompatStatus::Unknown,
manifest_url: None,
install_cmd: format!("cargo install {name} --version 0.1.0"),
install_params: PluginsInstallParams {
crate_name: name.into(),
version: Some("0.1.0".into()),
repo: None,
source: InstallSource::Release,
force: false,
require_signature: false,
skip_signature_verify: false,
},
}
}
fn owners() -> Vec<String> {
vec!["lordmacu".to_string(), "nexo-rs".to_string()]
}
fn no_index() -> HashSet<String> {
HashSet::new()
}
#[test]
fn same_crate_two_sources_dedup_with_combined_badges() {
let a = stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::CratesIo,
TrustTier::Unverified,
);
let b = stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::GithubTopic {
repo: "lordmacu/nexo-rs-plugin-foo".into(),
},
TrustTier::Unverified,
);
let merged = merge(vec![a, b], &owners(), &no_index());
assert_eq!(merged.len(), 1);
let entry = &merged[0];
assert!(entry
.sources
.iter()
.any(|s| matches!(s, PluginSource::CratesIo)));
assert!(entry
.sources
.iter()
.any(|s| matches!(s, PluginSource::GithubTopic { .. })));
}
#[test]
fn owner_in_allowlist_promotes_to_official() {
let a = stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::CratesIo,
TrustTier::Unverified,
);
let merged = merge(vec![a], &owners(), &no_index());
assert_eq!(merged[0].trust_tier, TrustTier::Official);
}
#[test]
fn curated_index_entry_promotes_to_community_indexed() {
let a = stub(
"nexo-plugin-foo",
"communitydev",
PluginSource::CratesIo,
TrustTier::Unverified,
);
let mut indexed = HashSet::new();
indexed.insert("nexo-plugin-foo".to_string());
let merged = merge(vec![a], &owners(), &indexed);
assert_eq!(merged[0].trust_tier, TrustTier::CommunityIndexed);
}
#[test]
fn curated_only_owner_unknown_stays_community_indexed() {
let a = stub(
"nexo-plugin-foo",
"communitydev",
PluginSource::CuratedIndex,
TrustTier::CommunityIndexed,
);
let merged = merge(vec![a], &owners(), &no_index());
assert_eq!(merged[0].trust_tier, TrustTier::CommunityIndexed);
}
#[test]
fn allowlist_wins_over_curated_index() {
let a = stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::CuratedIndex,
TrustTier::CommunityIndexed,
);
let mut indexed = HashSet::new();
indexed.insert("nexo-plugin-foo".to_string());
let merged = merge(vec![a], &owners(), &indexed);
assert_eq!(merged[0].trust_tier, TrustTier::Official);
}
#[test]
fn unknown_crate_no_allowlist_stays_unverified() {
let a = stub(
"stranger-plugin",
"stranger",
PluginSource::CratesIo,
TrustTier::Unverified,
);
let merged = merge(vec![a], &owners(), &no_index());
assert_eq!(merged[0].trust_tier, TrustTier::Unverified);
}
#[test]
fn merge_fills_missing_version_from_second_source() {
let github = DiscoveredPlugin {
version: None,
..stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::GithubTopic {
repo: "lordmacu/nexo-rs-plugin-foo".into(),
},
TrustTier::Unverified,
)
};
let crates_io = stub(
"nexo-plugin-foo",
"lordmacu",
PluginSource::CratesIo,
TrustTier::Unverified,
);
let merged = merge(vec![github, crates_io], &owners(), &no_index());
assert_eq!(merged[0].version.as_deref(), Some("0.1.0"));
assert!(merged[0].install_cmd.contains("0.1.0"));
assert_eq!(merged[0].install_params.version.as_deref(), Some("0.1.0"));
}
}