geam_core/host/
component.rs1use crate::host::{HostProfile, HostProviderModule, HostRegistrationError};
2use ecow::EcoString;
3use std::fmt::{self, Display, Formatter};
4
5mod configuration;
6
7pub use configuration::{HostProviderConfiguration, HostProviderConfigurationValue};
8
9pub trait HostProviderComponent: Send + Sync + 'static {
11 const ID: &'static str;
13
14 type Stores: Default + 'static;
16
17 type RunState: 'static;
19}
20
21pub trait HostProviderComponentInitialization: HostProviderComponent {
23 fn initialize(
25 configuration: &HostProviderConfiguration,
26 ) -> Result<Self::RunState, HostProviderInitializationError>;
27}
28
29pub 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
39pub trait HostProviderComponentRegistration<Profile>: HostProviderComponent
41where
42 Profile: HostComponentProfile<Self>,
43 Self: Sized,
44{
45 fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>;
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct HostProviderInitializationError {
51 component_id: EcoString,
52 reason: EcoString,
53}
54
55impl HostProviderInitializationError {
56 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}