reactive_graph_runtime_impl/
builder.rs

1use std::future::Future;
2use std::marker::PhantomData;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use tokio::time::Duration;
7
8use crate::get_runtime;
9use reactive_graph_remotes_model::InstanceAddress;
10use reactive_graph_runtime_api::Runtime;
11
12pub enum SetConfigLocations {}
13pub enum ConfigFilesLoaded {}
14
15pub enum NotRunning {}
16pub enum Initialized {}
17pub enum Ready {}
18pub enum Running {}
19pub enum Finished {}
20pub enum PreShutdown {}
21pub enum Shutdown {}
22
23pub struct RuntimeBuilder<L, R> {
24    runtime: Arc<dyn Runtime + Send + Sync>,
25
26    typestate: PhantomData<(L, R)>,
27}
28
29impl RuntimeBuilder<SetConfigLocations, NotRunning> {
30    pub fn new() -> Self {
31        Self {
32            runtime: get_runtime(),
33            typestate: PhantomData,
34        }
35    }
36
37    /// Sets the location of the instance configuration.
38    pub fn instance_config<P: Into<OptionOption<PathBuf>>>(self, location: P) -> RuntimeBuilder<SetConfigLocations, NotRunning> {
39        if let Some(location) = location.into().get() {
40            self.runtime.get_config_manager().set_instance_config_location(location);
41        }
42        self
43    }
44
45    /// Sets the location of the graphql server configuration.
46    pub fn graphql_server_config<P: Into<OptionOption<PathBuf>>>(self, location: P) -> RuntimeBuilder<SetConfigLocations, NotRunning> {
47        if let Some(location) = location.into().get() {
48            self.runtime.get_config_manager().set_graphql_server_config_location(location);
49        }
50        self
51    }
52
53    /// Sets the location of the plugins configuration.
54    pub fn plugins_config<P: Into<OptionOption<PathBuf>>>(self, location: P) -> RuntimeBuilder<SetConfigLocations, NotRunning> {
55        if let Some(location) = location.into().get() {
56            self.runtime.get_config_manager().set_plugins_config_location(location);
57        }
58        self
59    }
60
61    /// Loads the config files and transits to state `ConfigFilesLoaded`.
62    pub async fn load_config_files(self) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
63        self.runtime.config().await;
64        RuntimeBuilder {
65            runtime: self.runtime,
66            typestate: PhantomData,
67        }
68    }
69
70    /// Doesn't load the config files. Transits to state `ConfigFilesLoaded`.
71    pub fn ignore_config_files(self) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
72        RuntimeBuilder {
73            runtime: self.runtime,
74            typestate: PhantomData,
75        }
76    }
77}
78
79impl RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
80    /// Sets the name of the instance.
81    pub fn instance_name<S: Into<OptionOption<String>>>(self, name: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
82        if let Some(name) = name.into().get() {
83            self.runtime.get_config_manager().set_instance_name(&name);
84        }
85        self
86    }
87
88    /// Sets the description of the instance.
89    pub fn instance_description<S: Into<OptionOption<String>>>(self, description: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
90        if let Some(description) = description.into().get() {
91            self.runtime.get_config_manager().set_instance_description(&description);
92        }
93        self
94    }
95
96    /// Sets the hostname of the GraphQL server.
97    pub fn hostname<S: Into<OptionOption<String>>>(self, hostname: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
98        if let Some(hostname) = hostname.into().get() {
99            self.runtime.get_config_manager().set_graphql_hostname(&hostname);
100        }
101        self
102    }
103
104    /// Sets the port number of the GraphQL server.
105    pub fn port<S: Into<OptionOption<u16>>>(self, port: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
106        if let Some(port) = port.into().get() {
107            self.runtime.get_config_manager().set_graphql_port(port);
108        }
109        self
110    }
111
112    /// Picks a free port instead of a fixed port number.
113    pub fn pick_free_port(self) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
114        if let Some(port) = portpicker::pick_unused_port() {
115            self.runtime.get_config_manager().set_graphql_port(port);
116        }
117        self
118    }
119
120    /// Sets if the GraphQL server shall use https or http.
121    pub fn secure<S: Into<OptionOption<bool>>>(self, secure: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
122        if let Some(secure) = secure.into().get() {
123            self.runtime.get_config_manager().set_graphql_secure(secure);
124        }
125        self
126    }
127
128    /// Sets the path to the SSL certificate.
129    pub fn ssl_certificate_path<S: Into<OptionOption<String>>>(self, ssl_certificate_path: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
130        if let Some(ssl_certificate_path) = ssl_certificate_path.into().get() {
131            self.runtime.get_config_manager().set_graphql_ssl_certificate_path(&ssl_certificate_path);
132        }
133        self
134    }
135
136    /// Sets the path to the SSL private key.
137    pub fn ssl_private_key_path<S: Into<OptionOption<String>>>(self, ssl_private_key_path: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
138        if let Some(ssl_private_key_path) = ssl_private_key_path.into().get() {
139            self.runtime.get_config_manager().set_graphql_ssl_private_key_path(&ssl_private_key_path);
140        }
141        self
142    }
143
144    /// Sets the hostname, port and secure of the GraphQL server.
145    pub fn address(self, address: &InstanceAddress) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
146        let config_manager = self.runtime.get_config_manager();
147        config_manager.set_graphql_hostname(&address.hostname);
148        config_manager.set_graphql_port(address.port);
149        config_manager.set_graphql_secure(address.secure);
150        self
151    }
152
153    /// Sets timeout of the shutdown of the GraphQL server.
154    pub fn shutdown_timeout<S: Into<OptionOption<u64>>>(self, shutdown_timeout: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
155        if let Some(shutdown_timeout) = shutdown_timeout.into().get() {
156            self.runtime.get_config_manager().set_graphql_shutdown_timeout(shutdown_timeout);
157        }
158        self
159    }
160
161    /// Sets the number of workers of the GraphQL server.
162    pub fn workers<S: Into<OptionOption<usize>>>(self, workers: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
163        if let Some(workers) = workers.into().get() {
164            self.runtime.get_config_manager().set_graphql_workers(workers);
165        }
166        self
167    }
168
169    /// Sets context path of a web resource provider which shall be used as default context path.
170    pub fn default_context_path<S: Into<OptionOption<String>>>(self, default_context_path: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
171        if let Some(default_context_path) = default_context_path.into().get() {
172            self.runtime.get_config_manager().set_graphql_default_context_path(default_context_path);
173        }
174        self
175    }
176
177    /// Disables all plugins.
178    pub fn disable_all_plugins<S: Into<OptionOption<bool>>>(self, disabled: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
179        if let Some(disabled) = disabled.into().get() {
180            self.runtime.get_config_manager().set_disable_all_plugins(disabled);
181        }
182        self
183    }
184
185    /// Sets which plugins will be disabled.
186    pub fn disabled_plugins<S: Into<OptionOption<Vec<String>>>>(self, disabled_plugins: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
187        if let Some(disabled_plugins) = disabled_plugins.into().get() {
188            self.runtime.get_config_manager().set_disabled_plugins(disabled_plugins);
189        }
190        self
191    }
192
193    /// Sets which plugins will be enabled. If set, disabled_plugins will have no effect.
194    pub fn enabled_plugins<S: Into<OptionOption<Vec<String>>>>(self, enabled_plugins: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
195        if let Some(enabled_plugins) = enabled_plugins.into().get() {
196            self.runtime.get_config_manager().set_enabled_plugins(enabled_plugins);
197        }
198        self
199    }
200
201    /// Disables hot deployment of plugins.
202    pub fn disable_hot_deploy<S: Into<OptionOption<bool>>>(self, disabled: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
203        if let Some(disabled) = disabled.into().get() {
204            self.runtime.get_config_manager().set_disable_hot_deploy(disabled);
205        }
206        self
207    }
208
209    /// Sets the directory where plugins can be hot deployed.
210    pub fn hot_deploy_location<S: Into<OptionOption<String>>>(self, hot_deploy_location: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
211        if let Some(hot_deploy_location) = hot_deploy_location.into().get() {
212            self.runtime.get_config_manager().set_hot_deploy_location(Some(hot_deploy_location))
213        }
214        self
215    }
216
217    /// Sets the directory where plugins are located.
218    /// During hot deployment new plugins will be moved into this directory.
219    pub fn install_location<S: Into<OptionOption<String>>>(self, install_location: S) -> RuntimeBuilder<ConfigFilesLoaded, NotRunning> {
220        if let Some(install_location) = install_location.into().get() {
221            self.runtime.get_config_manager().set_install_location(Some(install_location))
222        }
223        self
224    }
225
226    pub fn get(self) -> Arc<dyn Runtime> {
227        self.runtime
228    }
229
230    pub async fn init(self) -> RuntimeBuilder<ConfigFilesLoaded, Initialized> {
231        self.runtime.init().await;
232        RuntimeBuilder {
233            runtime: self.runtime,
234            typestate: PhantomData,
235        }
236    }
237
238    pub async fn block_on(self) -> RuntimeBuilder<ConfigFilesLoaded, Shutdown> {
239        {
240            self.runtime.init().await;
241            self.runtime.post_init().await;
242            self.runtime.run().await;
243            self.runtime.pre_shutdown().await;
244            self.runtime.shutdown().await;
245        }
246        RuntimeBuilder {
247            runtime: self.runtime,
248            typestate: PhantomData,
249        }
250    }
251}
252
253impl RuntimeBuilder<ConfigFilesLoaded, Initialized> {
254    pub async fn post_init(self) -> RuntimeBuilder<ConfigFilesLoaded, Ready> {
255        self.runtime.post_init().await;
256        RuntimeBuilder {
257            runtime: self.runtime,
258            typestate: PhantomData,
259        }
260    }
261}
262
263impl RuntimeBuilder<ConfigFilesLoaded, Ready> {
264    pub fn get(self) -> Arc<dyn Runtime> {
265        self.runtime
266    }
267
268    pub async fn with_runtime<F, C>(self, f: C) -> RuntimeBuilder<ConfigFilesLoaded, Ready>
269    where
270        F: Future<Output = ()>,
271        C: FnOnce(Arc<dyn Runtime>) -> F,
272    {
273        let runtime = self.runtime.clone();
274        f(runtime).await;
275        self
276    }
277
278    /// Starts the GraphQL server (non-blocking).
279    pub async fn spawn(self) -> RuntimeBuilder<ConfigFilesLoaded, Running> {
280        let runtime_inner = self.runtime.clone();
281        tokio::task::spawn(async move {
282            runtime_inner.run().await;
283        });
284        let _ = self.runtime.wait_for_started(Duration::from_secs(5)).await;
285        RuntimeBuilder {
286            runtime: self.runtime,
287            typestate: PhantomData,
288        }
289    }
290
291    /// Starts the GraphQL server (blocking).
292    pub async fn spawn_blocking(self) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
293        self.runtime.run().await;
294        RuntimeBuilder {
295            runtime: self.runtime,
296            typestate: PhantomData,
297        }
298    }
299
300    /// Runs starts the GraphQL server. Stops the GraphQL server after the given duration.
301    pub async fn run_for(self, duration: Duration) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
302        let inner_runtime = self.runtime.clone();
303        tokio::spawn(async move {
304            tokio::time::sleep(duration).await;
305            inner_runtime.get_shutdown_manager().do_shutdown();
306        });
307        self.runtime.run().await;
308        RuntimeBuilder {
309            runtime: self.runtime,
310            typestate: PhantomData,
311        }
312    }
313
314    /// Do not start the GraphQL server but shutdown the runtime.
315    pub async fn do_not_run(self) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
316        RuntimeBuilder {
317            runtime: self.runtime,
318            typestate: PhantomData,
319        }
320    }
321}
322
323impl RuntimeBuilder<ConfigFilesLoaded, Running> {
324    pub fn get(self) -> Arc<dyn Runtime + Send + Sync> {
325        self.runtime
326    }
327
328    pub async fn with_runtime<F, C>(self, f: C) -> RuntimeBuilder<ConfigFilesLoaded, Running>
329    where
330        F: Future<Output = ()>,
331        C: FnOnce(Arc<dyn Runtime + Send + Sync>) -> F,
332    {
333        let runtime = self.runtime.clone();
334        f(runtime).await;
335        self
336    }
337
338    /// Stops the runtime. Waits for the GraphQL server has been stopped.
339    pub async fn stop(self) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
340        self.runtime.stop();
341        self.runtime.wait_for_stopped().await;
342        RuntimeBuilder {
343            runtime: self.runtime,
344            typestate: PhantomData,
345        }
346    }
347
348    /// Stops the runtime. Waits for the GraphQL server has been stopped or the given timeout has
349    /// been reached.
350    pub async fn stop_with_timeout(self, timeout_duration: Duration) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
351        self.runtime.stop();
352        let _ = self.runtime.wait_for_stopped_with_timeout(timeout_duration).await;
353        RuntimeBuilder {
354            runtime: self.runtime,
355            typestate: PhantomData,
356        }
357    }
358
359    /// Waits for the GraphQL server has been stopped.
360    pub async fn wait_for_stopped(self) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
361        self.runtime.wait_for_stopped().await;
362        RuntimeBuilder {
363            runtime: self.runtime,
364            typestate: PhantomData,
365        }
366    }
367
368    /// Waits for the GraphQL server has been stopped.
369    pub async fn wait_for_stopped_with_timeout(self, timeout_duration: Duration) -> RuntimeBuilder<ConfigFilesLoaded, Finished> {
370        let _ = self.runtime.wait_for_stopped_with_timeout(timeout_duration).await;
371        RuntimeBuilder {
372            runtime: self.runtime,
373            typestate: PhantomData,
374        }
375    }
376}
377
378impl RuntimeBuilder<ConfigFilesLoaded, Finished> {
379    pub fn get(self) -> Arc<dyn Runtime> {
380        self.runtime
381    }
382
383    pub async fn with_runtime<F, C>(self, f: C) -> RuntimeBuilder<ConfigFilesLoaded, Finished>
384    where
385        F: Future<Output = ()>,
386        C: FnOnce(Arc<dyn Runtime>) -> F,
387    {
388        let runtime = self.runtime.clone();
389        f(runtime).await;
390        self
391    }
392
393    pub async fn pre_shutdown(self) -> RuntimeBuilder<ConfigFilesLoaded, PreShutdown> {
394        self.runtime.pre_shutdown().await;
395        RuntimeBuilder {
396            runtime: self.runtime,
397            typestate: PhantomData,
398        }
399    }
400}
401
402impl RuntimeBuilder<ConfigFilesLoaded, PreShutdown> {
403    pub async fn shutdown(self) -> RuntimeBuilder<ConfigFilesLoaded, Shutdown> {
404        self.runtime.shutdown().await;
405        RuntimeBuilder {
406            runtime: self.runtime,
407            typestate: PhantomData,
408        }
409    }
410}
411
412impl RuntimeBuilder<ConfigFilesLoaded, Shutdown> {
413    pub async fn wait_for(self, duration: Duration) -> RuntimeBuilder<ConfigFilesLoaded, Shutdown> {
414        tokio::time::sleep(duration).await;
415        self
416    }
417}
418
419impl Default for RuntimeBuilder<SetConfigLocations, NotRunning> {
420    fn default() -> Self {
421        Self::new()
422    }
423}
424
425pub struct OptionOption<T>(Option<T>);
426
427impl<T> OptionOption<T> {
428    pub fn get(self) -> Option<T> {
429        self.0
430    }
431}
432
433impl<T> From<T> for OptionOption<T> {
434    fn from(value: T) -> Self {
435        Self(Some(value))
436    }
437}
438
439impl<T> From<Option<T>> for OptionOption<T> {
440    fn from(value: Option<T>) -> Self {
441        Self(value)
442    }
443}
444
445impl From<&str> for OptionOption<String> {
446    fn from(value: &str) -> Self {
447        Self(Some(value.into()))
448    }
449}
450
451impl From<String> for OptionOption<PathBuf> {
452    fn from(value: String) -> Self {
453        Self(Some(value.into()))
454    }
455}
456
457impl From<Option<String>> for OptionOption<PathBuf> {
458    fn from(value: Option<String>) -> Self {
459        Self(value.map(|v| v.into()))
460    }
461}
462
463#[tokio::test(flavor = "multi_thread")]
464async fn build_builder_get() {
465    let runtime = RuntimeBuilder::new()
466        .ignore_config_files()
467        .instance_name("Test Runtime Builder Get")
468        .pick_free_port()
469        .disable_all_plugins(true)
470        .get();
471    let inner_runtime = runtime.clone();
472    tokio::spawn(async move {
473        tokio::time::sleep(Duration::from_millis(2000)).await;
474        inner_runtime.get_shutdown_manager().do_shutdown();
475    });
476    {
477        runtime.init().await;
478        runtime.post_init().await;
479        runtime.run().await;
480        runtime.pre_shutdown().await;
481        runtime.shutdown().await;
482    }
483}
484
485#[tokio::test(flavor = "multi_thread")]
486async fn run_it() {
487    RuntimeBuilder::new()
488        .load_config_files()
489        .await
490        .instance_name("Test runtime builder with timeout")
491        .disable_all_plugins(true)
492        .init()
493        .await
494        .post_init()
495        .await
496        .run_for(Duration::from_millis(300))
497        .await
498        .pre_shutdown()
499        .await
500        .shutdown()
501        .await
502        .wait_for(Duration::from_millis(300))
503        .await;
504}