Skip to main content

platform_module/
host.rs

1use crate::{LinkedBinding, Module, ModuleManifest};
2use platform_core::{AppContext, AppResult, Migration};
3use std::any::{Any, TypeId};
4use std::sync::Arc;
5
6#[derive(Clone)]
7pub struct HostContribution {
8    type_id: TypeId,
9    value: Arc<dyn Any + Send + Sync>,
10}
11
12impl std::fmt::Debug for HostContribution {
13    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        formatter
15            .debug_struct("HostContribution")
16            .field("type_id", &self.type_id)
17            .finish_non_exhaustive()
18    }
19}
20
21impl HostContribution {
22    pub fn typed<T>(value: T) -> Self
23    where
24        T: Send + Sync + 'static,
25    {
26        Self {
27            type_id: TypeId::of::<T>(),
28            value: Arc::new(value),
29        }
30    }
31
32    pub fn get<T>(&self) -> Option<&T>
33    where
34        T: Send + Sync + 'static,
35    {
36        (self.type_id == TypeId::of::<T>())
37            .then(|| self.value.downcast_ref::<T>())
38            .flatten()
39    }
40}
41
42#[derive(Debug, Clone)]
43pub struct HostLinkedModule {
44    pub module_name: &'static str,
45    pub manifest: fn() -> ModuleManifest,
46    pub load: Option<fn(&AppContext) -> Module>,
47    try_load: Option<fn(&AppContext) -> AppResult<Module>>,
48    pub http_binding: Option<fn() -> LinkedBinding>,
49    pub migrations: &'static [Migration],
50    contributions: Vec<HostContribution>,
51}
52
53impl HostLinkedModule {
54    #[must_use]
55    pub fn manifest_only(
56        module_name: &'static str,
57        manifest: fn() -> ModuleManifest,
58        migrations: &'static [Migration],
59    ) -> Self {
60        Self {
61            module_name,
62            manifest,
63            load: None,
64            try_load: None,
65            http_binding: None,
66            migrations,
67            contributions: Vec::new(),
68        }
69    }
70
71    #[must_use]
72    pub fn linked(
73        module_name: &'static str,
74        manifest: fn() -> ModuleManifest,
75        load: fn(&AppContext) -> Module,
76        migrations: &'static [Migration],
77    ) -> Self {
78        Self {
79            module_name,
80            manifest,
81            load: Some(load),
82            try_load: None,
83            http_binding: None,
84            migrations,
85            contributions: Vec::new(),
86        }
87    }
88
89    /// Compose a linked Module whose context-bound setup can reject invalid
90    /// deployment configuration without panicking.
91    #[must_use]
92    pub fn try_linked(
93        module_name: &'static str,
94        manifest: fn() -> ModuleManifest,
95        load: fn(&AppContext) -> AppResult<Module>,
96        migrations: &'static [Migration],
97    ) -> Self {
98        Self {
99            module_name,
100            manifest,
101            load: None,
102            try_load: Some(load),
103            http_binding: None,
104            migrations,
105            contributions: Vec::new(),
106        }
107    }
108
109    #[doc(hidden)]
110    pub fn try_load_module(&self, context: &AppContext) -> AppResult<Module> {
111        if let Some(load) = self.try_load {
112            return load(context);
113        }
114        Ok(match self.load {
115            Some(load) => load(context),
116            None => Module::linked((self.manifest)(), LinkedBinding::builder().build()),
117        })
118    }
119
120    #[must_use]
121    pub fn with_http_binding(mut self, http_binding: fn() -> LinkedBinding) -> Self {
122        self.http_binding = Some(http_binding);
123        self
124    }
125
126    #[must_use]
127    pub fn with_contribution<T>(mut self, contribution: T) -> Self
128    where
129        T: Send + Sync + 'static,
130    {
131        self.contributions
132            .push(HostContribution::typed(contribution));
133        self
134    }
135
136    pub fn contributions<T>(&self) -> impl Iterator<Item = &T>
137    where
138        T: Send + Sync + 'static,
139    {
140        self.contributions
141            .iter()
142            .filter_map(HostContribution::get::<T>)
143    }
144}