lenso 0.5.21

Rust authoring and Host facade for Lenso applications and Plugins.
Documentation
#![allow(dead_code)]

use std::cell::Cell;

use lenso::prelude::*;
use lenso_app_plan::{
    AppComposition, CapabilityBinding, CapabilityEndpointPlan, CapabilityRequirementPlan,
    PluginInstancePlan,
};
use lenso_kernel::{DeterministicDriver, Kernel, NativeExecutionAdapter};
use lenso_native_adapter::{
    NativePluginFactory, NativePluginFactoryContext, NativePluginInstance, NativePluginRegistry,
};

#[derive(Debug)]
struct ConsumerFactory;

impl NativePluginFactory for ConsumerFactory {
    fn package_id(&self) -> &'static str {
        "fixture.consumer"
    }

    fn runtime_profile(&self) -> &'static str {
        "lenso.native-authoring@2"
    }

    fn instantiate(
        &self,
        _context: NativePluginFactoryContext<'_>,
    ) -> Result<NativePluginInstance, lenso_kernel::RuntimeFailure> {
        Ok(NativePluginInstance::default())
    }
}

#[derive(Clone, Debug, PluginConfig)]
struct PluginSettings {
    enabled: bool,
}

#[doc(hidden)]
pub mod __lenso_native_support {
    pub use lenso::__private::{
        NativeEventEndpoint, NativePluginInstance, NativeRequestEndpoint, NativeStreamEndpoint,
    };
}

mod health {
    use std::any::Any;

    use lenso::__private::{
        InvocationContext, LocalBoxFuture, NativeRequestEndpoint, NativeRequestFuture,
        RuntimeFailure,
    };
    use lenso_kernel::RequestCapability;

    #[derive(Debug)]
    pub struct HealthEndpoint {
        provider: std::rc::Rc<dyn HealthProvider>,
    }

    impl HealthEndpoint {
        pub(crate) fn new(provider: impl HealthProvider) -> Self {
            Self {
                provider: std::rc::Rc::new(provider),
            }
        }
    }

    impl NativeRequestEndpoint for HealthEndpoint {
        fn capability_id(&self) -> &'static str {
            "example.health@1"
        }

        fn descriptor_version(&self) -> &'static str {
            "1.0.0"
        }

        fn operations(&self) -> &'static [&'static str] {
            &["check"]
        }

        fn invoke(
            &self,
            _operation: &str,
            request: Box<dyn Any>,
            context: InvocationContext,
        ) -> LocalBoxFuture<'static, Result<Result<Box<dyn Any>, Box<dyn Any>>, RuntimeFailure>>
        {
            let Ok(request) = request.downcast::<()>() else {
                return Box::pin(async {
                    Err(RuntimeFailure::ProtocolViolation {
                        capability: "example.health@1",
                    })
                });
            };
            let _: () = *request;
            let invocation = self.provider.check(context, ());
            Box::pin(async move {
                invocation.await.map(|result| {
                    result
                        .map(|value| Box::new(value) as Box<dyn Any>)
                        .map_err(|error| Box::new(error) as Box<dyn Any>)
                })
            })
        }
    }

    #[macro_export]
    macro_rules! __test_lenso_provided_health {
        () => {
            r#"{"capability_id":"example.health@1","descriptor_version":"1.0.0","operations":["check"],"operation_kinds":{},"default_admission":{"queue_capacity":0,"max_concurrency":1},"operation_admissions":{},"event_admission":null,"cross_lane_transfer":false}"#
        };
    }
    pub use crate::__test_lenso_provided_health as __lenso_provided_health;

    #[macro_export]
    macro_rules! __test_lenso_native_endpoints_health {
        ($provider:expr, $support:path) => {{
            use $support as __LensoNativeSupport;
            (
                vec![
                    std::rc::Rc::new($crate::health::HealthEndpoint::new($provider))
                        as std::rc::Rc<dyn __LensoNativeSupport::NativeRequestEndpoint>,
                ],
                Vec::<std::rc::Rc<dyn __LensoNativeSupport::NativeStreamEndpoint>>::new(),
                Vec::<std::rc::Rc<dyn __LensoNativeSupport::NativeEventEndpoint>>::new(),
            )
        }};
    }
    pub use crate::__test_lenso_native_endpoints_health as __lenso_native_endpoints_health;

    #[macro_export]
    macro_rules! __test_lenso_native_lower_health {
        ($plugin:ty, $support:path) => {
            use $support as __LensoNativeSupportHealth;
            impl $crate::health::HealthProvider for $plugin {
                fn check(
                    &self,
                    context: __LensoNativeSupportHealth::InvocationContext,
                    request: (),
                ) -> __LensoNativeSupportHealth::NativeRequestFuture<$crate::health::Health> {
                    let plugin = self.clone();
                    Box::pin(async move { Ok(<$plugin>::check(&plugin, context, request).await) })
                }
            }
        };
    }
    pub use crate::__test_lenso_native_lower_health as __lenso_native_lower_health;

    #[macro_export]
    macro_rules! __test_lenso_native_lower_object_health {
        ($object:ty, $plugin:ty, $support:path) => {
            use $support as __LensoNativeObjectSupportHealth;
            impl $crate::health::HealthProvider for $object {
                fn check(
                    &self,
                    context: __LensoNativeObjectSupportHealth::InvocationContext,
                    request: (),
                ) -> __LensoNativeObjectSupportHealth::NativeRequestFuture<$crate::health::Health>
                {
                    let object = self.clone();
                    Box::pin(async move {
                        let plugin = object.get()?;
                        Ok(<$plugin>::check(plugin.as_ref(), context, request).await)
                    })
                }
            }
        };
    }
    pub use crate::__test_lenso_native_lower_object_health as __lenso_native_lower_object_health;

    #[derive(Debug)]
    pub struct Health;

    impl RequestCapability for Health {
        type Request = ();
        type Response = usize;
        type DomainError = ();

        const ID: &'static str = "example.health@1";
        const DESCRIPTOR_VERSION: &'static str = "1.0.0";
    }

    pub trait HealthProvider: std::fmt::Debug + 'static {
        fn check(&self, context: InvocationContext, request: ()) -> NativeRequestFuture<Health>;
    }
}

#[plugin]
#[derive(Clone, Debug, Default)]
struct StatelessHealthPlugin {
    calls: Cell<usize>,
}

#[provides(health::Health)]
impl StatelessHealthPlugin {
    async fn check(
        &self,
        _context: lenso::__private::InvocationContext,
        (): (),
    ) -> Result<usize, ()> {
        let calls = self.calls.get() + 1;
        self.calls.set(calls);
        Ok(calls)
    }
}

#[test]
fn stateless_plugin_derives_empty_configuration() {
    let descriptor: lenso::__private::serde_json::Value =
        lenso::__private::serde_json::from_str(PLUGIN_DESCRIPTOR_JSON)
            .expect("generated Descriptor should be valid JSON");
    assert_eq!(
        descriptor["configuration_schema"],
        lenso::__private::serde_json::json!({
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "additionalProperties": false,
            "required": [],
            "properties": {},
        })
    );
    assert_eq!(descriptor["authoring_version"], 2);
    assert_eq!(descriptor["runtime_profile"], "lenso.native-authoring@2");

    let provider = PluginInstancePlan::new("health", "lenso")
        .with_authoring(2, "lenso.native-authoring@2")
        .with_configuration("{}")
        .with_capability(CapabilityEndpointPlan::new(
            "example.health@1",
            "1.0.0",
            ["check"],
        ));
    let plan = AppComposition::new(vec![provider], vec![])
        .resolve()
        .expect("stateless plan should resolve");
    NativeExecutionAdapter::prepare(&NativePluginRegistry::new().with_linked_factories(), &plan)
        .expect("stateless Plugin should accept empty configuration");
}

#[test]
fn authoring_v2_provider_invokes_the_one_constructed_object() {
    let provider = PluginInstancePlan::new("health", "lenso")
        .with_authoring(2, "lenso.native-authoring@2")
        .with_configuration("{}")
        .with_capability(CapabilityEndpointPlan::new(
            "example.health@1",
            "1.0.0",
            ["check"],
        ));
    let consumer = PluginInstancePlan::new("consumer", "fixture.consumer")
        .with_authoring(2, "lenso.native-authoring@2")
        .with_requirement(
            CapabilityRequirementPlan::one("example.health@1", "1.0.0")
                .with_requirement_id("health"),
        );
    let plan = AppComposition::new(
        vec![consumer, provider],
        vec![
            CapabilityBinding::new("consumer", "example.health@1", "1.0.0", "health")
                .with_requirement_id("health"),
        ],
    )
    .resolve()
    .expect("complete-object provider plan should resolve");
    let driver = DeterministicDriver::new();
    let app = driver
        .run(Kernel::start_native(
            plan,
            driver.clone(),
            NativePluginRegistry::new()
                .with_linked_factories()
                .with_factory(ConsumerFactory),
        ))
        .expect("authoring v2 provider should construct");

    assert_eq!(
        driver
            .run(app.invoke::<health::Health>("consumer", "check", ()))
            .expect("first request should reach the provider"),
        Ok(1)
    );
    assert_eq!(
        driver
            .run(app.invoke::<health::Health>("consumer", "check", ()))
            .expect("second request should reach the provider"),
        Ok(2)
    );
}

#[test]
fn stateless_plugin_rejects_non_empty_configuration() {
    let provider = PluginInstancePlan::new("health", "lenso")
        .with_authoring(2, "lenso.native-authoring@2")
        .with_configuration(r#"{"unexpected":true}"#)
        .with_capability(CapabilityEndpointPlan::new(
            "example.health@1",
            "1.0.0",
            ["check"],
        ));
    let plan = AppComposition::new(vec![provider], vec![])
        .resolve()
        .expect("composition resolution defers Plugin-owned configuration validation");
    let driver = DeterministicDriver::new();
    let error = driver
        .run(Kernel::start_native(
            plan,
            driver.clone(),
            NativePluginRegistry::new().with_linked_factories(),
        ))
        .expect_err("stateless Plugin must reject non-empty configuration");
    assert!(format!("{error:?}").contains("does not accept configuration"));
}