reactive_graph_runtime_impl/
builder.rs1use 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}