1mod managed_tasks;
4
5use std::{collections::BTreeMap, rc::Rc};
6
7#[doc(hidden)]
8pub use inventory as __inventory;
9use lenso_app_plan::{
10 ExecutionClassId, ResolvedAppPlan,
11 authoring::{HostCatalog, HostDefaultPlugin, HostPluginRelease, HostSlot, PluginDescriptor},
12};
13use lenso_kernel::{ActivateContext, DeactivateContext, PrepareContext};
14pub use lenso_kernel::{CancellationToken, RuntimeFailure};
15pub use lenso_native_adapter_macros::{PluginConfig, plugin, provides};
16pub use managed_tasks::{ManagedTasks, ManagedTasksError};
17
18#[allow(async_fn_in_trait)]
24pub trait Lifecycle: Clone + 'static {
25 async fn prepare(&self, _context: PrepareContext) -> Result<(), RuntimeFailure> {
26 Ok(())
27 }
28
29 async fn activate(&self, _context: ActivateContext) -> Result<(), RuntimeFailure> {
30 Ok(())
31 }
32
33 async fn deactivate(&self, _context: DeactivateContext) -> Result<(), RuntimeFailure> {
34 Ok(())
35 }
36}
37
38#[doc(hidden)]
40pub mod __private {
41 pub use crate::{
42 __inventory, Lifecycle, LinkedNativePluginFactory, NativePluginFactory,
43 NativePluginFactoryContext, NativePluginInstance, RuntimeFailure,
44 };
45 pub use futures;
46 pub use futures::future::LocalBoxFuture;
47 pub use lenso_kernel::{
48 ActivateContext, DeactivateContext, InvocationContext, NativeEventEndpoint,
49 NativeRequestEndpoint, NativeRequestFuture, NativeStreamEndpoint, NativeStreamSession,
50 PluginFuture, PluginLifecycle, PrepareContext,
51 };
52 pub use serde_json;
53}
54
55use lenso_kernel::{
56 NativeEndpointSet, NativeEventEndpoint, NativeExecutionAdapter, NativeRequestEndpoint,
57 NativeStreamEndpoint, NoopPluginLifecycle, PluginLifecycle, PreparedBinding,
58 PreparedEventBinding, PreparedNativeApp, PreparedNativePlugin, PreparedStreamBinding,
59};
60
61#[derive(Clone, Copy, Debug)]
63#[doc(hidden)]
64pub struct LinkedNativePluginFactory {
65 constructor: fn() -> Rc<dyn NativePluginFactory>,
66 descriptor: &'static str,
67}
68
69impl LinkedNativePluginFactory {
70 #[doc(hidden)]
72 pub const fn new(
73 constructor: fn() -> Rc<dyn NativePluginFactory>,
74 descriptor: &'static str,
75 ) -> Self {
76 Self {
77 constructor,
78 descriptor,
79 }
80 }
81}
82
83inventory::collect!(LinkedNativePluginFactory);
84
85#[derive(Debug)]
87pub struct NativePluginInstance {
88 endpoints: NativeEndpointSet,
89 lifecycle: Rc<dyn PluginLifecycle>,
90}
91
92impl NativePluginInstance {
93 pub fn new(endpoints: Vec<Rc<dyn NativeRequestEndpoint>>) -> Self {
95 Self::with_lifecycle(endpoints, NoopPluginLifecycle)
96 }
97
98 pub fn with_lifecycle(
100 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
101 lifecycle: impl PluginLifecycle,
102 ) -> Self {
103 Self {
104 endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
105 lifecycle: Rc::new(lifecycle),
106 }
107 }
108
109 pub fn with_endpoints(
111 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
112 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
113 lifecycle: impl PluginLifecycle,
114 ) -> Self {
115 Self {
116 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
117 lifecycle: Rc::new(lifecycle),
118 }
119 }
120
121 pub fn with_stream_endpoints(
123 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
124 lifecycle: impl PluginLifecycle,
125 ) -> Self {
126 Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
127 }
128
129 pub fn with_event_endpoints(
131 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
132 lifecycle: impl PluginLifecycle,
133 ) -> Self {
134 Self {
135 endpoints: NativeEndpointSet::new(Vec::new(), Vec::new(), event_endpoints),
136 lifecycle: Rc::new(lifecycle),
137 }
138 }
139
140 pub fn with_all_endpoints(
142 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
143 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
144 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
145 lifecycle: impl PluginLifecycle,
146 ) -> Self {
147 Self {
148 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
149 lifecycle: Rc::new(lifecycle),
150 }
151 }
152
153 pub fn lifecycle(&self) -> Rc<dyn PluginLifecycle> {
155 self.lifecycle.clone()
156 }
157
158 pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
160 self.endpoints.request()
161 }
162
163 pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
165 self.endpoints.stream()
166 }
167
168 pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
170 self.endpoints.event()
171 }
172}
173
174impl Default for NativePluginInstance {
175 fn default() -> Self {
176 Self::new(Vec::new())
177 }
178}
179
180pub trait NativePluginFactory: std::fmt::Debug + 'static {
182 fn package_id(&self) -> &'static str;
184 fn package_version(&self) -> &'static str {
186 ""
187 }
188 fn factory_identity(&self) -> String {
195 let version = self.package_version();
196 if version.is_empty() {
197 self.package_id().to_owned()
198 } else {
199 format!("{}@{version}", self.package_id())
200 }
201 }
202 fn instantiate(
204 &self,
205 context: NativePluginFactoryContext<'_>,
206 ) -> Result<NativePluginInstance, RuntimeFailure>;
207}
208
209#[derive(Clone, Copy, Debug)]
211pub struct NativePluginFactoryContext<'a> {
212 instance_key: &'a str,
213 entrypoint: &'a str,
214 configuration: &'a str,
215}
216
217impl<'a> NativePluginFactoryContext<'a> {
218 fn from_plan(instance: &'a lenso_app_plan::PluginInstancePlan) -> Self {
219 Self {
220 instance_key: instance.instance_key(),
221 entrypoint: instance.entrypoint(),
222 configuration: instance.configuration(),
223 }
224 }
225
226 pub const fn instance_key(self) -> &'a str {
228 self.instance_key
229 }
230
231 pub const fn entrypoint(self) -> &'a str {
233 self.entrypoint
234 }
235
236 pub const fn configuration(self) -> &'a str {
238 self.configuration
239 }
240}
241
242#[derive(Debug, Default)]
244pub struct NativePluginRegistry {
245 factories: Vec<Rc<dyn NativePluginFactory>>,
246}
247
248type NativeInstances = BTreeMap<String, NativePluginInstance>;
249type PreparedGenerations = BTreeMap<String, PreparedNativePlugin>;
250type NativeBindings = (
251 Vec<PreparedBinding>,
252 Vec<PreparedStreamBinding>,
253 Vec<PreparedEventBinding>,
254);
255
256fn factory_matches(
257 factory: &dyn NativePluginFactory,
258 expected: &lenso_app_plan::PluginInstancePlan,
259) -> bool {
260 factory.package_id() == expected.package_id()
261 && (expected.package_revision().is_empty()
262 || factory.package_version() == expected.package_revision()
263 || factory.factory_identity() == expected.package_revision())
264}
265
266impl NativePluginRegistry {
267 pub fn new() -> Self {
269 Self::default()
270 }
271
272 #[must_use]
277 pub fn with_linked_factories(mut self) -> Self {
278 self.factories.extend(
279 inventory::iter::<LinkedNativePluginFactory>
280 .into_iter()
281 .map(|linked| (linked.constructor)()),
282 );
283 self.factories
284 .sort_by_key(|factory| factory.factory_identity());
285 self
286 }
287
288 pub fn factories(&self) -> impl Iterator<Item = &dyn NativePluginFactory> {
290 self.factories.iter().map(std::convert::AsRef::as_ref)
291 }
292
293 pub fn host_catalog(
295 slots: impl IntoIterator<Item = HostSlot>,
296 defaults: impl IntoIterator<Item = HostDefaultPlugin>,
297 ) -> Result<HostCatalog, RuntimeFailure> {
298 let plugins = inventory::iter::<LinkedNativePluginFactory>
299 .into_iter()
300 .map(|linked| {
301 serde_json::from_str::<PluginDescriptor>(linked.descriptor)
302 .map(HostPluginRelease::new)
303 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
304 detail: format!("invalid linked Plugin Descriptor: {error}"),
305 })
306 })
307 .collect::<Result<Vec<_>, _>>()?;
308 Ok(HostCatalog::new(slots, plugins, defaults))
309 }
310 #[must_use]
312 pub fn with_factory(mut self, factory: impl NativePluginFactory) -> Self {
313 self.factories.push(Rc::new(factory));
314 self
315 }
316
317 fn prepare_instances(
318 &self,
319 plan: &ResolvedAppPlan,
320 ) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
321 let mut instances = BTreeMap::new();
322 let mut generations = BTreeMap::new();
323 for expected in plan
324 .plugin_instances()
325 .iter()
326 .filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
327 {
328 let matching_factories: Vec<_> = self
329 .factories
330 .iter()
331 .filter(|factory| factory_matches(factory.as_ref(), expected))
332 .collect();
333 let factory = match matching_factories.as_slice() {
334 [] => {
335 return Err(RuntimeFailure::MissingPluginFactory {
336 instance: expected.instance_key().to_owned(),
337 package_id: expected.package_id().to_owned(),
338 });
339 }
340 [factory] => *factory,
341 _ => {
342 return invalid(format!(
343 "multiple statically linked factories declare package `{}`",
344 expected.package_id()
345 ));
346 }
347 };
348 let generation =
349 factory.instantiate(NativePluginFactoryContext::from_plan(expected))?;
350 generations.insert(
351 expected.instance_key().to_owned(),
352 PreparedNativePlugin::with_endpoint_set_lifecycle(
353 generation.endpoints.clone(),
354 generation.lifecycle(),
355 ),
356 );
357 if instances
358 .insert(expected.instance_key().to_owned(), generation)
359 .is_some()
360 {
361 return invalid(format!(
362 "duplicate Plugin Instance `{}`",
363 expected.instance_key()
364 ));
365 }
366 }
367 Ok((instances, generations))
368 }
369}
370
371impl NativeExecutionAdapter for NativePluginRegistry {
372 fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
373 plan.validate()
374 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
375 detail: error.to_string(),
376 })?;
377
378 let (instances, generations) = self.prepare_instances(plan)?;
379 let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
380 Ok(PreparedNativeApp::new(bindings, generations)
381 .with_stream_bindings(stream_bindings)
382 .with_event_bindings(event_bindings))
383 }
384
385 fn recreate(
386 &self,
387 plan: &ResolvedAppPlan,
388 instance_key: &str,
389 ) -> Result<PreparedNativePlugin, RuntimeFailure> {
390 let expected = plan
391 .plugin_instances()
392 .iter()
393 .find(|instance| instance.instance_key() == instance_key)
394 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
395 detail: format!("unknown Plugin Instance `{instance_key}`"),
396 })?;
397 let matching_factories: Vec<_> = self
398 .factories
399 .iter()
400 .filter(|factory| factory_matches(factory.as_ref(), expected))
401 .collect();
402 let factory = match matching_factories.as_slice() {
403 [] => {
404 return Err(RuntimeFailure::MissingPluginFactory {
405 instance: expected.instance_key().to_owned(),
406 package_id: expected.package_id().to_owned(),
407 });
408 }
409 [factory] => *factory,
410 _ => {
411 return invalid(format!(
412 "multiple statically linked factories declare package `{}`",
413 expected.package_id()
414 ));
415 }
416 };
417 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(expected))?;
418 Ok(PreparedNativePlugin::with_endpoint_set_lifecycle(
419 generation.endpoints.clone(),
420 generation.lifecycle(),
421 ))
422 }
423}
424
425fn prepare_bindings(
426 plan: &ResolvedAppPlan,
427 instances: &NativeInstances,
428) -> Result<NativeBindings, RuntimeFailure> {
429 let mut bindings = Vec::new();
430 let mut stream_bindings = Vec::new();
431 let mut event_bindings = Vec::new();
432 for binding in plan.capability_bindings() {
433 if !instances.contains_key(binding.provider_instance()) {
434 continue;
435 }
436 let provider = plan
437 .plugin_instance(binding.provider_instance())
438 .expect("validated binding provider should exist");
439 let descriptor = provider
440 .provided_capabilities()
441 .iter()
442 .find(|descriptor| descriptor.capability_id() == binding.capability_id())
443 .expect("validated binding descriptor should exist");
444 if !descriptor.request_operations().is_empty() {
445 let endpoint = instances
446 .get(binding.provider_instance())
447 .and_then(|instance| {
448 instance.endpoints.request().iter().find(|endpoint| {
449 endpoint.capability_id() == binding.capability_id()
450 && endpoint.descriptor_version() == binding.descriptor_version()
451 })
452 })
453 .cloned()
454 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
455 detail: format!(
456 "Capability `{}` version `{}` has no request endpoint on provider `{}`",
457 binding.capability_id(),
458 binding.descriptor_version(),
459 binding.provider_instance()
460 ),
461 })?;
462 bindings.push(PreparedBinding::new(
463 binding.consumer_instance(),
464 binding.provider_instance(),
465 endpoint,
466 ));
467 }
468 if !descriptor.stream_operations().is_empty() {
469 let endpoint = instances
470 .get(binding.provider_instance())
471 .and_then(|instance| {
472 instance.endpoints.stream().iter().find(|endpoint| {
473 endpoint.capability_id() == binding.capability_id()
474 && endpoint.descriptor_version() == binding.descriptor_version()
475 })
476 })
477 .cloned()
478 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
479 detail: format!(
480 "Capability `{}` version `{}` has no stream endpoint on provider `{}`",
481 binding.capability_id(),
482 binding.descriptor_version(),
483 binding.provider_instance()
484 ),
485 })?;
486 stream_bindings.push(PreparedStreamBinding::new(
487 binding.consumer_instance(),
488 binding.provider_instance(),
489 endpoint,
490 ));
491 }
492 if !descriptor.event_operations().is_empty() {
493 let endpoint = instances
494 .get(binding.provider_instance())
495 .and_then(|instance| {
496 instance.endpoints.event().iter().find(|endpoint| {
497 endpoint.capability_id() == binding.capability_id()
498 && endpoint.descriptor_version() == binding.descriptor_version()
499 })
500 })
501 .cloned()
502 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
503 detail: format!(
504 "Capability `{}` version `{}` has no Event endpoint on provider `{}`",
505 binding.capability_id(),
506 binding.descriptor_version(),
507 binding.provider_instance()
508 ),
509 })?;
510 event_bindings.push(PreparedEventBinding::new(
511 binding.consumer_instance(),
512 binding.provider_instance(),
513 endpoint,
514 ));
515 }
516 }
517 Ok((bindings, stream_bindings, event_bindings))
518}
519
520fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
521 Err(RuntimeFailure::InvalidResolvedPlan { detail })
522}