#[cfg(test)]
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use wm_core::{Gana, Tool};
struct RegistryInner {
tools: Vec<Arc<dyn Tool>>,
by_gana: HashMap<Gana, Vec<usize>>,
by_name: HashMap<String, usize>,
}
pub struct ToolRegistry {
inner: Arc<RegistryInner>,
}
impl ToolRegistry {
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(RegistryInner {
tools: vec![],
by_gana: HashMap::new(),
by_name: HashMap::new(),
}),
}
}
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
inner: Arc::new(RegistryInner {
tools: Vec::with_capacity(cap),
by_gana: HashMap::new(),
by_name: HashMap::new(),
}),
}
}
#[must_use]
pub fn register(&self, tool: Arc<dyn Tool>) -> Self {
let mut inner = RegistryInner {
tools: self.inner.tools.clone(),
by_gana: self.inner.by_gana.clone(),
by_name: self.inner.by_name.clone(),
};
let idx = inner.tools.len();
let gana = tool.gana();
let name = tool.name().to_string();
inner.by_gana.entry(gana).or_default().push(idx);
inner.by_name.insert(name, idx);
inner.tools.push(tool);
Self {
inner: Arc::new(inner),
}
}
#[must_use]
pub fn get(&self, name: &str) -> Option<Arc<dyn Tool>> {
self.inner
.by_name
.get(name)
.map(|&i| Arc::clone(&self.inner.tools[i]))
}
#[must_use]
pub fn by_gana(&self, gana: Gana) -> Vec<Arc<dyn Tool>> {
self.inner
.by_gana
.get(&gana)
.map(|indices| {
indices
.iter()
.map(|&i| Arc::clone(&self.inner.tools[i]))
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn all(&self) -> Vec<Arc<dyn Tool>> {
self.inner.tools.clone()
}
#[must_use]
pub fn all_ref(&self) -> &[Arc<dyn Tool>] {
&self.inner.tools
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.tools.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.tools.is_empty()
}
#[must_use]
pub fn available_in(&self, brain_wave: wm_core::BrainWave) -> Vec<Arc<dyn Tool>> {
self.inner
.tools
.iter()
.filter(|t| t.effects().is_available_in(brain_wave))
.cloned()
.collect()
}
#[must_use]
pub fn available_count(&self, brain_wave: wm_core::BrainWave) -> usize {
self.inner
.tools
.iter()
.filter(|t| t.effects().is_available_in(brain_wave))
.count()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
impl Clone for ToolRegistry {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
pub struct ToolRegistryBuilder {
tools: Vec<Arc<dyn Tool>>,
by_gana: HashMap<Gana, Vec<usize>>,
by_name: HashMap<String, usize>,
}
impl ToolRegistryBuilder {
#[must_use]
pub fn new() -> Self {
Self {
tools: vec![],
by_gana: HashMap::new(),
by_name: HashMap::new(),
}
}
pub fn register(&mut self, tool: Arc<dyn Tool>) -> &mut Self {
let idx = self.tools.len();
let gana = tool.gana();
let name = tool.name().to_string();
if self.by_name.contains_key(&name) {
tracing::warn!(
tool_name = %name,
"Duplicate tool registration — new tool will shadow existing one"
);
}
self.by_gana.entry(gana).or_default().push(idx);
self.by_name.insert(name, idx);
self.tools.push(tool);
self
}
#[must_use]
pub fn build(self) -> ToolRegistry {
ToolRegistry {
inner: Arc::new(RegistryInner {
tools: self.tools,
by_gana: self.by_gana,
by_name: self.by_name,
}),
}
}
}
impl Default for ToolRegistryBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use wm_core::{EffectRow, ToolStats};
struct MockTool {
name: String,
gana: Gana,
effects: EffectRow,
stats: ToolStats,
}
#[async_trait]
impl Tool for MockTool {
fn name(&self) -> &str {
&self.name
}
fn gana(&self) -> Gana {
self.gana
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn stats(&self) -> &ToolStats {
&self.stats
}
async fn call(
&self,
_ctx: &mut wm_core::Context,
_args: wm_core::Args,
) -> wm_core::Result<wm_core::Output> {
Ok(serde_json::json!({"ok": true}))
}
}
fn make_tool(name: &str) -> Arc<dyn Tool> {
Arc::new(MockTool {
name: name.into(),
gana: Gana::Horn,
effects: EffectRow::default(),
stats: ToolStats::default(),
})
}
#[test]
fn duplicate_registration_shadows_and_warns() {
let mut builder = ToolRegistryBuilder::new();
builder.register(make_tool("dup"));
builder.register(make_tool("dup"));
let registry = builder.build();
let tool = registry.get("dup").unwrap();
assert_eq!(tool.name(), "dup");
}
#[test]
fn unique_registration_no_warning() {
let mut builder = ToolRegistryBuilder::new();
builder.register(make_tool("tool_a"));
builder.register(make_tool("tool_b"));
let registry = builder.build();
assert!(registry.get("tool_a").is_some());
assert!(registry.get("tool_b").is_some());
}
}