1use crate::config::LinksConfig;
10use crate::core::events::EventBus;
11use crate::core::{EntityCreator, EntityFetcher, service::LinkService};
12use crate::events::log::EventLog;
13use crate::events::sinks::SinkRegistry;
14use crate::events::sinks::device_tokens::DeviceTokenStore;
15use crate::events::sinks::in_app::NotificationStore;
16use crate::events::sinks::preferences::NotificationPreferencesStore;
17use crate::links::registry::LinkRouteRegistry;
18use crate::server::entity_registry::EntityRegistry;
19use anyhow::Result;
20use std::collections::HashMap;
21use std::sync::Arc;
22
23pub struct ServerHost {
45 pub config: Arc<LinksConfig>,
47
48 pub link_service: Arc<dyn LinkService>,
50
51 pub registry: Arc<LinkRouteRegistry>,
53
54 pub entity_registry: EntityRegistry,
56
57 pub entity_fetchers: Arc<HashMap<String, Arc<dyn EntityFetcher>>>,
59
60 pub entity_creators: Arc<HashMap<String, Arc<dyn EntityCreator>>>,
62
63 pub event_bus: Option<Arc<EventBus>>,
68
69 pub event_log: Option<Arc<dyn EventLog>>,
74
75 pub sink_registry: Option<Arc<SinkRegistry>>,
80
81 pub notification_store: Option<Arc<NotificationStore>>,
86
87 pub device_token_store: Option<Arc<DeviceTokenStore>>,
92
93 pub preferences_store: Option<Arc<NotificationPreferencesStore>>,
98}
99
100impl ServerHost {
101 pub fn from_builder_components(
118 link_service: Arc<dyn LinkService>,
119 config: LinksConfig,
120 entity_registry: EntityRegistry,
121 fetchers: HashMap<String, Arc<dyn EntityFetcher>>,
122 creators: HashMap<String, Arc<dyn EntityCreator>>,
123 ) -> Result<Self> {
124 let config = Arc::new(config);
125 let registry = Arc::new(LinkRouteRegistry::new(config.clone()));
126
127 Ok(Self {
128 config,
129 link_service,
130 registry,
131 entity_registry,
132 entity_fetchers: Arc::new(fetchers),
133 entity_creators: Arc::new(creators),
134 event_bus: None,
135 event_log: None,
136 sink_registry: None,
137 notification_store: None,
138 device_token_store: None,
139 preferences_store: None,
140 })
141 }
142
143 pub fn entity_types(&self) -> Vec<&str> {
145 self.entity_registry.entity_types()
146 }
147
148 pub fn is_ready(&self) -> bool {
150 !self.entity_fetchers.is_empty()
151 }
152
153 pub fn with_event_bus(mut self, event_bus: EventBus) -> Self {
155 self.event_bus = Some(Arc::new(event_bus));
156 self
157 }
158
159 pub fn event_bus(&self) -> Option<&Arc<EventBus>> {
161 self.event_bus.as_ref()
162 }
163
164 pub fn with_event_log(mut self, event_log: Arc<dyn EventLog>) -> Self {
166 self.event_log = Some(event_log);
167 self
168 }
169
170 pub fn event_log(&self) -> Option<&Arc<dyn EventLog>> {
172 self.event_log.as_ref()
173 }
174
175 pub fn with_sink_registry(mut self, registry: SinkRegistry) -> Self {
177 self.sink_registry = Some(Arc::new(registry));
178 self
179 }
180
181 pub fn sink_registry(&self) -> Option<&Arc<SinkRegistry>> {
183 self.sink_registry.as_ref()
184 }
185
186 pub fn with_notification_store(mut self, store: Arc<NotificationStore>) -> Self {
188 self.notification_store = Some(store);
189 self
190 }
191
192 pub fn notification_store(&self) -> Option<&Arc<NotificationStore>> {
194 self.notification_store.as_ref()
195 }
196
197 pub fn with_device_token_store(mut self, store: Arc<DeviceTokenStore>) -> Self {
199 self.device_token_store = Some(store);
200 self
201 }
202
203 pub fn device_token_store(&self) -> Option<&Arc<DeviceTokenStore>> {
205 self.device_token_store.as_ref()
206 }
207
208 pub fn with_preferences_store(mut self, store: Arc<NotificationPreferencesStore>) -> Self {
210 self.preferences_store = Some(store);
211 self
212 }
213
214 pub fn preferences_store(&self) -> Option<&Arc<NotificationPreferencesStore>> {
216 self.preferences_store.as_ref()
217 }
218
219 #[cfg(test)]
224 pub fn minimal_for_test() -> Self {
225 use crate::core::link::LinkEntity;
226
227 struct NoopLinkService;
228
229 #[async_trait::async_trait]
230 impl crate::core::service::LinkService for NoopLinkService {
231 async fn create(&self, link: LinkEntity) -> anyhow::Result<LinkEntity> {
232 Ok(link)
233 }
234 async fn get(&self, _id: &uuid::Uuid) -> anyhow::Result<Option<LinkEntity>> {
235 Ok(None)
236 }
237 async fn list(&self) -> anyhow::Result<Vec<LinkEntity>> {
238 Ok(vec![])
239 }
240 async fn find_by_source(
241 &self,
242 _source_id: &uuid::Uuid,
243 _link_type: Option<&str>,
244 _target_type: Option<&str>,
245 ) -> anyhow::Result<Vec<LinkEntity>> {
246 Ok(vec![])
247 }
248 async fn find_by_target(
249 &self,
250 _target_id: &uuid::Uuid,
251 _link_type: Option<&str>,
252 _source_type: Option<&str>,
253 ) -> anyhow::Result<Vec<LinkEntity>> {
254 Ok(vec![])
255 }
256 async fn update(
257 &self,
258 _id: &uuid::Uuid,
259 link: LinkEntity,
260 ) -> anyhow::Result<LinkEntity> {
261 Ok(link)
262 }
263 async fn delete(&self, _id: &uuid::Uuid) -> anyhow::Result<()> {
264 Ok(())
265 }
266 async fn delete_by_entity(&self, _entity_id: &uuid::Uuid) -> anyhow::Result<()> {
267 Ok(())
268 }
269 }
270
271 let config = LinksConfig {
272 entities: vec![],
273 links: vec![],
274 validation_rules: None,
275 events: None,
276 sinks: None,
277 };
278 let config = Arc::new(config);
279 let registry = Arc::new(LinkRouteRegistry::new(config.clone()));
280
281 Self {
282 config,
283 link_service: Arc::new(NoopLinkService),
284 registry,
285 entity_registry: EntityRegistry::new(),
286 entity_fetchers: Arc::new(HashMap::new()),
287 entity_creators: Arc::new(HashMap::new()),
288 event_bus: None,
289 event_log: None,
290 sink_registry: None,
291 notification_store: None,
292 device_token_store: None,
293 preferences_store: None,
294 }
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::config::{EntityAuthConfig, EntityConfig};
302 use crate::core::link::LinkEntity;
303
304 struct MockLinkService;
306
307 #[async_trait::async_trait]
308 impl crate::core::service::LinkService for MockLinkService {
309 async fn create(&self, link: LinkEntity) -> anyhow::Result<LinkEntity> {
310 Ok(link)
311 }
312 async fn get(&self, _id: &uuid::Uuid) -> anyhow::Result<Option<LinkEntity>> {
313 Ok(None)
314 }
315 async fn list(&self) -> anyhow::Result<Vec<LinkEntity>> {
316 Ok(vec![])
317 }
318 async fn find_by_source(
319 &self,
320 _source_id: &uuid::Uuid,
321 _link_type: Option<&str>,
322 _target_type: Option<&str>,
323 ) -> anyhow::Result<Vec<LinkEntity>> {
324 Ok(vec![])
325 }
326 async fn find_by_target(
327 &self,
328 _target_id: &uuid::Uuid,
329 _link_type: Option<&str>,
330 _source_type: Option<&str>,
331 ) -> anyhow::Result<Vec<LinkEntity>> {
332 Ok(vec![])
333 }
334 async fn update(&self, _id: &uuid::Uuid, link: LinkEntity) -> anyhow::Result<LinkEntity> {
335 Ok(link)
336 }
337 async fn delete(&self, _id: &uuid::Uuid) -> anyhow::Result<()> {
338 Ok(())
339 }
340 async fn delete_by_entity(&self, _entity_id: &uuid::Uuid) -> anyhow::Result<()> {
341 Ok(())
342 }
343 }
344
345 fn test_config() -> LinksConfig {
346 LinksConfig {
347 entities: vec![EntityConfig {
348 singular: "order".to_string(),
349 plural: "orders".to_string(),
350 auth: EntityAuthConfig::default(),
351 }],
352 links: vec![],
353 validation_rules: None,
354 events: None,
355 sinks: None,
356 }
357 }
358
359 fn make_host() -> ServerHost {
360 ServerHost::from_builder_components(
361 Arc::new(MockLinkService),
362 test_config(),
363 EntityRegistry::new(),
364 HashMap::new(),
365 HashMap::new(),
366 )
367 .expect("should build host")
368 }
369
370 #[test]
371 fn test_from_builder_components_creates_host() {
372 let host = make_host();
373 assert!(host.event_bus.is_none());
374 }
375
376 #[test]
377 fn test_entity_types_empty_registry() {
378 let host = make_host();
379 assert!(host.entity_types().is_empty());
380 }
381
382 #[test]
383 fn test_is_ready_no_fetchers_returns_false() {
384 let host = make_host();
385 assert!(!host.is_ready());
386 }
387
388 #[test]
389 fn test_with_event_bus_sets_bus() {
390 let host = make_host();
391 let bus = EventBus::new(16);
392 let host = host.with_event_bus(bus);
393 assert!(host.event_bus().is_some());
394 }
395
396 #[test]
397 fn test_event_bus_none_by_default() {
398 let host = make_host();
399 assert!(host.event_bus().is_none());
400 }
401
402 #[test]
403 fn test_config_accessible_from_host() {
404 let host = make_host();
405 assert_eq!(host.config.entities.len(), 1);
406 assert_eq!(host.config.entities[0].singular, "order");
407 }
408
409 #[test]
410 fn test_registry_built_from_config() {
411 let host = make_host();
412 let routes = host.registry.list_routes_for_entity("order");
414 assert!(routes.is_empty());
416 }
417
418 #[test]
419 fn test_is_ready_with_fetchers_returns_true() {
420 use crate::core::EntityFetcher;
421
422 struct StubFetcher;
423
424 #[async_trait::async_trait]
425 impl EntityFetcher for StubFetcher {
426 async fn fetch_as_json(
427 &self,
428 _entity_id: &uuid::Uuid,
429 ) -> anyhow::Result<serde_json::Value> {
430 Ok(serde_json::json!({}))
431 }
432 }
433
434 let mut fetchers: HashMap<String, Arc<dyn EntityFetcher>> = HashMap::new();
435 fetchers.insert("order".to_string(), Arc::new(StubFetcher));
436
437 let host = ServerHost::from_builder_components(
438 Arc::new(MockLinkService),
439 test_config(),
440 EntityRegistry::new(),
441 fetchers,
442 HashMap::new(),
443 )
444 .expect("should build host");
445
446 assert!(host.is_ready());
447 }
448
449 #[test]
450 fn test_entity_creators_accessible() {
451 let host = make_host();
452 assert!(host.entity_creators.is_empty());
453 }
454
455 #[test]
456 fn test_link_service_accessible() {
457 let host = make_host();
458 let _ = host.link_service.clone();
460 }
461
462 #[test]
463 fn test_new_fields_none_by_default() {
464 let host = make_host();
465 assert!(host.event_log().is_none());
466 assert!(host.sink_registry().is_none());
467 assert!(host.notification_store().is_none());
468 assert!(host.device_token_store().is_none());
469 assert!(host.preferences_store().is_none());
470 }
471
472 #[test]
473 fn test_with_notification_store() {
474 use crate::events::sinks::in_app::NotificationStore;
475 let host = make_host();
476 let store = Arc::new(NotificationStore::new());
477 let host = host.with_notification_store(store);
478 assert!(host.notification_store().is_some());
479 }
480
481 #[test]
482 fn test_with_device_token_store() {
483 use crate::events::sinks::device_tokens::DeviceTokenStore;
484 let host = make_host();
485 let store = Arc::new(DeviceTokenStore::new());
486 let host = host.with_device_token_store(store);
487 assert!(host.device_token_store().is_some());
488 }
489
490 #[test]
491 fn test_with_preferences_store() {
492 use crate::events::sinks::preferences::NotificationPreferencesStore;
493 let host = make_host();
494 let store = Arc::new(NotificationPreferencesStore::new());
495 let host = host.with_preferences_store(store);
496 assert!(host.preferences_store().is_some());
497 }
498
499 #[test]
500 fn test_with_sink_registry() {
501 use crate::events::sinks::SinkRegistry;
502 let host = make_host();
503 let registry = SinkRegistry::new();
504 let host = host.with_sink_registry(registry);
505 assert!(host.sink_registry().is_some());
506 }
507
508 #[test]
509 fn test_with_event_log() {
510 use crate::events::memory::InMemoryEventLog;
511 let host = make_host();
512 let log = Arc::new(InMemoryEventLog::new());
513 let host = host.with_event_log(log);
514 assert!(host.event_log().is_some());
515 }
516}