1use anyhow::Result;
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::time::Instant;
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum BackendKind {
15 LocalProcess,
16 K8s,
17 Static,
18 Mock,
19}
20
21pub struct OopModuleConfig {
23 pub name: String,
24 pub binary: Option<PathBuf>,
25 pub args: Vec<String>,
26 pub env: HashMap<String, String>,
27 pub working_directory: Option<String>,
28 pub backend: BackendKind,
29 pub version: Option<String>,
30}
31
32impl OopModuleConfig {
33 pub fn new(name: impl Into<String>, backend: BackendKind) -> Self {
34 Self {
35 name: name.into(),
36 binary: None,
37 args: Vec::new(),
38 env: HashMap::new(),
39 working_directory: None,
40 backend,
41 version: None,
42 }
43 }
44}
45
46#[derive(Clone)]
48pub struct InstanceHandle {
49 pub module: String,
50 pub instance_id: Uuid,
51 pub backend: BackendKind,
52 pub pid: Option<u32>,
53 pub created_at: Instant,
54}
55
56impl std::fmt::Debug for InstanceHandle {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("InstanceHandle")
59 .field("module", &self.module)
60 .field("instance_id", &self.instance_id)
61 .field("backend", &self.backend)
62 .field("pid", &self.pid)
63 .field("created_at", &self.created_at)
64 .finish()
65 }
66}
67
68#[async_trait]
70pub trait ModuleRuntimeBackend: Send + Sync {
71 async fn spawn_instance(&self, cfg: &OopModuleConfig) -> Result<InstanceHandle>;
72 async fn stop_instance(&self, handle: &InstanceHandle) -> Result<()>;
73 async fn list_instances(&self, module: &str) -> Result<Vec<InstanceHandle>>;
74}
75
76pub struct OopSpawnConfig {
78 pub module_name: String,
79 pub binary: PathBuf,
80 pub args: Vec<String>,
81 pub env: HashMap<String, String>,
82 pub working_directory: Option<String>,
83}
84
85#[async_trait]
89pub trait OopBackend: Send + Sync {
90 async fn spawn(&self, config: OopSpawnConfig) -> Result<()>;
92
93 async fn shutdown_all(&self);
95}
96
97pub mod local;
98pub mod log_forwarder;
99
100pub use local::LocalProcessBackend;
101
102#[async_trait]
106impl OopBackend for LocalProcessBackend {
107 async fn spawn(&self, config: OopSpawnConfig) -> Result<()> {
108 let mut oop_config = OopModuleConfig::new(&config.module_name, BackendKind::LocalProcess);
109 oop_config.binary = Some(config.binary);
110 oop_config.args = config.args;
111 oop_config.env = config.env;
112 oop_config.working_directory = config.working_directory;
113
114 self.spawn_instance(&oop_config).await?;
115 Ok(())
116 }
117
118 async fn shutdown_all(&self) {
119 }
123}
124
125#[cfg(test)]
126#[cfg_attr(coverage_nightly, coverage(off))]
127mod tests {
128 use super::*;
129 use std::path::PathBuf;
130
131 #[test]
132 fn test_oop_module_config_builder() {
133 let mut cfg = OopModuleConfig::new("my_module", BackendKind::LocalProcess);
134 cfg.binary = Some(PathBuf::from("/usr/bin/myapp"));
135 cfg.args = vec!["--port".to_owned(), "8080".to_owned()];
136 cfg.env.insert("LOG_LEVEL".to_owned(), "debug".to_owned());
137 cfg.version = Some("1.0.0".to_owned());
138
139 assert_eq!(cfg.name, "my_module");
140 assert_eq!(cfg.backend, BackendKind::LocalProcess);
141 assert_eq!(cfg.binary, Some(PathBuf::from("/usr/bin/myapp")));
142 assert_eq!(cfg.args.len(), 2);
143 assert_eq!(cfg.env.len(), 1);
144 assert_eq!(cfg.version, Some("1.0.0".to_owned()));
145 }
146
147 #[test]
148 fn test_backend_kind_equality() {
149 assert_eq!(BackendKind::LocalProcess, BackendKind::LocalProcess);
150 assert_ne!(BackendKind::LocalProcess, BackendKind::K8s);
151 assert_ne!(BackendKind::K8s, BackendKind::Static);
152 assert_ne!(BackendKind::Static, BackendKind::Mock);
153 }
154
155 #[test]
156 fn test_instance_handle_debug() {
157 let instance_id = Uuid::new_v4();
158 let handle = InstanceHandle {
159 module: "test_module".to_owned(),
160 instance_id,
161 backend: BackendKind::LocalProcess,
162 pid: Some(12345),
163 created_at: Instant::now(),
164 };
165
166 let debug_str = format!("{handle:?}");
167 assert!(debug_str.contains("test_module"));
168 assert!(debug_str.contains(&instance_id.to_string()));
169 assert!(debug_str.contains("LocalProcess"));
170 assert!(debug_str.contains("12345"));
171 }
172}