use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use crate::application::projection::Projection;
use crate::domain::error::{WireError, WireResult};
use crate::infrastructure::adapter::Adapter;
use crate::infrastructure::template::TemplateEngine;
#[derive(Clone, Default)]
pub struct PluginRegistry {
adapters: HashMap<&'static str, Arc<dyn Adapter>>,
engines: HashMap<&'static str, Arc<dyn TemplateEngine>>,
projections: HashMap<&'static str, Arc<dyn Projection>>,
}
impl fmt::Debug for PluginRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PluginRegistry")
.field("schemes", &self.schemes())
.field("engine_ids", &self.engine_ids())
.field("projection_kinds", &self.projection_kinds())
.finish()
}
}
impl PluginRegistry {
pub fn builder() -> PluginRegistryBuilder {
PluginRegistryBuilder::default()
}
pub fn default_for_wire() -> WireResult<Self> {
use crate::application::projection::StaticProjection;
use crate::infrastructure::adapter::{FileAdapter, MiniAppAdapter};
use crate::infrastructure::template::HandlebarsEngine;
Self::builder()
.with_adapter(FileAdapter)
.with_adapter(MiniAppAdapter)
.with_engine(HandlebarsEngine::new())
.with_projection(StaticProjection::new())
.build()
}
pub fn adapter_for_uri(&self, source_uri: &str) -> Option<&Arc<dyn Adapter>> {
let scheme = source_uri.split_once(':').map(|(s, _)| s)?;
self.adapters.get(scheme)
}
pub fn adapter(&self, scheme: &str) -> Option<&Arc<dyn Adapter>> {
self.adapters.get(scheme)
}
pub fn engine(&self, id: &str) -> Option<&Arc<dyn TemplateEngine>> {
self.engines.get(id)
}
pub fn projection(&self, kind: &str) -> Option<&Arc<dyn Projection>> {
self.projections.get(kind)
}
pub fn schemes(&self) -> Vec<&'static str> {
let mut v: Vec<_> = self.adapters.keys().copied().collect();
v.sort_unstable();
v
}
pub fn engine_ids(&self) -> Vec<&'static str> {
let mut v: Vec<_> = self.engines.keys().copied().collect();
v.sort_unstable();
v
}
pub fn projection_kinds(&self) -> Vec<&'static str> {
let mut v: Vec<_> = self.projections.keys().copied().collect();
v.sort_unstable();
v
}
}
#[derive(Default)]
pub struct PluginRegistryBuilder {
adapters: Vec<Arc<dyn Adapter>>,
engines: Vec<Arc<dyn TemplateEngine>>,
projections: Vec<Arc<dyn Projection>>,
}
impl PluginRegistryBuilder {
pub fn with_adapter<A: Adapter + 'static>(mut self, adapter: A) -> Self {
self.adapters.push(Arc::new(adapter));
self
}
pub fn with_engine<E: TemplateEngine + 'static>(mut self, engine: E) -> Self {
self.engines.push(Arc::new(engine));
self
}
pub fn with_projection<P: Projection + 'static>(mut self, projection: P) -> Self {
self.projections.push(Arc::new(projection));
self
}
pub fn build(self) -> WireResult<PluginRegistry> {
let mut adapters = HashMap::new();
for a in self.adapters {
let scheme = a.scheme();
if adapters.insert(scheme, a).is_some() {
return Err(WireError::Storage(format!(
"plugin registry: duplicate adapter scheme `{scheme}`"
)));
}
}
let mut engines = HashMap::new();
for e in self.engines {
let id = e.id();
if engines.insert(id, e).is_some() {
return Err(WireError::Storage(format!(
"plugin registry: duplicate template engine id `{id}`"
)));
}
}
let mut projections = HashMap::new();
for p in self.projections {
let kind = p.kind();
if projections.insert(kind, p).is_some() {
return Err(WireError::Storage(format!(
"plugin registry: duplicate projection kind `{kind}`"
)));
}
}
Ok(PluginRegistry {
adapters,
engines,
projections,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::application::projection::StaticProjection;
use crate::infrastructure::adapter::{FileAdapter, MiniAppAdapter};
use crate::infrastructure::template::HandlebarsEngine;
#[test]
fn empty_registry_has_no_plugins() {
let reg = PluginRegistry::builder().build().unwrap();
assert!(reg.schemes().is_empty());
assert!(reg.engine_ids().is_empty());
assert!(reg.projection_kinds().is_empty());
}
#[test]
fn registers_all_three_axes() {
let reg = PluginRegistry::builder()
.with_adapter(FileAdapter)
.with_adapter(MiniAppAdapter)
.with_engine(HandlebarsEngine::new())
.with_projection(StaticProjection::new())
.build()
.unwrap();
assert_eq!(reg.schemes(), vec!["file", "mini-app"]);
assert_eq!(reg.engine_ids(), vec!["handlebars"]);
assert_eq!(reg.projection_kinds(), vec!["static"]);
}
#[test]
fn adapter_for_uri_dispatches_by_scheme() {
let reg = PluginRegistry::builder()
.with_adapter(FileAdapter)
.build()
.unwrap();
assert!(reg.adapter_for_uri("file:///tmp/x").is_some());
assert!(reg.adapter_for_uri("mini-app://x").is_none());
assert!(reg.adapter_for_uri("no-scheme").is_none());
}
#[test]
fn duplicate_scheme_fails_build() {
let err = PluginRegistry::builder()
.with_adapter(FileAdapter)
.with_adapter(FileAdapter)
.build()
.unwrap_err();
let msg = format!("{:?}", err);
assert!(msg.contains("duplicate adapter scheme"));
assert!(msg.contains("file"));
}
#[test]
fn duplicate_engine_fails_build() {
let err = PluginRegistry::builder()
.with_engine(HandlebarsEngine::new())
.with_engine(HandlebarsEngine::new())
.build()
.unwrap_err();
let msg = format!("{:?}", err);
assert!(msg.contains("duplicate template engine id"));
}
#[test]
fn duplicate_projection_fails_build() {
let err = PluginRegistry::builder()
.with_projection(StaticProjection::new())
.with_projection(StaticProjection::new())
.build()
.unwrap_err();
let msg = format!("{:?}", err);
assert!(msg.contains("duplicate projection kind"));
}
}