#![allow(unsafe_code)]
use nautilus_model::identifiers::ActorId;
use nautilus_trading::strategy::StrategyConfig;
use crate::{
bridge::{
PluginActorAdapter, PluginControllerAdapter, PluginStrategyAdapter, controller_host_vtable,
host_vtable, register_custom_data_from_manifest,
},
manifest::{
ValidatedActorRegistration, ValidatedActorVTable, ValidatedControllerRegistration,
ValidatedControllerVTable, ValidatedPluginManifest, ValidatedStrategyRegistration,
ValidatedStrategyVTable,
},
};
pub enum ConfiguredPluginEntry {
Actor(ConfiguredActorEntry),
Strategy(ConfiguredStrategyEntry),
Controller(ConfiguredControllerEntry),
}
pub struct ConfiguredActorEntry {
plugin_name: String,
type_name: String,
vtable: ValidatedActorVTable,
}
pub struct ConfiguredStrategyEntry {
plugin_name: String,
type_name: String,
vtable: ValidatedStrategyVTable,
}
pub struct ConfiguredControllerEntry {
plugin_name: String,
type_name: String,
vtable: ValidatedControllerVTable,
}
impl ConfiguredActorEntry {
pub fn create_adapter(
&self,
actor_id: ActorId,
config_json: &str,
) -> anyhow::Result<PluginActorAdapter> {
unsafe {
PluginActorAdapter::new(
actor_id,
self.plugin_name.clone(),
self.type_name.clone(),
self.vtable,
host_vtable(),
config_json,
)
}
}
}
impl ConfiguredStrategyEntry {
pub fn create_adapter(
&self,
strategy_config: StrategyConfig,
config_json: &str,
) -> anyhow::Result<PluginStrategyAdapter> {
unsafe {
PluginStrategyAdapter::new(
strategy_config,
self.plugin_name.clone(),
self.type_name.clone(),
self.vtable,
host_vtable(),
config_json,
)
}
}
}
impl ConfiguredControllerEntry {
pub fn create_adapter(&self, config_json: &str) -> anyhow::Result<PluginControllerAdapter> {
unsafe {
PluginControllerAdapter::new(
self.plugin_name.clone(),
self.type_name.clone(),
self.vtable,
controller_host_vtable(),
config_json,
)
}
}
}
pub fn register_manifest_custom_data(
manifest: ValidatedPluginManifest<'_>,
) -> anyhow::Result<usize> {
register_custom_data_from_manifest(manifest)
}
pub fn configured_entry(
manifest: ValidatedPluginManifest<'_>,
path: &str,
type_name: &str,
) -> anyhow::Result<ConfiguredPluginEntry> {
let plugin_name = manifest.plugin_name().to_string();
let actor_entry = find_actor_entry(manifest, type_name);
let strategy_entry = find_strategy_entry(manifest, type_name);
let controller_entry = find_controller_entry(manifest, type_name);
match (actor_entry, strategy_entry, controller_entry) {
(Some(entry), None, None) => Ok(ConfiguredPluginEntry::Actor(ConfiguredActorEntry {
plugin_name,
type_name: entry.type_name().to_string(),
vtable: entry.vtable(),
})),
(None, Some(entry), None) => Ok(ConfiguredPluginEntry::Strategy(ConfiguredStrategyEntry {
plugin_name,
type_name: entry.type_name().to_string(),
vtable: entry.vtable(),
})),
(None, None, Some(entry)) => Ok(ConfiguredPluginEntry::Controller(
ConfiguredControllerEntry {
plugin_name,
type_name: entry.type_name().to_string(),
vtable: entry.vtable(),
},
)),
(None, None, None) => {
anyhow::bail!(
"plug-in '{path}' does not expose actor, strategy, or controller type '{type_name}'"
)
}
(actor, strategy, controller) => {
let mut kinds = Vec::new();
if actor.is_some() {
kinds.push("actor");
}
if strategy.is_some() {
kinds.push("strategy");
}
if controller.is_some() {
kinds.push("controller");
}
anyhow::bail!(
"plug-in '{path}' exposes type '{type_name}' as multiple component kinds ({})",
kinds.join(", ")
)
}
}
}
fn find_actor_entry(
manifest: ValidatedPluginManifest<'_>,
type_name: &str,
) -> Option<ValidatedActorRegistration> {
manifest
.actors()
.find(|entry| entry.type_name() == type_name)
}
fn find_strategy_entry(
manifest: ValidatedPluginManifest<'_>,
type_name: &str,
) -> Option<ValidatedStrategyRegistration> {
manifest
.strategies()
.find(|entry| entry.type_name() == type_name)
}
fn find_controller_entry(
manifest: ValidatedPluginManifest<'_>,
type_name: &str,
) -> Option<ValidatedControllerRegistration> {
manifest
.controllers()
.find(|entry| entry.type_name() == type_name)
}
#[cfg(test)]
mod tests {
use std::sync::LazyLock;
use rstest::rstest;
use super::*;
use crate::{
NAUTILUS_PLUGIN_ABI_VERSION,
boundary::{BorrowedStr, Slice},
host::{HostContext, HostVTable},
manifest::{ActorRegistration, PluginBuildId, PluginManifest, StrategyRegistration},
surfaces::{
actor::{PluginActor, actor_vtable},
controller::{PluginController, controller_vtable},
strategy::{PluginStrategy, strategy_vtable},
},
};
struct ExampleActor;
impl PluginActor for ExampleActor {
const TYPE_NAME: &'static str = "ExampleActor";
fn new(_host: *const HostVTable, _ctx: *const HostContext, _config_json: &str) -> Self {
Self
}
}
struct ExampleStrategy;
impl PluginStrategy for ExampleStrategy {
const TYPE_NAME: &'static str = "ExampleStrategy";
fn new(_host: *const HostVTable, _ctx: *const HostContext, _config_json: &str) -> Self {
Self
}
}
struct ExampleController;
impl PluginController for ExampleController {
const TYPE_NAME: &'static str = "ExampleController";
fn new(
_host: *const crate::host::ControllerHostVTable,
_ctx: *const crate::host::ControllerHostContext,
_config_json: &str,
) -> Self {
Self
}
}
static ACTOR_REGISTRATIONS: LazyLock<[ActorRegistration; 1]> = LazyLock::new(|| {
[ActorRegistration {
type_name: BorrowedStr::from_str("ExampleActor"),
vtable: actor_vtable::<ExampleActor>(),
}]
});
static STRATEGY_REGISTRATIONS: LazyLock<[StrategyRegistration; 1]> = LazyLock::new(|| {
[StrategyRegistration {
type_name: BorrowedStr::from_str("ExampleStrategy"),
vtable: strategy_vtable::<ExampleStrategy>(),
}]
});
static CONTROLLER_REGISTRATIONS: LazyLock<[crate::manifest::ControllerRegistration; 1]> =
LazyLock::new(|| {
[crate::manifest::ControllerRegistration {
type_name: BorrowedStr::from_str("ExampleController"),
vtable: controller_vtable::<ExampleController>(),
}]
});
static AMBIGUOUS_ACTOR_REGISTRATIONS: LazyLock<[ActorRegistration; 1]> = LazyLock::new(|| {
[ActorRegistration {
type_name: BorrowedStr::from_str("DuplicateType"),
vtable: actor_vtable::<ExampleActor>(),
}]
});
static AMBIGUOUS_STRATEGY_REGISTRATIONS: LazyLock<[StrategyRegistration; 1]> =
LazyLock::new(|| {
[StrategyRegistration {
type_name: BorrowedStr::from_str("DuplicateType"),
vtable: strategy_vtable::<ExampleStrategy>(),
}]
});
static AMBIGUOUS_CONTROLLER_REGISTRATIONS: LazyLock<
[crate::manifest::ControllerRegistration; 1],
> = LazyLock::new(|| {
[crate::manifest::ControllerRegistration {
type_name: BorrowedStr::from_str("DuplicateType"),
vtable: controller_vtable::<ExampleController>(),
}]
});
fn manifest(
actors: Slice<'static, ActorRegistration>,
strategies: Slice<'static, StrategyRegistration>,
controllers: Slice<'static, crate::manifest::ControllerRegistration>,
) -> PluginManifest {
PluginManifest {
abi_version: NAUTILUS_PLUGIN_ABI_VERSION,
plugin_name: BorrowedStr::from_str("test-plugin"),
plugin_vendor: BorrowedStr::from_str("nautech"),
plugin_version: BorrowedStr::from_str("0.0.0"),
build_id: PluginBuildId::current(),
custom_data: Slice::empty(),
actors,
strategies,
controllers,
}
}
#[rstest]
fn configured_entry_resolves_actor_by_type_name() {
let manifest = manifest(
Slice::from_slice(&*ACTOR_REGISTRATIONS),
Slice::from_slice(&*STRATEGY_REGISTRATIONS),
Slice::from_slice(&*CONTROLLER_REGISTRATIONS),
);
let manifest = ValidatedPluginManifest::new(&manifest)
.expect("configured actor lookup uses a loader-valid manifest");
let entry = configured_entry(manifest, "./libexample.so", "ExampleActor").unwrap();
let ConfiguredPluginEntry::Actor(entry) = entry else {
panic!("expected actor entry");
};
assert_eq!(entry.plugin_name, "test-plugin");
assert_eq!(entry.type_name, "ExampleActor");
assert_eq!(entry.vtable.as_ptr(), ACTOR_REGISTRATIONS[0].vtable);
}
#[rstest]
fn configured_entry_resolves_strategy_by_type_name() {
let manifest = manifest(
Slice::from_slice(&*ACTOR_REGISTRATIONS),
Slice::from_slice(&*STRATEGY_REGISTRATIONS),
Slice::from_slice(&*CONTROLLER_REGISTRATIONS),
);
let manifest = ValidatedPluginManifest::new(&manifest)
.expect("configured strategy lookup uses a loader-valid manifest");
let entry = configured_entry(manifest, "./libexample.so", "ExampleStrategy").unwrap();
let ConfiguredPluginEntry::Strategy(entry) = entry else {
panic!("expected strategy entry");
};
assert_eq!(entry.plugin_name, "test-plugin");
assert_eq!(entry.type_name, "ExampleStrategy");
assert_eq!(entry.vtable.as_ptr(), STRATEGY_REGISTRATIONS[0].vtable);
}
#[rstest]
fn configured_entry_resolves_controller_by_type_name() {
let manifest = manifest(
Slice::from_slice(&*ACTOR_REGISTRATIONS),
Slice::from_slice(&*STRATEGY_REGISTRATIONS),
Slice::from_slice(&*CONTROLLER_REGISTRATIONS),
);
let manifest = ValidatedPluginManifest::new(&manifest)
.expect("configured controller lookup uses a loader-valid manifest");
let entry = configured_entry(manifest, "./libexample.so", "ExampleController").unwrap();
let ConfiguredPluginEntry::Controller(entry) = entry else {
panic!("expected controller entry");
};
assert_eq!(entry.plugin_name, "test-plugin");
assert_eq!(entry.type_name, "ExampleController");
assert_eq!(entry.vtable.as_ptr(), CONTROLLER_REGISTRATIONS[0].vtable);
}
#[rstest]
fn configured_entry_rejects_missing_type_name() {
let manifest = manifest(
Slice::from_slice(&*ACTOR_REGISTRATIONS),
Slice::from_slice(&*STRATEGY_REGISTRATIONS),
Slice::from_slice(&*CONTROLLER_REGISTRATIONS),
);
let manifest = ValidatedPluginManifest::new(&manifest)
.expect("missing configured type test uses a loader-valid manifest");
let error = match configured_entry(manifest, "./libexample.so", "MissingType") {
Ok(_) => panic!("configured entry should reject missing type"),
Err(e) => e.to_string(),
};
assert!(error.contains("does not expose actor, strategy, or controller type"));
assert!(error.contains("MissingType"));
}
#[rstest]
fn validated_manifest_rejects_ambiguous_type_name() {
let manifest = manifest(
Slice::from_slice(&*AMBIGUOUS_ACTOR_REGISTRATIONS),
Slice::from_slice(&*AMBIGUOUS_STRATEGY_REGISTRATIONS),
Slice::empty(),
);
let validation_error = ValidatedPluginManifest::new(&manifest)
.expect_err("loader rejects ambiguous manifest type names");
assert!(
validation_error
.to_string()
.contains("type name 'DuplicateType' appears in both actors[0] and strategies[0]")
);
}
#[rstest]
fn validated_manifest_rejects_controller_ambiguous_type_name() {
let manifest = manifest(
Slice::from_slice(&*AMBIGUOUS_ACTOR_REGISTRATIONS),
Slice::empty(),
Slice::from_slice(&*AMBIGUOUS_CONTROLLER_REGISTRATIONS),
);
let validation_error = ValidatedPluginManifest::new(&manifest)
.expect_err("loader rejects ambiguous controller manifest type names");
assert!(
validation_error
.to_string()
.contains("type name 'DuplicateType' appears in both actors[0] and controllers[0]")
);
}
#[rstest]
fn validated_manifest_rejects_strategy_controller_ambiguous_type_name() {
let manifest = manifest(
Slice::empty(),
Slice::from_slice(&*AMBIGUOUS_STRATEGY_REGISTRATIONS),
Slice::from_slice(&*AMBIGUOUS_CONTROLLER_REGISTRATIONS),
);
let validation_error = ValidatedPluginManifest::new(&manifest)
.expect_err("loader rejects ambiguous strategy/controller manifest type names");
assert!(validation_error.to_string().contains(
"type name 'DuplicateType' appears in both strategies[0] and controllers[0]"
));
}
}