use std::{cell::OnceCell, ops::Deref, rc::Rc};
#[derive(Clone, Debug, PartialEq)]
pub enum PluginError<DomainError, RuntimeError> {
Domain(DomainError),
Runtime(RuntimeError),
}
impl<DomainError, RuntimeError> PluginError<DomainError, RuntimeError> {
pub const fn domain(error: DomainError) -> Self {
Self::Domain(error)
}
pub const fn runtime(error: RuntimeError) -> Self {
Self::Runtime(error)
}
pub fn map_domain<Other>(
self,
map: impl FnOnce(DomainError) -> Other,
) -> PluginError<Other, RuntimeError> {
match self {
Self::Domain(error) => PluginError::Domain(map(error)),
Self::Runtime(error) => PluginError::Runtime(error),
}
}
}
pub trait CapabilityClient: Sized + 'static {
type Dependencies: ?Sized;
type Error;
const CAPABILITY_ID: &'static str;
const DESCRIPTOR_VERSION: &'static str;
fn from_dependencies(dependencies: &Self::Dependencies) -> Result<Self, Self::Error>;
fn already_connected() -> Self::Error;
}
pub trait CapabilityClientMany: CapabilityClient {
fn many_from_dependencies(
dependencies: &Self::Dependencies,
) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error>;
}
#[derive(Debug)]
pub struct BoundCapabilityClient<C> {
provider_instance: String,
client: C,
}
impl<C> BoundCapabilityClient<C> {
#[must_use]
pub fn new(provider_instance: impl Into<String>, client: C) -> Self {
Self {
provider_instance: provider_instance.into(),
client,
}
}
#[must_use]
pub fn provider_instance(&self) -> &str {
&self.provider_instance
}
#[must_use]
pub const fn client(&self) -> &C {
&self.client
}
}
impl<C> Deref for BoundCapabilityClient<C> {
type Target = C;
fn deref(&self) -> &Self::Target {
&self.client
}
}
pub struct Port<C: CapabilityClient> {
client: Rc<OnceCell<C>>,
}
impl<C: CapabilityClient> Port<C> {
#[must_use]
pub fn new() -> Self {
Self {
client: Rc::new(OnceCell::new()),
}
}
pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
let client = C::from_dependencies(dependencies)?;
self.client.set(client).map_err(|_| C::already_connected())
}
#[must_use]
pub fn is_connected(&self) -> bool {
self.client.get().is_some()
}
}
impl<C: CapabilityClient> Clone for Port<C> {
fn clone(&self) -> Self {
Self {
client: Rc::clone(&self.client),
}
}
}
impl<C: CapabilityClient> std::fmt::Debug for Port<C> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Port")
.field("capability_id", &C::CAPABILITY_ID)
.field("descriptor_version", &C::DESCRIPTOR_VERSION)
.field("connected", &self.is_connected())
.finish()
}
}
impl<C: CapabilityClient> Default for Port<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: CapabilityClient> Deref for Port<C> {
type Target = C;
fn deref(&self) -> &Self::Target {
self.client.get().unwrap_or_else(|| {
panic!(
"Capability Port {} was used before Plugin activation",
C::CAPABILITY_ID
)
})
}
}
pub struct ManyPort<C: CapabilityClientMany> {
clients: Rc<OnceCell<Vec<BoundCapabilityClient<C>>>>,
}
impl<C: CapabilityClientMany> ManyPort<C> {
#[must_use]
pub fn new() -> Self {
Self {
clients: Rc::new(OnceCell::new()),
}
}
pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
let clients = C::many_from_dependencies(dependencies)?;
self.clients
.set(clients)
.map_err(|_| C::already_connected())
}
#[must_use]
pub fn is_connected(&self) -> bool {
self.clients.get().is_some()
}
}
impl<C: CapabilityClientMany> Clone for ManyPort<C> {
fn clone(&self) -> Self {
Self {
clients: Rc::clone(&self.clients),
}
}
}
impl<C: CapabilityClientMany> std::fmt::Debug for ManyPort<C> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ManyPort")
.field("capability_id", &C::CAPABILITY_ID)
.field("descriptor_version", &C::DESCRIPTOR_VERSION)
.field("connected", &self.is_connected())
.field("provider_count", &self.clients.get().map(Vec::len))
.finish()
}
}
impl<C: CapabilityClientMany> Default for ManyPort<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: CapabilityClientMany> Deref for ManyPort<C> {
type Target = [BoundCapabilityClient<C>];
fn deref(&self) -> &Self::Target {
self.clients.get().map_or_else(
|| {
panic!(
"Capability ManyPort {} was used before Plugin activation",
C::CAPABILITY_ID
)
},
Vec::as_slice,
)
}
}
pub mod prelude {
pub use crate::{
BoundCapabilityClient, CapabilityClient, CapabilityClientMany, ManyPort, PluginError, Port,
};
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Eq, PartialEq)]
struct ExampleClient(u64);
#[derive(Debug, Eq, PartialEq)]
enum ExampleError {
AlreadyConnected,
}
impl CapabilityClient for ExampleClient {
type Dependencies = ();
type Error = ExampleError;
const CAPABILITY_ID: &'static str = "example.echo@1";
const DESCRIPTOR_VERSION: &'static str = "1.0.0";
fn from_dependencies(_dependencies: &Self::Dependencies) -> Result<Self, Self::Error> {
Ok(Self(42))
}
fn already_connected() -> Self::Error {
ExampleError::AlreadyConnected
}
}
impl CapabilityClientMany for ExampleClient {
fn many_from_dependencies(
_dependencies: &Self::Dependencies,
) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
Ok(vec![
BoundCapabilityClient::new("alpha", Self(1)),
BoundCapabilityClient::new("beta", Self(2)),
])
}
}
#[test]
fn port_connects_once_and_is_shared_by_plugin_clones() {
let port = Port::<ExampleClient>::new();
let plugin_clone = port.clone();
assert!(!port.is_connected());
port.connect(&())
.expect("the generated client should connect");
assert!(plugin_clone.is_connected());
assert_eq!(plugin_clone.0, 42);
assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
}
#[test]
fn many_port_preserves_provider_identity_and_resolved_order() {
let port = ManyPort::<ExampleClient>::new();
let plugin_clone = port.clone();
assert!(!port.is_connected());
port.connect(&())
.expect("the generated clients should connect");
assert!(plugin_clone.is_connected());
assert_eq!(plugin_clone[0].provider_instance(), "alpha");
assert_eq!(plugin_clone[0].client().0, 1);
assert_eq!(plugin_clone[1].provider_instance(), "beta");
assert_eq!(plugin_clone[1].client().0, 2);
assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
}
#[test]
fn plugin_error_preserves_runtime_failures_while_mapping_domain_errors() {
let domain = PluginError::<_, &str>::domain("missing").map_domain(str::len);
assert_eq!(domain, PluginError::Domain(7));
let runtime = PluginError::<&str, _>::runtime("cancelled").map_domain(str::len);
assert_eq!(runtime, PluginError::Runtime("cancelled"));
}
}