1use std::net::SocketAddr;
36use std::sync::Arc;
37
38use anyhow::Result;
39use tokio::task::JoinHandle;
40use tracing::{error, info};
41
42use crate::instance_handlers::InstanceHandlerState;
43use crate::persistence::Persistence;
44use crate::server::InstanceServerState;
45
46pub struct CoreRuntimeBuilder {
48 persistence: Option<Arc<dyn Persistence>>,
49 bind_addr: SocketAddr,
50}
51
52impl std::fmt::Debug for CoreRuntimeBuilder {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("CoreRuntimeBuilder")
55 .field("persistence", &self.persistence.as_ref().map(|_| "..."))
56 .field("bind_addr", &self.bind_addr)
57 .finish()
58 }
59}
60
61impl Default for CoreRuntimeBuilder {
62 fn default() -> Self {
63 Self {
64 persistence: None,
65 bind_addr: "0.0.0.0:8001".parse().unwrap(),
66 }
67 }
68}
69
70impl CoreRuntimeBuilder {
71 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn persistence(mut self, persistence: Arc<dyn Persistence>) -> Self {
78 self.persistence = Some(persistence);
79 self
80 }
81
82 pub fn bind_addr(mut self, addr: SocketAddr) -> Self {
86 self.bind_addr = addr;
87 self
88 }
89
90 pub fn build(self) -> Result<CoreRuntimeConfig> {
94 let persistence = self
95 .persistence
96 .ok_or_else(|| anyhow::anyhow!("persistence is required"))?;
97
98 Ok(CoreRuntimeConfig {
99 persistence,
100 bind_addr: self.bind_addr,
101 })
102 }
103}
104
105pub struct CoreRuntimeConfig {
107 persistence: Arc<dyn Persistence>,
108 bind_addr: SocketAddr,
109}
110
111impl std::fmt::Debug for CoreRuntimeConfig {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.debug_struct("CoreRuntimeConfig")
114 .field("persistence", &"...")
115 .field("bind_addr", &self.bind_addr)
116 .finish()
117 }
118}
119
120impl CoreRuntimeConfig {
121 pub async fn start(self) -> Result<CoreRuntime> {
123 let state = Arc::new(InstanceHandlerState::new(self.persistence));
124
125 let bind_addr = self.bind_addr;
126 let server_state = state.clone();
127 let server_handle = tokio::spawn(async move {
128 crate::server::http_server::run_http_server(bind_addr, server_state).await
129 });
130
131 info!(addr = %bind_addr, "CoreRuntime started");
132
133 Ok(CoreRuntime {
134 server_handle,
135 state,
136 bind_addr,
137 })
138 }
139}
140
141pub struct CoreRuntime {
148 server_handle: JoinHandle<anyhow::Result<()>>,
149 state: Arc<InstanceServerState>,
150 bind_addr: SocketAddr,
151}
152
153impl CoreRuntime {
154 pub fn builder() -> CoreRuntimeBuilder {
156 CoreRuntimeBuilder::new()
157 }
158
159 pub fn bind_addr(&self) -> SocketAddr {
161 self.bind_addr
162 }
163
164 pub fn state(&self) -> &Arc<InstanceServerState> {
168 &self.state
169 }
170
171 pub fn persistence(&self) -> &Arc<dyn Persistence> {
173 &self.state.persistence
174 }
175
176 pub async fn shutdown(self) -> Result<()> {
180 info!("CoreRuntime shutting down...");
181
182 self.server_handle.abort();
184
185 match self.server_handle.await {
186 Ok(Ok(())) => {
187 info!("CoreRuntime shutdown complete");
188 Ok(())
189 }
190 Ok(Err(e)) => {
191 error!("CoreRuntime server error during shutdown: {}", e);
192 Err(e)
193 }
194 Err(e) if e.is_cancelled() => {
195 info!("CoreRuntime shutdown complete");
196 Ok(())
197 }
198 Err(e) => {
199 error!("CoreRuntime server task panicked: {}", e);
200 Err(anyhow::anyhow!("server task panicked: {}", e))
201 }
202 }
203 }
204
205 pub fn is_running(&self) -> bool {
207 !self.server_handle.is_finished()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::error::CoreError;
215 use crate::persistence::{
216 CheckpointRecord, CustomSignalRecord, EventRecord, InstanceRecord, ListEventsFilter,
217 ListStepSummariesFilter, Persistence, SignalRecord, StepSummaryRecord,
218 };
219 use async_trait::async_trait;
220 use chrono::{DateTime, Utc};
221
222 struct MockPersistence;
224
225 #[async_trait]
226 impl Persistence for MockPersistence {
227 async fn register_instance(
228 &self,
229 _instance_id: &str,
230 _tenant_id: &str,
231 ) -> Result<(), CoreError> {
232 Ok(())
233 }
234
235 async fn get_instance(
236 &self,
237 _instance_id: &str,
238 ) -> Result<Option<InstanceRecord>, CoreError> {
239 Ok(None)
240 }
241
242 async fn update_instance_status(
243 &self,
244 _instance_id: &str,
245 _status: &str,
246 _started_at: Option<DateTime<Utc>>,
247 ) -> Result<(), CoreError> {
248 Ok(())
249 }
250
251 async fn update_instance_checkpoint(
252 &self,
253 _instance_id: &str,
254 _checkpoint_id: &str,
255 ) -> Result<(), CoreError> {
256 Ok(())
257 }
258
259 async fn complete_instance(
260 &self,
261 _instance_id: &str,
262 _output: Option<&[u8]>,
263 _error: Option<&str>,
264 ) -> Result<(), CoreError> {
265 Ok(())
266 }
267
268 async fn save_checkpoint(
269 &self,
270 _instance_id: &str,
271 _checkpoint_id: &str,
272 _state: &[u8],
273 ) -> Result<(), CoreError> {
274 Ok(())
275 }
276
277 async fn load_checkpoint(
278 &self,
279 _instance_id: &str,
280 _checkpoint_id: &str,
281 ) -> Result<Option<CheckpointRecord>, CoreError> {
282 Ok(None)
283 }
284
285 async fn list_checkpoints(
286 &self,
287 _instance_id: &str,
288 _checkpoint_id: Option<&str>,
289 _limit: i64,
290 _offset: i64,
291 _created_after: Option<DateTime<Utc>>,
292 _created_before: Option<DateTime<Utc>>,
293 ) -> Result<Vec<CheckpointRecord>, CoreError> {
294 Ok(Vec::new())
295 }
296
297 async fn count_checkpoints(
298 &self,
299 _instance_id: &str,
300 _checkpoint_id: Option<&str>,
301 _created_after: Option<DateTime<Utc>>,
302 _created_before: Option<DateTime<Utc>>,
303 ) -> Result<i64, CoreError> {
304 Ok(0)
305 }
306
307 async fn insert_event(&self, _event: &EventRecord) -> Result<(), CoreError> {
308 Ok(())
309 }
310
311 async fn insert_signal(
312 &self,
313 _instance_id: &str,
314 _signal_type: &str,
315 _payload: &[u8],
316 ) -> Result<(), CoreError> {
317 Ok(())
318 }
319
320 async fn get_pending_signal(
321 &self,
322 _instance_id: &str,
323 ) -> Result<Option<SignalRecord>, CoreError> {
324 Ok(None)
325 }
326
327 async fn acknowledge_signal(&self, _instance_id: &str) -> Result<(), CoreError> {
328 Ok(())
329 }
330
331 async fn insert_custom_signal(
332 &self,
333 _instance_id: &str,
334 _checkpoint_id: &str,
335 _payload: &[u8],
336 ) -> Result<(), CoreError> {
337 Ok(())
338 }
339
340 async fn take_pending_custom_signal(
341 &self,
342 _instance_id: &str,
343 _checkpoint_id: &str,
344 ) -> Result<Option<CustomSignalRecord>, CoreError> {
345 Ok(None)
346 }
347
348 async fn save_retry_attempt(
349 &self,
350 _instance_id: &str,
351 _checkpoint_id: &str,
352 _attempt: i32,
353 _error_message: Option<&str>,
354 ) -> Result<(), CoreError> {
355 Ok(())
356 }
357
358 async fn list_instances(
359 &self,
360 _tenant_id: Option<&str>,
361 _status: Option<&str>,
362 _limit: i64,
363 _offset: i64,
364 ) -> Result<Vec<InstanceRecord>, CoreError> {
365 Ok(Vec::new())
366 }
367
368 async fn health_check_db(&self) -> Result<bool, CoreError> {
369 Ok(true)
370 }
371
372 async fn count_active_instances(&self) -> Result<i64, CoreError> {
373 Ok(0)
374 }
375
376 async fn set_instance_sleep(
377 &self,
378 _instance_id: &str,
379 _sleep_until: DateTime<Utc>,
380 ) -> Result<(), CoreError> {
381 Ok(())
382 }
383
384 async fn clear_instance_sleep(&self, _instance_id: &str) -> Result<(), CoreError> {
385 Ok(())
386 }
387
388 async fn get_sleeping_instances_due(
389 &self,
390 _limit: i64,
391 ) -> Result<Vec<InstanceRecord>, CoreError> {
392 Ok(Vec::new())
393 }
394
395 async fn list_events(
396 &self,
397 _instance_id: &str,
398 _filter: &ListEventsFilter,
399 _limit: i64,
400 _offset: i64,
401 ) -> Result<Vec<EventRecord>, CoreError> {
402 Ok(Vec::new())
403 }
404
405 async fn count_events(
406 &self,
407 _instance_id: &str,
408 _filter: &ListEventsFilter,
409 ) -> Result<i64, CoreError> {
410 Ok(0)
411 }
412
413 async fn list_step_summaries(
414 &self,
415 _instance_id: &str,
416 _filter: &ListStepSummariesFilter,
417 _limit: i64,
418 _offset: i64,
419 ) -> Result<Vec<StepSummaryRecord>, CoreError> {
420 Ok(Vec::new())
421 }
422
423 async fn count_step_summaries(
424 &self,
425 _instance_id: &str,
426 _filter: &ListStepSummariesFilter,
427 ) -> Result<i64, CoreError> {
428 Ok(0)
429 }
430 }
431
432 #[test]
433 fn test_builder_default() {
434 let builder = CoreRuntimeBuilder::default();
435 assert!(builder.persistence.is_none());
436 assert_eq!(builder.bind_addr.port(), 8001);
437 }
438
439 #[test]
440 fn test_builder_new() {
441 let builder = CoreRuntimeBuilder::new();
442 assert!(builder.persistence.is_none());
443 assert_eq!(builder.bind_addr.port(), 8001);
444 }
445
446 #[test]
447 fn test_builder_persistence() {
448 let persistence = Arc::new(MockPersistence);
449 let builder = CoreRuntimeBuilder::new().persistence(persistence);
450 assert!(builder.persistence.is_some());
451 }
452
453 #[test]
454 fn test_builder_bind_addr() {
455 let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
456 let builder = CoreRuntimeBuilder::new().bind_addr(addr);
457 assert_eq!(builder.bind_addr.port(), 9000);
458 }
459
460 #[test]
461 fn test_builder_chaining() {
462 let persistence = Arc::new(MockPersistence);
463 let addr: SocketAddr = "127.0.0.1:9001".parse().unwrap();
464 let builder = CoreRuntimeBuilder::new()
465 .persistence(persistence)
466 .bind_addr(addr);
467 assert!(builder.persistence.is_some());
468 assert_eq!(builder.bind_addr.port(), 9001);
469 }
470
471 #[test]
472 fn test_builder_debug() {
473 let builder = CoreRuntimeBuilder::new();
474 let debug_str = format!("{:?}", builder);
475 assert!(debug_str.contains("CoreRuntimeBuilder"));
476 assert!(debug_str.contains("bind_addr"));
477 }
478
479 #[test]
480 fn test_builder_debug_with_persistence() {
481 let persistence = Arc::new(MockPersistence);
482 let builder = CoreRuntimeBuilder::new().persistence(persistence);
483 let debug_str = format!("{:?}", builder);
484 assert!(debug_str.contains("CoreRuntimeBuilder"));
485 assert!(debug_str.contains("..."));
487 }
488
489 #[test]
490 fn test_builder_build_missing_persistence() {
491 let result = CoreRuntimeBuilder::new().build();
492 assert!(result.is_err());
493 let err = result.unwrap_err();
494 assert!(err.to_string().contains("persistence is required"));
495 }
496
497 #[test]
498 fn test_builder_build_success() {
499 let persistence = Arc::new(MockPersistence);
500 let result = CoreRuntimeBuilder::new().persistence(persistence).build();
501 assert!(result.is_ok());
502 let config = result.unwrap();
503 assert_eq!(config.bind_addr.port(), 8001);
504 }
505
506 #[test]
507 fn test_builder_build_with_custom_addr() {
508 let persistence = Arc::new(MockPersistence);
509 let addr: SocketAddr = "0.0.0.0:9002".parse().unwrap();
510 let result = CoreRuntimeBuilder::new()
511 .persistence(persistence)
512 .bind_addr(addr)
513 .build();
514 assert!(result.is_ok());
515 let config = result.unwrap();
516 assert_eq!(config.bind_addr.port(), 9002);
517 }
518
519 #[test]
520 fn test_core_runtime_builder_static_method() {
521 let builder = CoreRuntime::builder();
522 assert!(builder.persistence.is_none());
523 }
524
525 #[tokio::test]
526 async fn test_runtime_start_and_shutdown() {
527 let persistence = Arc::new(MockPersistence);
528 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
530
531 let config = CoreRuntimeBuilder::new()
532 .persistence(persistence)
533 .bind_addr(addr)
534 .build()
535 .unwrap();
536
537 let runtime = config.start().await;
538 if let Ok(runtime) = runtime {
540 assert!(runtime.is_running());
541
542 let _actual_addr = runtime.bind_addr();
545
546 let _persistence = runtime.persistence();
548 let _state = runtime.state();
549
550 let result = runtime.shutdown().await;
552 assert!(result.is_ok());
553 }
554 }
555}