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