use neurogrim_core::registry::ScoringSourceConfig;
use neurogrim_core::scoring::CmdbData;
use neurogrim_core::scoring_source::ScoringSourceRegistry;
use neurogrim_core::scoring_sources::cmdb::CmdbSource;
use neurogrim_core::scoring_sources::function::FunctionSource;
use neurogrim_ecosystem::scoring_source::A2aSource;
use std::path::Path;
use std::sync::OnceLock;
pub enum BuiltinScoringSource {
Cmdb(CmdbSource),
A2a(A2aSource),
Function(FunctionSource),
}
impl BuiltinScoringSource {
pub fn from_source_type(source_type: &str) -> Option<Self> {
match source_type {
"cmdb" => Some(Self::Cmdb(CmdbSource)),
"a2a" => Some(Self::A2a(A2aSource)),
"function" => Some(Self::Function(FunctionSource)),
_ => None,
}
}
pub fn source_type_name(&self) -> &'static str {
match self {
Self::Cmdb(_) => "cmdb",
Self::A2a(_) => "a2a",
Self::Function(_) => "function",
}
}
pub async fn load(
&self,
domain_key: &str,
config: &ScoringSourceConfig,
project_root: &Path,
) -> Option<CmdbData> {
match self {
Self::Cmdb(s) => s.load_inherent(domain_key, config, project_root).await,
Self::A2a(s) => s.load_inherent(domain_key, config, project_root).await,
Self::Function(s) => s.load_inherent(domain_key, config, project_root).await,
}
}
}
static PLUGIN_REGISTRY: OnceLock<ScoringSourceRegistry> = OnceLock::new();
pub fn plugin_registry() -> &'static ScoringSourceRegistry {
PLUGIN_REGISTRY.get_or_init(ScoringSourceRegistry::new)
}
pub enum Dispatcher {
Builtin(BuiltinScoringSource),
Plugin(Box<dyn neurogrim_core::scoring_source::ScoringSource>),
}
impl Dispatcher {
pub fn for_source_type(source_type: &str) -> Option<Self> {
if let Some(builtin) = BuiltinScoringSource::from_source_type(source_type) {
return Some(Self::Builtin(builtin));
}
if let Some(plugin) = plugin_registry().build(source_type) {
return Some(Self::Plugin(plugin));
}
None
}
pub async fn load(
&self,
domain_key: &str,
config: &ScoringSourceConfig,
project_root: &Path,
) -> Option<CmdbData> {
match self {
Self::Builtin(b) => b.load(domain_key, config, project_root).await,
Self::Plugin(p) => p.load(domain_key, config, project_root).await,
}
}
}
pub fn is_registered(source_type: &str) -> bool {
BuiltinScoringSource::from_source_type(source_type).is_some()
|| plugin_registry().has(source_type)
}
pub fn all_registered_names() -> Vec<&'static str> {
let mut names = vec!["cmdb", "a2a", "function"];
names.extend(plugin_registry().registered_names().copied());
names
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_resolves_three_known_types() {
assert!(BuiltinScoringSource::from_source_type("cmdb").is_some());
assert!(BuiltinScoringSource::from_source_type("a2a").is_some());
assert!(BuiltinScoringSource::from_source_type("function").is_some());
}
#[test]
fn builtin_rejects_unknown_type() {
assert!(BuiltinScoringSource::from_source_type("not-a-type").is_none());
}
#[test]
fn builtin_source_type_name_matches_construction() {
let cmdb = BuiltinScoringSource::from_source_type("cmdb").unwrap();
assert_eq!(cmdb.source_type_name(), "cmdb");
let a2a = BuiltinScoringSource::from_source_type("a2a").unwrap();
assert_eq!(a2a.source_type_name(), "a2a");
let func = BuiltinScoringSource::from_source_type("function").unwrap();
assert_eq!(func.source_type_name(), "function");
}
#[test]
fn dispatcher_resolves_built_ins() {
assert!(matches!(
Dispatcher::for_source_type("cmdb"),
Some(Dispatcher::Builtin(_))
));
assert!(matches!(
Dispatcher::for_source_type("a2a"),
Some(Dispatcher::Builtin(_))
));
assert!(matches!(
Dispatcher::for_source_type("function"),
Some(Dispatcher::Builtin(_))
));
}
#[test]
fn dispatcher_returns_none_for_unknown() {
assert!(Dispatcher::for_source_type("totally-unknown").is_none());
}
#[test]
fn is_registered_covers_built_ins() {
assert!(is_registered("cmdb"));
assert!(is_registered("a2a"));
assert!(is_registered("function"));
assert!(!is_registered("not-a-thing"));
}
#[test]
fn all_registered_names_includes_three_builtins() {
let names = all_registered_names();
assert!(names.contains(&"cmdb"));
assert!(names.contains(&"a2a"));
assert!(names.contains(&"function"));
assert!(names.len() >= 3);
}
#[tokio::test]
async fn function_dispatch_returns_none() {
let dispatcher = Dispatcher::for_source_type("function").unwrap();
let config = ScoringSourceConfig {
source_type: "function".to_string(),
path: None,
endpoint: None,
interface_version: None,
score_field: None,
updated_at_field: None,
no_file_score: None,
};
let result = dispatcher
.load("test_domain", &config, Path::new("."))
.await;
assert!(result.is_none(), "function source is a no-op marker");
}
}