lenso_plugin_authoring/
lib.rs1use std::{cell::OnceCell, ops::Deref, rc::Rc};
4
5#[derive(Clone, Debug, PartialEq)]
11pub enum PluginError<DomainError, RuntimeError> {
12 Domain(DomainError),
14 Runtime(RuntimeError),
16}
17
18impl<DomainError, RuntimeError> PluginError<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 ) -> PluginError<Other, RuntimeError> {
34 match self {
35 Self::Domain(error) => PluginError::Domain(map(error)),
36 Self::Runtime(error) => PluginError::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 trait CapabilityClientMany: CapabilityClient {
68 fn many_from_dependencies(
70 dependencies: &Self::Dependencies,
71 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error>;
72}
73
74#[derive(Debug)]
76pub struct BoundCapabilityClient<C> {
77 provider_instance: String,
78 client: C,
79}
80
81impl<C> BoundCapabilityClient<C> {
82 #[must_use]
84 pub fn new(provider_instance: impl Into<String>, client: C) -> Self {
85 Self {
86 provider_instance: provider_instance.into(),
87 client,
88 }
89 }
90
91 #[must_use]
93 pub fn provider_instance(&self) -> &str {
94 &self.provider_instance
95 }
96
97 #[must_use]
99 pub const fn client(&self) -> &C {
100 &self.client
101 }
102}
103
104impl<C> Deref for BoundCapabilityClient<C> {
105 type Target = C;
106
107 fn deref(&self) -> &Self::Target {
108 &self.client
109 }
110}
111
112pub struct Port<C: CapabilityClient> {
119 client: Rc<OnceCell<C>>,
120}
121
122impl<C: CapabilityClient> Port<C> {
123 #[must_use]
125 pub fn new() -> Self {
126 Self {
127 client: Rc::new(OnceCell::new()),
128 }
129 }
130
131 pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
133 let client = C::from_dependencies(dependencies)?;
134 self.client.set(client).map_err(|_| C::already_connected())
135 }
136
137 #[must_use]
139 pub fn is_connected(&self) -> bool {
140 self.client.get().is_some()
141 }
142}
143
144impl<C: CapabilityClient> Clone for Port<C> {
145 fn clone(&self) -> Self {
146 Self {
147 client: Rc::clone(&self.client),
148 }
149 }
150}
151
152impl<C: CapabilityClient> std::fmt::Debug for Port<C> {
153 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 formatter
155 .debug_struct("Port")
156 .field("capability_id", &C::CAPABILITY_ID)
157 .field("descriptor_version", &C::DESCRIPTOR_VERSION)
158 .field("connected", &self.is_connected())
159 .finish()
160 }
161}
162
163impl<C: CapabilityClient> Default for Port<C> {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl<C: CapabilityClient> Deref for Port<C> {
170 type Target = C;
171
172 fn deref(&self) -> &Self::Target {
173 self.client.get().unwrap_or_else(|| {
174 panic!(
175 "Capability Port {} was used before Plugin activation",
176 C::CAPABILITY_ID
177 )
178 })
179 }
180}
181
182pub struct ManyPort<C: CapabilityClientMany> {
187 clients: Rc<OnceCell<Vec<BoundCapabilityClient<C>>>>,
188}
189
190impl<C: CapabilityClientMany> ManyPort<C> {
191 #[must_use]
193 pub fn new() -> Self {
194 Self {
195 clients: Rc::new(OnceCell::new()),
196 }
197 }
198
199 pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
201 let clients = C::many_from_dependencies(dependencies)?;
202 self.clients
203 .set(clients)
204 .map_err(|_| C::already_connected())
205 }
206
207 #[must_use]
209 pub fn is_connected(&self) -> bool {
210 self.clients.get().is_some()
211 }
212}
213
214impl<C: CapabilityClientMany> Clone for ManyPort<C> {
215 fn clone(&self) -> Self {
216 Self {
217 clients: Rc::clone(&self.clients),
218 }
219 }
220}
221
222impl<C: CapabilityClientMany> std::fmt::Debug for ManyPort<C> {
223 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 formatter
225 .debug_struct("ManyPort")
226 .field("capability_id", &C::CAPABILITY_ID)
227 .field("descriptor_version", &C::DESCRIPTOR_VERSION)
228 .field("connected", &self.is_connected())
229 .field("provider_count", &self.clients.get().map(Vec::len))
230 .finish()
231 }
232}
233
234impl<C: CapabilityClientMany> Default for ManyPort<C> {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240impl<C: CapabilityClientMany> Deref for ManyPort<C> {
241 type Target = [BoundCapabilityClient<C>];
242
243 fn deref(&self) -> &Self::Target {
244 self.clients.get().map_or_else(
245 || {
246 panic!(
247 "Capability ManyPort {} was used before Plugin activation",
248 C::CAPABILITY_ID
249 )
250 },
251 Vec::as_slice,
252 )
253 }
254}
255
256pub mod prelude {
258 pub use crate::{
259 BoundCapabilityClient, CapabilityClient, CapabilityClientMany, ManyPort, PluginError, Port,
260 };
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[derive(Debug, Eq, PartialEq)]
268 struct ExampleClient(u64);
269
270 #[derive(Debug, Eq, PartialEq)]
271 enum ExampleError {
272 AlreadyConnected,
273 }
274
275 impl CapabilityClient for ExampleClient {
276 type Dependencies = ();
277 type Error = ExampleError;
278
279 const CAPABILITY_ID: &'static str = "example.echo@1";
280 const DESCRIPTOR_VERSION: &'static str = "1.0.0";
281
282 fn from_dependencies(_dependencies: &Self::Dependencies) -> Result<Self, Self::Error> {
283 Ok(Self(42))
284 }
285
286 fn already_connected() -> Self::Error {
287 ExampleError::AlreadyConnected
288 }
289 }
290
291 impl CapabilityClientMany for ExampleClient {
292 fn many_from_dependencies(
293 _dependencies: &Self::Dependencies,
294 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
295 Ok(vec![
296 BoundCapabilityClient::new("alpha", Self(1)),
297 BoundCapabilityClient::new("beta", Self(2)),
298 ])
299 }
300 }
301
302 #[test]
303 fn port_connects_once_and_is_shared_by_plugin_clones() {
304 let port = Port::<ExampleClient>::new();
305 let plugin_clone = port.clone();
306 assert!(!port.is_connected());
307
308 port.connect(&())
309 .expect("the generated client should connect");
310
311 assert!(plugin_clone.is_connected());
312 assert_eq!(plugin_clone.0, 42);
313 assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
314 }
315
316 #[test]
317 fn many_port_preserves_provider_identity_and_resolved_order() {
318 let port = ManyPort::<ExampleClient>::new();
319 let plugin_clone = port.clone();
320 assert!(!port.is_connected());
321
322 port.connect(&())
323 .expect("the generated clients should connect");
324
325 assert!(plugin_clone.is_connected());
326 assert_eq!(plugin_clone[0].provider_instance(), "alpha");
327 assert_eq!(plugin_clone[0].client().0, 1);
328 assert_eq!(plugin_clone[1].provider_instance(), "beta");
329 assert_eq!(plugin_clone[1].client().0, 2);
330 assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
331 }
332
333 #[test]
334 fn plugin_error_preserves_runtime_failures_while_mapping_domain_errors() {
335 let domain = PluginError::<_, &str>::domain("missing").map_domain(str::len);
336 assert_eq!(domain, PluginError::Domain(7));
337
338 let runtime = PluginError::<&str, _>::runtime("cancelled").map_domain(str::len);
339 assert_eq!(runtime, PluginError::Runtime("cancelled"));
340 }
341}