lenso_module_authoring/
lib.rs1use std::{cell::OnceCell, ops::Deref, rc::Rc};
4
5#[derive(Clone, Debug, PartialEq)]
11pub enum ModuleError<DomainError, RuntimeError> {
12 Domain(DomainError),
14 Runtime(RuntimeError),
16}
17
18impl<DomainError, RuntimeError> ModuleError<DomainError, RuntimeError> {
19 pub const fn domain(error: DomainError) -> Self {
21 Self::Domain(error)
22 }
23
24 pub const fn runtime(error: RuntimeError) -> Self {
26 Self::Runtime(error)
27 }
28
29 pub fn map_domain<Other>(
31 self,
32 map: impl FnOnce(DomainError) -> Other,
33 ) -> ModuleError<Other, RuntimeError> {
34 match self {
35 Self::Domain(error) => ModuleError::Domain(map(error)),
36 Self::Runtime(error) => ModuleError::Runtime(error),
37 }
38 }
39}
40
41pub trait CapabilityClient: Sized + 'static {
48 type Dependencies: ?Sized;
50 type Error;
52
53 const CAPABILITY_ID: &'static str;
55 const DESCRIPTOR_VERSION: &'static str;
57
58 fn from_dependencies(dependencies: &Self::Dependencies) -> Result<Self, Self::Error>;
60
61 fn already_connected() -> Self::Error;
63}
64
65pub struct Port<C: CapabilityClient> {
72 client: Rc<OnceCell<C>>,
73}
74
75impl<C: CapabilityClient> Port<C> {
76 #[must_use]
78 pub fn new() -> Self {
79 Self {
80 client: Rc::new(OnceCell::new()),
81 }
82 }
83
84 pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
86 let client = C::from_dependencies(dependencies)?;
87 self.client.set(client).map_err(|_| C::already_connected())
88 }
89
90 #[must_use]
92 pub fn is_connected(&self) -> bool {
93 self.client.get().is_some()
94 }
95}
96
97impl<C: CapabilityClient> Clone for Port<C> {
98 fn clone(&self) -> Self {
99 Self {
100 client: Rc::clone(&self.client),
101 }
102 }
103}
104
105impl<C: CapabilityClient> std::fmt::Debug for Port<C> {
106 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 formatter
108 .debug_struct("Port")
109 .field("capability_id", &C::CAPABILITY_ID)
110 .field("descriptor_version", &C::DESCRIPTOR_VERSION)
111 .field("connected", &self.is_connected())
112 .finish()
113 }
114}
115
116impl<C: CapabilityClient> Default for Port<C> {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl<C: CapabilityClient> Deref for Port<C> {
123 type Target = C;
124
125 fn deref(&self) -> &Self::Target {
126 self.client.get().unwrap_or_else(|| {
127 panic!(
128 "Capability Port {} was used before Module activation",
129 C::CAPABILITY_ID
130 )
131 })
132 }
133}
134
135pub mod prelude {
137 pub use crate::{CapabilityClient, ModuleError, Port};
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[derive(Debug, Eq, PartialEq)]
145 struct ExampleClient(u64);
146
147 #[derive(Debug, Eq, PartialEq)]
148 enum ExampleError {
149 AlreadyConnected,
150 }
151
152 impl CapabilityClient for ExampleClient {
153 type Dependencies = ();
154 type Error = ExampleError;
155
156 const CAPABILITY_ID: &'static str = "example.echo@1";
157 const DESCRIPTOR_VERSION: &'static str = "1.0.0";
158
159 fn from_dependencies(_dependencies: &Self::Dependencies) -> Result<Self, Self::Error> {
160 Ok(Self(42))
161 }
162
163 fn already_connected() -> Self::Error {
164 ExampleError::AlreadyConnected
165 }
166 }
167
168 #[test]
169 fn port_connects_once_and_is_shared_by_module_clones() {
170 let port = Port::<ExampleClient>::new();
171 let module_clone = port.clone();
172 assert!(!port.is_connected());
173
174 port.connect(&())
175 .expect("the generated client should connect");
176
177 assert!(module_clone.is_connected());
178 assert_eq!(module_clone.0, 42);
179 assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
180 }
181
182 #[test]
183 fn module_error_preserves_runtime_failures_while_mapping_domain_errors() {
184 let domain = ModuleError::<_, &str>::domain("missing").map_domain(str::len);
185 assert_eq!(domain, ModuleError::Domain(7));
186
187 let runtime = ModuleError::<&str, _>::runtime("cancelled").map_domain(str::len);
188 assert_eq!(runtime, ModuleError::Runtime("cancelled"));
189 }
190}