lenso-native-adapter 0.3.21

Native Rust Execution Adapter for Lenso.
Documentation
use std::{cell::RefCell, rc::Rc, time::Duration};

use lenso_app_plan::{PluginInstancePlan, ResolvedAppPlan};
use lenso_kernel::{DeterministicDriver, Kernel, RuntimeFailure, ShutdownOutcome};
use lenso_native_adapter::{
    ConfiguredPluginFactory, NativeFacilities, NativeInstanceFacilities, NativePluginDefinition,
    NativePluginRegistry,
};

#[derive(Clone, Debug)]
struct StateHandle(u64);

mod legacy {
    use super::StateHandle;

    #[lenso_native_adapter::plugin(consumer, lifecycle)]
    #[derive(Clone, Debug)]
    pub struct Plugin {
        #[facility(id = "state")]
        pub state: Option<StateHandle>,
    }

    impl lenso_native_adapter::Lifecycle for Plugin {}
}

mod complete {
    use super::StateHandle;

    #[lenso_native_adapter::plugin(consumer)]
    #[derive(Debug)]
    pub struct Plugin {
        #[facility(id = "state")]
        pub state: Option<StateHandle>,
    }

    #[lenso_native_adapter::plugin_impl]
    impl Plugin {
        #[create]
        fn create(state: Option<StateHandle>) -> Self {
            Self { state }
        }
    }
}

mod mandatory {
    use super::StateHandle;

    #[lenso_native_adapter::plugin(consumer)]
    #[derive(Debug)]
    pub struct Plugin {
        #[facility(id = "state")]
        pub state: StateHandle,
    }

    #[lenso_native_adapter::plugin_impl]
    impl Plugin {
        #[create]
        fn create(state: StateHandle) -> Self {
            Self { state }
        }
    }
}

fn construct<P: NativePluginDefinition>(
    authoring: u32,
    facilities: NativeFacilities,
    inspect: fn(&P) -> Option<u64>,
) -> Result<Vec<Option<u64>>, RuntimeFailure> {
    let observed = Rc::new(RefCell::new(Vec::new()));
    let values = observed.clone();
    let registry = NativePluginRegistry::new()
        .with_factory(ConfiguredPluginFactory::<P, _>::new(move |plugin| {
            values.borrow_mut().push(inspect(plugin));
            Ok(())
        }))
        .with_facilities(
            NativeInstanceFacilities::new()
                .with("selected", facilities)
                .unwrap(),
        );
    let plan = ResolvedAppPlan::new(
        vec![
            PluginInstancePlan::new("selected", P::PACKAGE_ID)
                .with_authoring(authoring, P::RUNTIME_PROFILE),
        ],
        vec![],
    );
    let driver = DeterministicDriver::new();
    let app = driver.run(Kernel::start_native(plan, driver.clone(), registry))?;
    assert_eq!(
        driver.run(app.shutdown(Duration::from_secs(1))),
        ShutdownOutcome::Clean
    );
    let result = observed.borrow().clone();
    Ok(result)
}

fn check_optional<P: NativePluginDefinition>(authoring: u32, inspect: fn(&P) -> Option<u64>) {
    assert_eq!(
        construct::<P>(authoring, NativeFacilities::new(), inspect).unwrap(),
        vec![None]
    );
    assert_eq!(
        construct::<P>(
            authoring,
            NativeFacilities::new()
                .with("state", StateHandle(7))
                .unwrap(),
            inspect,
        )
        .unwrap(),
        vec![Some(7)]
    );
    assert!(
        construct::<P>(
            authoring,
            NativeFacilities::new()
                .with("state", "wrong type".to_owned())
                .unwrap(),
            inspect,
        )
        .is_err()
    );
    assert!(
        construct::<P>(
            authoring,
            NativeFacilities::new()
                .with_factory::<StateHandle>("state", || {
                    Err(RuntimeFailure::PluginFailure {
                        detail: "owner factory unavailable".into(),
                    })
                })
                .unwrap(),
            inspect,
        )
        .is_err()
    );
}

#[test]
fn legacy_constructor_preserves_optional_absence_and_rejects_invalid_present_inputs() {
    check_optional::<legacy::Plugin>(1, |plugin| plugin.state.as_ref().map(|state| state.0));
}

#[test]
fn complete_constructor_preserves_optional_absence_and_rejects_invalid_present_inputs() {
    check_optional::<complete::Plugin>(2, |plugin| plugin.state.as_ref().map(|state| state.0));
}

#[test]
fn mandatory_complete_constructor_still_requires_the_exact_typed_attachment() {
    assert!(
        construct::<mandatory::Plugin>(2, NativeFacilities::new(), |plugin| Some(plugin.state.0))
            .is_err()
    );
    assert_eq!(
        construct::<mandatory::Plugin>(
            2,
            NativeFacilities::new()
                .with("state", StateHandle(9))
                .unwrap(),
            |plugin| Some(plugin.state.0)
        )
        .unwrap(),
        vec![Some(9)]
    );
}