use crate::host::{HostProfile, HostProviderModule, HostRegistrationError};
use ecow::EcoString;
use std::fmt::{self, Display, Formatter};
mod configuration;
pub use configuration::{HostProviderConfiguration, HostProviderConfigurationValue};
pub trait HostProviderComponent: Send + Sync + 'static {
const ID: &'static str;
type Stores: Default + 'static;
type RunState: 'static;
fn initialize(
configuration: &HostProviderConfiguration,
) -> Result<Self::RunState, HostProviderInitializationError>;
}
pub trait HostComponentProfile<Component>: HostProfile
where
Component: HostProviderComponent,
{
fn component_stores(stores: &Self::ExternalStores) -> &Component::Stores;
fn component_state(state: &mut Self::RunState) -> &mut Component::RunState;
}
pub trait HostProviderComponentRegistration<Profile>: HostProviderComponent
where
Profile: HostComponentProfile<Self>,
Self: Sized,
{
fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostProviderInitializationError {
component_id: EcoString,
reason: EcoString,
}
impl HostProviderInitializationError {
pub fn for_component<Component>(reason: impl Into<EcoString>) -> Self
where
Component: HostProviderComponent,
{
Self {
component_id: Component::ID.into(),
reason: reason.into(),
}
}
pub fn component_id(&self) -> &str {
&self.component_id
}
pub fn reason(&self) -> &str {
&self.reason
}
}
impl Display for HostProviderInitializationError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"could not initialize host provider component {}: {}",
self.component_id, self.reason,
)
}
}
impl std::error::Error for HostProviderInitializationError {}
#[cfg(test)]
mod tests {
use super::{
HostComponentProfile, HostProviderComponent, HostProviderConfiguration,
HostProviderInitializationError,
};
use crate::host::HostProfile;
struct FirstComponent;
struct SecondComponent;
struct AggregateProfile;
#[derive(Default)]
struct AggregateStores {
first: Vec<u8>,
second: Vec<u16>,
}
struct AggregateState {
first: String,
second: usize,
}
impl HostProviderComponent for FirstComponent {
const ID: &'static str = "first";
type Stores = Vec<u8>;
type RunState = String;
fn initialize(
_configuration: &HostProviderConfiguration,
) -> Result<Self::RunState, HostProviderInitializationError> {
Ok("ready".into())
}
}
impl HostProviderComponent for SecondComponent {
const ID: &'static str = "second";
type Stores = Vec<u16>;
type RunState = usize;
fn initialize(
_configuration: &HostProviderConfiguration,
) -> Result<Self::RunState, HostProviderInitializationError> {
Err(HostProviderInitializationError::for_component::<Self>(
"missing endpoint",
))
}
}
impl HostProfile for AggregateProfile {
type RunState = AggregateState;
type ExternalStores = AggregateStores;
}
impl HostComponentProfile<FirstComponent> for AggregateProfile {
fn component_stores(stores: &Self::ExternalStores) -> &Vec<u8> {
&stores.first
}
fn component_state(state: &mut Self::RunState) -> &mut String {
&mut state.first
}
}
impl HostComponentProfile<SecondComponent> for AggregateProfile {
fn component_stores(stores: &Self::ExternalStores) -> &Vec<u16> {
&stores.second
}
fn component_state(state: &mut Self::RunState) -> &mut usize {
&mut state.second
}
}
#[test]
fn generated_profiles_project_each_component_without_erasure() {
let stores = AggregateStores::default();
let mut state = AggregateState {
first: "initial".into(),
second: 7,
};
assert!(
<AggregateProfile as HostComponentProfile<FirstComponent>>::component_stores(&stores)
.is_empty()
);
assert!(
<AggregateProfile as HostComponentProfile<SecondComponent>>::component_stores(&stores)
.is_empty()
);
<AggregateProfile as HostComponentProfile<FirstComponent>>::component_state(&mut state)
.push_str(" first");
*<AggregateProfile as HostComponentProfile<SecondComponent>>::component_state(
&mut state,
) += 1;
assert_eq!(state.first, "initial first");
assert_eq!(state.second, 8);
}
#[test]
fn component_initialization_preserves_identity_and_owned_reason() {
let configuration = HostProviderConfiguration::empty();
assert_eq!(
FirstComponent::initialize(&configuration),
Ok("ready".into())
);
let error = SecondComponent::initialize(&configuration)
.expect_err("second component should reject missing configuration");
assert_eq!(error.component_id(), "second");
assert_eq!(error.reason(), "missing endpoint");
assert_eq!(
error.to_string(),
"could not initialize host provider component second: missing endpoint"
);
assert_eq!(error.clone(), error);
}
}