mod managed_tasks;
use std::{collections::BTreeMap, rc::Rc};
#[doc(hidden)]
pub use inventory as __inventory;
use lenso_app_plan::{
ExecutionClassId, ResolvedAppPlan,
authoring::{HostCatalog, HostDefaultPlugin, HostPluginRelease, HostSlot, PluginDescriptor},
};
use lenso_kernel::{ActivateContext, DeactivateContext, PrepareContext};
pub use lenso_kernel::{CancellationToken, RuntimeFailure};
pub use lenso_native_adapter_macros::{PluginConfig, plugin, provides};
pub use managed_tasks::{ManagedTasks, ManagedTasksError};
#[allow(async_fn_in_trait)]
pub trait Lifecycle: Clone + 'static {
async fn prepare(&self, _context: PrepareContext) -> Result<(), RuntimeFailure> {
Ok(())
}
async fn activate(&self, _context: ActivateContext) -> Result<(), RuntimeFailure> {
Ok(())
}
async fn deactivate(&self, _context: DeactivateContext) -> Result<(), RuntimeFailure> {
Ok(())
}
}
#[doc(hidden)]
pub mod __private {
pub use crate::{
__inventory, Lifecycle, LinkedNativePluginFactory, NativePluginFactory,
NativePluginFactoryContext, NativePluginInstance, RuntimeFailure,
};
pub use futures;
pub use futures::future::LocalBoxFuture;
pub use lenso_kernel::{
ActivateContext, DeactivateContext, InvocationContext, NativeEventEndpoint,
NativeRequestEndpoint, NativeRequestFuture, NativeStreamEndpoint, NativeStreamSession,
PluginFuture, PluginLifecycle, PrepareContext,
};
pub use serde_json;
}
use lenso_kernel::{
NativeEndpointSet, NativeEventEndpoint, NativeExecutionAdapter, NativeRequestEndpoint,
NativeStreamEndpoint, NoopPluginLifecycle, PluginLifecycle, PreparedBinding,
PreparedEventBinding, PreparedNativeApp, PreparedNativePlugin, PreparedStreamBinding,
};
#[derive(Clone, Copy, Debug)]
#[doc(hidden)]
pub struct LinkedNativePluginFactory {
constructor: fn() -> Rc<dyn NativePluginFactory>,
descriptor: &'static str,
}
impl LinkedNativePluginFactory {
#[doc(hidden)]
pub const fn new(
constructor: fn() -> Rc<dyn NativePluginFactory>,
descriptor: &'static str,
) -> Self {
Self {
constructor,
descriptor,
}
}
}
inventory::collect!(LinkedNativePluginFactory);
#[derive(Debug)]
pub struct NativePluginInstance {
endpoints: NativeEndpointSet,
lifecycle: Rc<dyn PluginLifecycle>,
}
impl NativePluginInstance {
pub fn new(endpoints: Vec<Rc<dyn NativeRequestEndpoint>>) -> Self {
Self::with_lifecycle(endpoints, NoopPluginLifecycle)
}
pub fn with_lifecycle(
endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
lifecycle: impl PluginLifecycle,
) -> Self {
Self {
endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
lifecycle: Rc::new(lifecycle),
}
}
pub fn with_endpoints(
endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
lifecycle: impl PluginLifecycle,
) -> Self {
Self {
endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
lifecycle: Rc::new(lifecycle),
}
}
pub fn with_stream_endpoints(
stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
lifecycle: impl PluginLifecycle,
) -> Self {
Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
}
pub fn with_event_endpoints(
event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
lifecycle: impl PluginLifecycle,
) -> Self {
Self {
endpoints: NativeEndpointSet::new(Vec::new(), Vec::new(), event_endpoints),
lifecycle: Rc::new(lifecycle),
}
}
pub fn with_all_endpoints(
endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
lifecycle: impl PluginLifecycle,
) -> Self {
Self {
endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
lifecycle: Rc::new(lifecycle),
}
}
pub fn lifecycle(&self) -> Rc<dyn PluginLifecycle> {
self.lifecycle.clone()
}
pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
self.endpoints.request()
}
pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
self.endpoints.stream()
}
pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
self.endpoints.event()
}
}
impl Default for NativePluginInstance {
fn default() -> Self {
Self::new(Vec::new())
}
}
pub trait NativePluginFactory: std::fmt::Debug + 'static {
fn package_id(&self) -> &'static str;
fn package_version(&self) -> &'static str {
""
}
fn factory_identity(&self) -> String {
let version = self.package_version();
if version.is_empty() {
self.package_id().to_owned()
} else {
format!("{}@{version}", self.package_id())
}
}
fn instantiate(
&self,
context: NativePluginFactoryContext<'_>,
) -> Result<NativePluginInstance, RuntimeFailure>;
}
#[derive(Clone, Copy, Debug)]
pub struct NativePluginFactoryContext<'a> {
instance_key: &'a str,
entrypoint: &'a str,
configuration: &'a str,
}
impl<'a> NativePluginFactoryContext<'a> {
fn from_plan(instance: &'a lenso_app_plan::PluginInstancePlan) -> Self {
Self {
instance_key: instance.instance_key(),
entrypoint: instance.entrypoint(),
configuration: instance.configuration(),
}
}
pub const fn instance_key(self) -> &'a str {
self.instance_key
}
pub const fn entrypoint(self) -> &'a str {
self.entrypoint
}
pub const fn configuration(self) -> &'a str {
self.configuration
}
}
#[derive(Debug, Default)]
pub struct NativePluginRegistry {
factories: Vec<Rc<dyn NativePluginFactory>>,
}
type NativeInstances = BTreeMap<String, NativePluginInstance>;
type PreparedGenerations = BTreeMap<String, PreparedNativePlugin>;
type NativeBindings = (
Vec<PreparedBinding>,
Vec<PreparedStreamBinding>,
Vec<PreparedEventBinding>,
);
fn factory_matches(
factory: &dyn NativePluginFactory,
expected: &lenso_app_plan::PluginInstancePlan,
) -> bool {
factory.package_id() == expected.package_id()
&& (expected.package_revision().is_empty()
|| factory.package_version() == expected.package_revision()
|| factory.factory_identity() == expected.package_revision())
}
impl NativePluginRegistry {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_linked_factories(mut self) -> Self {
self.factories.extend(
inventory::iter::<LinkedNativePluginFactory>
.into_iter()
.map(|linked| (linked.constructor)()),
);
self.factories
.sort_by_key(|factory| factory.factory_identity());
self
}
pub fn factories(&self) -> impl Iterator<Item = &dyn NativePluginFactory> {
self.factories.iter().map(std::convert::AsRef::as_ref)
}
pub fn host_catalog(
slots: impl IntoIterator<Item = HostSlot>,
defaults: impl IntoIterator<Item = HostDefaultPlugin>,
) -> Result<HostCatalog, RuntimeFailure> {
let plugins = inventory::iter::<LinkedNativePluginFactory>
.into_iter()
.map(|linked| {
serde_json::from_str::<PluginDescriptor>(linked.descriptor)
.map(HostPluginRelease::new)
.map_err(|error| RuntimeFailure::InvalidResolvedPlan {
detail: format!("invalid linked Plugin Descriptor: {error}"),
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(HostCatalog::new(slots, plugins, defaults))
}
#[must_use]
pub fn with_factory(mut self, factory: impl NativePluginFactory) -> Self {
self.factories.push(Rc::new(factory));
self
}
fn prepare_instances(
&self,
plan: &ResolvedAppPlan,
) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
let mut instances = BTreeMap::new();
let mut generations = BTreeMap::new();
for expected in plan
.plugin_instances()
.iter()
.filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
{
let matching_factories: Vec<_> = self
.factories
.iter()
.filter(|factory| factory_matches(factory.as_ref(), expected))
.collect();
let factory = match matching_factories.as_slice() {
[] => {
return Err(RuntimeFailure::MissingPluginFactory {
instance: expected.instance_key().to_owned(),
package_id: expected.package_id().to_owned(),
});
}
[factory] => *factory,
_ => {
return invalid(format!(
"multiple statically linked factories declare package `{}`",
expected.package_id()
));
}
};
let generation =
factory.instantiate(NativePluginFactoryContext::from_plan(expected))?;
generations.insert(
expected.instance_key().to_owned(),
PreparedNativePlugin::with_endpoint_set_lifecycle(
generation.endpoints.clone(),
generation.lifecycle(),
),
);
if instances
.insert(expected.instance_key().to_owned(), generation)
.is_some()
{
return invalid(format!(
"duplicate Plugin Instance `{}`",
expected.instance_key()
));
}
}
Ok((instances, generations))
}
}
impl NativeExecutionAdapter for NativePluginRegistry {
fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
plan.validate()
.map_err(|error| RuntimeFailure::InvalidResolvedPlan {
detail: error.to_string(),
})?;
let (instances, generations) = self.prepare_instances(plan)?;
let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
Ok(PreparedNativeApp::new(bindings, generations)
.with_stream_bindings(stream_bindings)
.with_event_bindings(event_bindings))
}
fn recreate(
&self,
plan: &ResolvedAppPlan,
instance_key: &str,
) -> Result<PreparedNativePlugin, RuntimeFailure> {
let expected = plan
.plugin_instances()
.iter()
.find(|instance| instance.instance_key() == instance_key)
.ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
detail: format!("unknown Plugin Instance `{instance_key}`"),
})?;
let matching_factories: Vec<_> = self
.factories
.iter()
.filter(|factory| factory_matches(factory.as_ref(), expected))
.collect();
let factory = match matching_factories.as_slice() {
[] => {
return Err(RuntimeFailure::MissingPluginFactory {
instance: expected.instance_key().to_owned(),
package_id: expected.package_id().to_owned(),
});
}
[factory] => *factory,
_ => {
return invalid(format!(
"multiple statically linked factories declare package `{}`",
expected.package_id()
));
}
};
let generation = factory.instantiate(NativePluginFactoryContext::from_plan(expected))?;
Ok(PreparedNativePlugin::with_endpoint_set_lifecycle(
generation.endpoints.clone(),
generation.lifecycle(),
))
}
}
fn prepare_bindings(
plan: &ResolvedAppPlan,
instances: &NativeInstances,
) -> Result<NativeBindings, RuntimeFailure> {
let mut bindings = Vec::new();
let mut stream_bindings = Vec::new();
let mut event_bindings = Vec::new();
for binding in plan.capability_bindings() {
if !instances.contains_key(binding.provider_instance()) {
continue;
}
let provider = plan
.plugin_instance(binding.provider_instance())
.expect("validated binding provider should exist");
let descriptor = provider
.provided_capabilities()
.iter()
.find(|descriptor| descriptor.capability_id() == binding.capability_id())
.expect("validated binding descriptor should exist");
if !descriptor.request_operations().is_empty() {
let endpoint = instances
.get(binding.provider_instance())
.and_then(|instance| {
instance.endpoints.request().iter().find(|endpoint| {
endpoint.capability_id() == binding.capability_id()
&& endpoint.descriptor_version() == binding.descriptor_version()
})
})
.cloned()
.ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
detail: format!(
"Capability `{}` version `{}` has no request endpoint on provider `{}`",
binding.capability_id(),
binding.descriptor_version(),
binding.provider_instance()
),
})?;
bindings.push(PreparedBinding::new(
binding.consumer_instance(),
binding.provider_instance(),
endpoint,
));
}
if !descriptor.stream_operations().is_empty() {
let endpoint = instances
.get(binding.provider_instance())
.and_then(|instance| {
instance.endpoints.stream().iter().find(|endpoint| {
endpoint.capability_id() == binding.capability_id()
&& endpoint.descriptor_version() == binding.descriptor_version()
})
})
.cloned()
.ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
detail: format!(
"Capability `{}` version `{}` has no stream endpoint on provider `{}`",
binding.capability_id(),
binding.descriptor_version(),
binding.provider_instance()
),
})?;
stream_bindings.push(PreparedStreamBinding::new(
binding.consumer_instance(),
binding.provider_instance(),
endpoint,
));
}
if !descriptor.event_operations().is_empty() {
let endpoint = instances
.get(binding.provider_instance())
.and_then(|instance| {
instance.endpoints.event().iter().find(|endpoint| {
endpoint.capability_id() == binding.capability_id()
&& endpoint.descriptor_version() == binding.descriptor_version()
})
})
.cloned()
.ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
detail: format!(
"Capability `{}` version `{}` has no Event endpoint on provider `{}`",
binding.capability_id(),
binding.descriptor_version(),
binding.provider_instance()
),
})?;
event_bindings.push(PreparedEventBinding::new(
binding.consumer_instance(),
binding.provider_instance(),
endpoint,
));
}
}
Ok((bindings, stream_bindings, event_bindings))
}
fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
Err(RuntimeFailure::InvalidResolvedPlan { detail })
}