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