use svc_registry::{Named, Registry};
use svc_registry_adapter::DefaultRegistry;
use wasm_capability_contract::{CapabilityDescriptor, CapabilityProtocol, CapabilityRegistry};
struct NamedCapabilityDescriptor {
key: String,
descriptor: CapabilityDescriptor,
}
impl Named for NamedCapabilityDescriptor {
fn name(&self) -> &str {
&self.key
}
}
pub struct InMemoryCapabilityRegistry {
entries: DefaultRegistry<NamedCapabilityDescriptor>,
}
impl InMemoryCapabilityRegistry {
#[must_use]
pub fn with_defaults() -> Self {
let mut entries = DefaultRegistry::default();
for (name, protocol) in [
("http-egress", CapabilityProtocol::Http),
("grpc-egress", CapabilityProtocol::Grpc),
("llm-complete", CapabilityProtocol::Complete),
("mcp-egress", CapabilityProtocol::Mcp),
("database", CapabilityProtocol::Database),
("secrets", CapabilityProtocol::Secrets),
] {
entries.register(NamedCapabilityDescriptor {
key: name.to_string(),
descriptor: CapabilityDescriptor {
import_name: name,
protocol,
},
});
}
Self { entries }
}
#[must_use]
pub fn register(mut self, name: impl Into<String>, descriptor: CapabilityDescriptor) -> Self {
self.entries.register(NamedCapabilityDescriptor {
key: name.into(),
descriptor,
});
self
}
}
impl CapabilityRegistry for InMemoryCapabilityRegistry {
fn descriptor(&self, capability: &str) -> Option<&CapabilityDescriptor> {
self.entries.get(capability).map(|entry| &entry.descriptor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_defaults_resolves_all_six_built_in_capabilities_to_correct_protocol() {
let registry = InMemoryCapabilityRegistry::with_defaults();
let expected = [
("http-egress", CapabilityProtocol::Http),
("grpc-egress", CapabilityProtocol::Grpc),
("llm-complete", CapabilityProtocol::Complete),
("mcp-egress", CapabilityProtocol::Mcp),
("database", CapabilityProtocol::Database),
("secrets", CapabilityProtocol::Secrets),
];
for (name, protocol) in expected {
let descriptor = registry
.descriptor(name)
.unwrap_or_else(|| panic!("expected a descriptor for '{name}'"));
assert_eq!(descriptor.import_name, name);
assert_eq!(descriptor.protocol, protocol);
}
}
#[test]
fn test_descriptor_returns_none_for_unregistered_capability() {
let registry = InMemoryCapabilityRegistry::with_defaults();
assert!(registry.descriptor("not-a-real-capability").is_none());
}
#[test]
fn test_register_adds_new_entry_without_disturbing_defaults() {
let registry = InMemoryCapabilityRegistry::with_defaults().register(
"grpc-egress:inventory-svc",
CapabilityDescriptor {
import_name: "grpc-egress",
protocol: CapabilityProtocol::Grpc,
},
);
let scoped = registry
.descriptor("grpc-egress:inventory-svc")
.unwrap_or_else(|| panic!("expected the newly registered instance"));
assert_eq!(scoped.import_name, "grpc-egress");
let default = registry
.descriptor("grpc-egress")
.unwrap_or_else(|| panic!("expected the default 'grpc-egress' to still resolve"));
assert_eq!(default.protocol, CapabilityProtocol::Grpc);
for name in [
"http-egress",
"llm-complete",
"mcp-egress",
"database",
"secrets",
] {
assert!(
registry.descriptor(name).is_some(),
"expected default '{name}' to still resolve after register()"
);
}
}
#[test]
fn test_register_replaces_existing_entry_with_same_key() {
let registry = InMemoryCapabilityRegistry::with_defaults().register(
"http-egress",
CapabilityDescriptor {
import_name: "http-egress-v2",
protocol: CapabilityProtocol::Http,
},
);
let descriptor = registry
.descriptor("http-egress")
.unwrap_or_else(|| panic!("expected 'http-egress' to still resolve"));
assert_eq!(descriptor.import_name, "http-egress-v2");
}
}