use std::{cell::OnceCell, ops::Deref, rc::Rc};
#[derive(Clone, Debug, PartialEq)]
pub enum ModuleError<DomainError, RuntimeError> {
Domain(DomainError),
Runtime(RuntimeError),
}
impl<DomainError, RuntimeError> ModuleError<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,
) -> ModuleError<Other, RuntimeError> {
match self {
Self::Domain(error) => ModuleError::Domain(map(error)),
Self::Runtime(error) => ModuleError::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 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 Module activation",
C::CAPABILITY_ID
)
})
}
}
pub mod prelude {
pub use crate::{CapabilityClient, ModuleError, 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
}
}
#[test]
fn port_connects_once_and_is_shared_by_module_clones() {
let port = Port::<ExampleClient>::new();
let module_clone = port.clone();
assert!(!port.is_connected());
port.connect(&())
.expect("the generated client should connect");
assert!(module_clone.is_connected());
assert_eq!(module_clone.0, 42);
assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
}
#[test]
fn module_error_preserves_runtime_failures_while_mapping_domain_errors() {
let domain = ModuleError::<_, &str>::domain("missing").map_domain(str::len);
assert_eq!(domain, ModuleError::Domain(7));
let runtime = ModuleError::<&str, _>::runtime("cancelled").map_domain(str::len);
assert_eq!(runtime, ModuleError::Runtime("cancelled"));
}
}