Skip to main content

coil_runtime/builder/
state.rs

1use super::*;
2use crate::builder::assembly;
3use coil_i18n::TranslationCatalog;
4use coil_template::TemplateDefinition;
5use std::env;
6use std::path::Path;
7use std::path::PathBuf;
8
9#[derive(Default)]
10pub struct Builder {
11    modules: Vec<Box<dyn PlatformModule>>,
12    customer_plugins: Vec<Box<dyn CustomerBackendPlugin>>,
13}
14
15impl Builder {
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    pub fn register_module<M>(mut self, module: M) -> Self
21    where
22        M: PlatformModule + 'static,
23    {
24        self.modules.push(Box::new(module));
25        self
26    }
27
28    pub fn register_customer_plugin<C>(mut self, plugin: C) -> Self
29    where
30        C: CustomerBackendPlugin,
31    {
32        self.customer_plugins.push(Box::new(plugin));
33        self
34    }
35
36    pub fn build_from_paths(
37        self,
38        app_root: impl AsRef<Path>,
39        config_path: impl AsRef<Path>,
40    ) -> Result<RuntimePlan, RuntimeBootstrapError> {
41        let bootstrap = CustomerRootBootstrapInputs::from_paths(app_root, config_path)?;
42        self.build_from_bootstrap_inputs(bootstrap)
43    }
44
45    pub fn run_from_env(self) -> Result<(), RuntimeBootstrapError> {
46        let bind_override = env::var("COIL_BIND").ok();
47        self.build_from_bootstrap_inputs(CustomerRootBootstrapInputs::from_env()?)?
48            .serve_from_env(bind_override)
49    }
50
51    fn build_from_bootstrap_inputs(
52        self,
53        bootstrap: CustomerRootBootstrapInputs,
54    ) -> Result<RuntimePlan, RuntimeBootstrapError> {
55        let mut builder = RuntimeBuilder::new(bootstrap.config, bootstrap.auth_package)
56            .with_template_root(bootstrap.app_root)
57            .with_translation_catalogs(bootstrap.translation_catalogs);
58        for module in self.modules {
59            builder = builder.with_boxed_module(module);
60        }
61        for plugin in self.customer_plugins {
62            builder = builder.with_boxed_customer_plugin(plugin);
63        }
64        builder = builder
65            .resolve_enabled_customer_modules(&bootstrap.enabled_modules)
66            .map_err(RuntimeBootstrapError::Build)?;
67        builder.build().map_err(RuntimeBootstrapError::Build)
68    }
69}
70
71pub struct RuntimeBuilder<P> {
72    config: PlatformConfig,
73    auth_package: P,
74    modules: Vec<Box<dyn PlatformModule>>,
75    customer_plugins: Vec<Box<dyn CustomerBackendPlugin>>,
76    translation_catalogs: Vec<TranslationCatalog>,
77    extensions: Vec<InstalledExtension>,
78    templates: Vec<TemplateDefinition>,
79    template_roots: Vec<PathBuf>,
80    storage_policies: StoragePolicySet,
81    routes: Vec<RouteDefinition>,
82    handlers: Vec<HandlerDefinition>,
83    feature_flags: Vec<FeatureFlag>,
84    maintenance_mode: Option<MaintenanceMode>,
85}
86
87pub(crate) struct RuntimeBuilderParts<P> {
88    pub(crate) config: PlatformConfig,
89    pub(crate) auth_package: P,
90    pub(crate) modules: Vec<Box<dyn PlatformModule>>,
91    pub(crate) customer_plugins: Vec<Box<dyn CustomerBackendPlugin>>,
92    pub(crate) translation_catalogs: Vec<TranslationCatalog>,
93    pub(crate) extensions: Vec<InstalledExtension>,
94    pub(crate) templates: Vec<TemplateDefinition>,
95    pub(crate) template_roots: Vec<PathBuf>,
96    pub(crate) storage_policies: StoragePolicySet,
97    pub(crate) routes: Vec<RouteDefinition>,
98    pub(crate) handlers: Vec<HandlerDefinition>,
99    pub(crate) feature_flags: Vec<FeatureFlag>,
100    pub(crate) maintenance_mode: Option<MaintenanceMode>,
101}
102
103impl<P> RuntimeBuilder<P>
104where
105    P: AuthModelPackage + 'static,
106{
107    /// Lower-level runtime builder for direct platform/runtime composition.
108    ///
109    /// Customer-root binaries should prefer `RuntimeBuilder::for_customer_root(...)` so the
110    /// linked-customer composition path stays explicit in code.
111    pub fn new(config: PlatformConfig, auth_package: P) -> Self {
112        Self {
113            config,
114            auth_package,
115            modules: Vec::new(),
116            customer_plugins: Vec::new(),
117            translation_catalogs: Vec::new(),
118            extensions: Vec::new(),
119            templates: Vec::new(),
120            template_roots: Vec::new(),
121            storage_policies: StoragePolicySet::default(),
122            routes: Vec::new(),
123            handlers: Vec::new(),
124            feature_flags: Vec::new(),
125            maintenance_mode: None,
126        }
127    }
128
129    /// Explicit ADR 96 entrypoint for linked customer-root binaries/workspaces.
130    pub fn for_customer_root(
131        config: PlatformConfig,
132        auth_package: P,
133    ) -> CustomerRootRuntimeBuilder<P> {
134        CustomerRootRuntimeBuilder::new(config, auth_package)
135    }
136
137    pub fn with_module<M>(mut self, module: M) -> Self
138    where
139        M: PlatformModule + 'static,
140    {
141        self.modules.push(Box::new(module));
142        self
143    }
144
145    pub fn register_module<M>(self, module: M) -> Self
146    where
147        M: PlatformModule + 'static,
148    {
149        self.with_module(module)
150    }
151
152    pub fn with_boxed_module(mut self, module: Box<dyn PlatformModule>) -> Self {
153        self.modules.push(module);
154        self
155    }
156
157    pub fn register_customer_plugin<C>(mut self, plugin: C) -> Self
158    where
159        C: CustomerBackendPlugin,
160    {
161        self.customer_plugins.push(Box::new(plugin));
162        self
163    }
164
165    pub fn with_boxed_customer_plugin(mut self, plugin: Box<dyn CustomerBackendPlugin>) -> Self {
166        self.customer_plugins.push(plugin);
167        self
168    }
169
170    pub fn with_translation_catalog(mut self, catalog: TranslationCatalog) -> Self {
171        self.translation_catalogs.push(catalog);
172        self
173    }
174
175    pub fn with_translation_catalogs<I>(mut self, catalogs: I) -> Self
176    where
177        I: IntoIterator<Item = TranslationCatalog>,
178    {
179        self.translation_catalogs.extend(catalogs);
180        self
181    }
182
183    pub fn with_customer_plugin<C>(self, plugin: C) -> Self
184    where
185        C: CustomerBackendPlugin,
186    {
187        self.register_customer_plugin(plugin)
188    }
189
190    pub fn with_installed_extension(mut self, extension: InstalledExtension) -> Self {
191        self.extensions.push(extension);
192        self
193    }
194
195    pub fn with_template(mut self, template: TemplateDefinition) -> Self {
196        self.templates.push(template);
197        self
198    }
199
200    pub fn with_templates<I>(mut self, templates: I) -> Self
201    where
202        I: IntoIterator<Item = TemplateDefinition>,
203    {
204        self.templates.extend(templates);
205        self
206    }
207
208    pub fn with_template_root<A>(mut self, root: A) -> Self
209    where
210        A: Into<PathBuf>,
211    {
212        self.template_roots.push(root.into());
213        self
214    }
215
216    pub fn with_storage_policy_rule(mut self, rule: PathPolicyRule) -> Self {
217        self.storage_policies = self.storage_policies.with_rule(rule);
218        self
219    }
220
221    pub fn with_storage_policies(mut self, policies: StoragePolicySet) -> Self {
222        self.storage_policies = policies;
223        self
224    }
225
226    pub fn with_route(mut self, route: RouteDefinition) -> Self {
227        self.routes.push(route);
228        self
229    }
230
231    pub fn with_handler(mut self, handler: HandlerDefinition) -> Self {
232        self.handlers.push(handler);
233        self
234    }
235
236    pub fn with_feature_flag(mut self, feature_flag: FeatureFlag) -> Self {
237        self.feature_flags.push(feature_flag);
238        self
239    }
240
241    pub fn with_maintenance_mode(mut self, maintenance_mode: MaintenanceMode) -> Self {
242        self.maintenance_mode = Some(maintenance_mode);
243        self
244    }
245
246    pub(crate) fn resolve_enabled_customer_modules(
247        mut self,
248        enabled_modules: &[String],
249    ) -> Result<Self, RuntimeBuildError> {
250        self.modules = crate::builder::customer_root::resolve_enabled_customer_modules(
251            enabled_modules,
252            self.modules,
253        )?;
254        Ok(self)
255    }
256
257    pub fn build(self) -> Result<RuntimePlan, RuntimeBuildError> {
258        assembly::build_runtime_plan(self)
259    }
260
261    pub fn run_from_env(self) -> Result<(), RuntimeBootstrapError> {
262        self.build()?.serve_from_env(env::var("COIL_BIND").ok())
263    }
264
265    pub(crate) fn into_parts(self) -> RuntimeBuilderParts<P> {
266        RuntimeBuilderParts {
267            config: self.config,
268            auth_package: self.auth_package,
269            modules: self.modules,
270            customer_plugins: self.customer_plugins,
271            translation_catalogs: self.translation_catalogs,
272            extensions: self.extensions,
273            templates: self.templates,
274            template_roots: self.template_roots,
275            storage_policies: self.storage_policies,
276            routes: self.routes,
277            handlers: self.handlers,
278            feature_flags: self.feature_flags,
279            maintenance_mode: self.maintenance_mode,
280        }
281    }
282}
283
284impl RuntimeBuilder<coil_auth::LoadedAuthModelPackage> {
285    pub fn for_customer_root_from_env() -> Result<
286        CustomerRootRuntimeBuilder<coil_auth::LoadedAuthModelPackage>,
287        RuntimeBootstrapError,
288    > {
289        CustomerRootRuntimeBuilder::from_env()
290    }
291
292    pub fn for_customer_root_from_paths(
293        app_root: impl AsRef<std::path::Path>,
294        config_path: impl AsRef<std::path::Path>,
295    ) -> Result<
296        CustomerRootRuntimeBuilder<coil_auth::LoadedAuthModelPackage>,
297        RuntimeBootstrapError,
298    > {
299        CustomerRootRuntimeBuilder::from_paths(app_root, config_path)
300    }
301}