Skip to main content

geam_core/host/
component.rs

1use crate::host::{HostProfile, HostProviderModule, HostRegistrationError};
2use ecow::EcoString;
3use std::fmt::{self, Display, Formatter};
4
5mod configuration;
6
7pub use configuration::{HostProviderConfiguration, HostProviderConfigurationValue};
8
9/// A statically composed Rust provider component.
10pub trait HostProviderComponent: Send + Sync + 'static {
11    /// Stable component identity used in initialization diagnostics.
12    const ID: &'static str;
13
14    /// External stores owned by this component.
15    type Stores: Default + 'static;
16
17    /// Caller-owned mutable state used while executing this component.
18    type RunState: 'static;
19}
20
21/// Initializes one provider component from explicit read-only configuration.
22pub trait HostProviderComponentInitialization: HostProviderComponent {
23    /// Initializes caller-owned run state from explicit component configuration.
24    fn initialize(
25        configuration: &HostProviderConfiguration,
26    ) -> Result<Self::RunState, HostProviderInitializationError>;
27}
28
29/// Projects one provider component from a statically generated host profile.
30pub trait HostComponentProfile<Component>: HostProfile
31where
32    Component: HostProviderComponent,
33{
34    fn component_stores(stores: &Self::ExternalStores) -> &Component::Stores;
35
36    fn component_state(state: &mut Self::RunState) -> &mut Component::RunState;
37}
38
39/// Registers the source-backed provider modules exported by one component.
40pub trait HostProviderComponentRegistration<Profile>: HostProviderComponent
41where
42    Profile: HostComponentProfile<Self>,
43    Self: Sized,
44{
45    fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>;
46}
47
48/// Failure to initialize one statically selected provider component.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct HostProviderInitializationError {
51    component_id: EcoString,
52    reason: EcoString,
53}
54
55impl HostProviderInitializationError {
56    /// Creates an owned initialization failure for the named component type.
57    pub fn for_component<Component>(reason: impl Into<EcoString>) -> Self
58    where
59        Component: HostProviderComponent,
60    {
61        Self {
62            component_id: Component::ID.into(),
63            reason: reason.into(),
64        }
65    }
66
67    pub fn component_id(&self) -> &str {
68        &self.component_id
69    }
70
71    pub fn reason(&self) -> &str {
72        &self.reason
73    }
74}
75
76impl Display for HostProviderInitializationError {
77    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
78        write!(
79            formatter,
80            "could not initialize host provider component {}: {}",
81            self.component_id, self.reason,
82        )
83    }
84}
85
86impl std::error::Error for HostProviderInitializationError {}
87
88#[cfg(test)]
89mod tests {
90    use super::{
91        HostComponentProfile, HostProviderComponent, HostProviderComponentInitialization,
92        HostProviderConfiguration, HostProviderInitializationError,
93    };
94    use crate::host::HostProfile;
95
96    struct FirstComponent;
97    struct SecondComponent;
98    struct AggregateProfile;
99
100    #[derive(Default)]
101    struct AggregateStores {
102        first: Vec<u8>,
103        second: Vec<u16>,
104    }
105
106    struct AggregateState {
107        first: String,
108        second: usize,
109    }
110
111    impl HostProviderComponent for FirstComponent {
112        const ID: &'static str = "first";
113        type Stores = Vec<u8>;
114        type RunState = String;
115    }
116
117    impl HostProviderComponentInitialization for FirstComponent {
118        fn initialize(
119            _configuration: &HostProviderConfiguration,
120        ) -> Result<Self::RunState, HostProviderInitializationError> {
121            Ok("ready".into())
122        }
123    }
124
125    impl HostProviderComponent for SecondComponent {
126        const ID: &'static str = "second";
127        type Stores = Vec<u16>;
128        type RunState = usize;
129    }
130
131    impl HostProviderComponentInitialization for SecondComponent {
132        fn initialize(
133            _configuration: &HostProviderConfiguration,
134        ) -> Result<Self::RunState, HostProviderInitializationError> {
135            Err(HostProviderInitializationError::for_component::<Self>(
136                "missing endpoint",
137            ))
138        }
139    }
140
141    impl HostProfile for AggregateProfile {
142        type RunState = AggregateState;
143        type ExternalStores = AggregateStores;
144    }
145
146    impl HostComponentProfile<FirstComponent> for AggregateProfile {
147        fn component_stores(stores: &Self::ExternalStores) -> &Vec<u8> {
148            &stores.first
149        }
150
151        fn component_state(state: &mut Self::RunState) -> &mut String {
152            &mut state.first
153        }
154    }
155
156    impl HostComponentProfile<SecondComponent> for AggregateProfile {
157        fn component_stores(stores: &Self::ExternalStores) -> &Vec<u16> {
158            &stores.second
159        }
160
161        fn component_state(state: &mut Self::RunState) -> &mut usize {
162            &mut state.second
163        }
164    }
165
166    #[test]
167    fn generated_profiles_project_each_component_without_erasure() {
168        let stores = AggregateStores::default();
169        let mut state = AggregateState {
170            first: "initial".into(),
171            second: 7,
172        };
173
174        assert!(
175            <AggregateProfile as HostComponentProfile<FirstComponent>>::component_stores(&stores)
176                .is_empty()
177        );
178        assert!(
179            <AggregateProfile as HostComponentProfile<SecondComponent>>::component_stores(&stores)
180                .is_empty()
181        );
182        <AggregateProfile as HostComponentProfile<FirstComponent>>::component_state(&mut state)
183            .push_str(" first");
184        *<AggregateProfile as HostComponentProfile<SecondComponent>>::component_state(
185            &mut state,
186        ) += 1;
187
188        assert_eq!(state.first, "initial first");
189        assert_eq!(state.second, 8);
190    }
191
192    #[test]
193    fn component_initialization_preserves_identity_and_owned_reason() {
194        let configuration = HostProviderConfiguration::empty();
195
196        assert_eq!(
197            FirstComponent::initialize(&configuration),
198            Ok("ready".into())
199        );
200        let error = SecondComponent::initialize(&configuration)
201            .expect_err("second component should reject missing configuration");
202        assert_eq!(error.component_id(), "second");
203        assert_eq!(error.reason(), "missing endpoint");
204        assert_eq!(
205            error.to_string(),
206            "could not initialize host provider component second: missing endpoint"
207        );
208        assert_eq!(error.clone(), error);
209    }
210}