use std::collections::BTreeMap;
use syncbat::{CheckoutResult, Core, OperationDescriptor, RuntimeError};
use crate::composition::HostCompositionManifest;
use crate::descriptor::{HookDescriptor, HookPhase};
use crate::error::{HookFailure, HostRuntimeError};
use crate::event_payload_binding::EventPayloadBinding;
use crate::identity::{HostFingerprint, InterfaceFingerprint};
use crate::module::{BoxedHook, BoxedJob};
use crate::schema::SchemaRegistry;
use crate::subscription::SubscriptionDescriptor;
use crate::supervisor::Supervisor;
pub struct HostHook {
module: String,
descriptor: HookDescriptor,
hook: BoxedHook,
}
impl HostHook {
pub(crate) fn new(module: String, descriptor: HookDescriptor, hook: BoxedHook) -> Self {
Self {
module,
descriptor,
hook,
}
}
pub(crate) fn phase(&self) -> HookPhase {
self.descriptor.phase
}
pub(crate) fn order_key(&self) -> (u32, &str, &str) {
(
self.descriptor.order,
self.module.as_str(),
self.descriptor.name.as_str(),
)
}
fn run(&self) -> Result<(), HookFailure> {
self.hook.run().map_err(|detail| HookFailure {
phase: self.descriptor.phase,
module: self.module.clone(),
hook: self.descriptor.name.clone(),
detail,
})
}
}
pub struct Host {
core: Core,
supervisor: Supervisor,
fingerprint: HostFingerprint,
interface_fingerprint: InterfaceFingerprint,
operations: Vec<OperationDescriptor>,
subscriptions: Vec<(String, SubscriptionDescriptor)>,
event_payload_bindings: Vec<(String, EventPayloadBinding)>,
composition_schemas: HostCompositionManifest,
schema_registry: SchemaRegistry,
startup: Vec<HostHook>,
shutdown: Vec<HostHook>,
job_factories: BTreeMap<String, BoxedJob>,
started: bool,
}
pub(crate) struct HostParts {
pub(crate) core: Core,
pub(crate) supervisor: Supervisor,
pub(crate) fingerprint: HostFingerprint,
pub(crate) interface_fingerprint: InterfaceFingerprint,
pub(crate) operations: Vec<OperationDescriptor>,
pub(crate) subscriptions: Vec<(String, SubscriptionDescriptor)>,
pub(crate) event_payload_bindings: Vec<(String, EventPayloadBinding)>,
pub(crate) composition_schemas: HostCompositionManifest,
pub(crate) schema_registry: SchemaRegistry,
pub(crate) startup: Vec<HostHook>,
pub(crate) shutdown: Vec<HostHook>,
pub(crate) job_factories: BTreeMap<String, BoxedJob>,
}
impl Host {
pub(crate) fn new(parts: HostParts) -> Self {
let HostParts {
core,
supervisor,
fingerprint,
interface_fingerprint,
operations,
subscriptions,
event_payload_bindings,
composition_schemas,
schema_registry,
startup,
shutdown,
job_factories,
} = parts;
Self {
core,
supervisor,
fingerprint,
interface_fingerprint,
operations,
subscriptions,
event_payload_bindings,
composition_schemas,
schema_registry,
startup,
shutdown,
job_factories,
started: false,
}
}
#[must_use]
pub fn fingerprint(&self) -> HostFingerprint {
self.fingerprint
}
#[must_use]
pub fn interface_fingerprint(&self) -> InterfaceFingerprint {
self.interface_fingerprint
}
pub fn operations(&self) -> impl Iterator<Item = &OperationDescriptor> {
self.operations.iter()
}
pub fn subscriptions(&self) -> impl Iterator<Item = (&str, &SubscriptionDescriptor)> {
self.subscriptions
.iter()
.map(|(module, descriptor)| (module.as_str(), descriptor))
}
pub fn event_payload_bindings(&self) -> impl Iterator<Item = (&str, &EventPayloadBinding)> {
self.event_payload_bindings
.iter()
.map(|(module, binding)| (module.as_str(), binding))
}
#[must_use]
pub fn composition_schemas(&self) -> &HostCompositionManifest {
&self.composition_schemas
}
#[must_use]
pub fn schema_registry(&self) -> &SchemaRegistry {
&self.schema_registry
}
#[must_use]
pub fn is_started(&self) -> bool {
self.started
}
pub fn start(&mut self) -> Result<(), HostRuntimeError> {
for hook in &self.startup {
hook.run().map_err(HostRuntimeError::StartupHook)?;
}
self.started = true;
Ok(())
}
pub fn invoke(
&mut self,
operation: impl AsRef<str>,
input: Vec<u8>,
) -> Result<CheckoutResult, RuntimeError> {
self.core.invoke(operation, input)
}
pub fn spawn_job(&mut self, kind: impl AsRef<str>) -> Result<(), HostRuntimeError> {
let kind = kind.as_ref();
let factory =
self.job_factories
.get(kind)
.ok_or_else(|| HostRuntimeError::UnknownJobKind {
kind: kind.to_owned(),
})?;
let body = factory.make();
self.supervisor.spawn(kind.to_owned(), None, body)?;
Ok(())
}
#[must_use]
pub fn supervisor(&self) -> &Supervisor {
&self.supervisor
}
pub fn shutdown(&mut self) -> Result<(), HostRuntimeError> {
let mut first_failure = None;
for hook in &self.shutdown {
if let Err(failure) = hook.run() {
if first_failure.is_none() {
first_failure = Some(failure);
}
}
}
let _outcomes = self.supervisor.join_all();
self.started = false;
match first_failure {
Some(failure) => Err(HostRuntimeError::ShutdownHook(failure)),
None => Ok(()),
}
}
}