Grove/Binary/Main/
Entry.rs
1use std::path::PathBuf;
7
8use anyhow::{Context, Result};
9use tracing::{error, info, instrument};
10
11use crate::{
12 Binary::Main::CliArgs,
13 Host::{ExtensionHost::ExtensionHostImpl, HostConfig},
14 Transport::Strategy::Transport,
15};
16
17pub struct Entry;
19
20impl Entry {
21 #[instrument(skip(args))]
23 pub async fn run(args:CliArgs) -> Result<()> {
24 info!("Starting Grove v{}", env!("CARGO_PKG_VERSION"));
25 info!("Mode: {}", args.mode);
26
27 match args.mode.as_str() {
28 "standalone" => Self::run_standalone(args).await,
29 "service" => Self::run_service(args).await,
30 "validate" => Self::run_validation(args).await,
31 _ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
32 }
33 }
34
35 #[instrument(skip(args))]
37 async fn run_standalone(args:CliArgs) -> Result<()> {
38 info!("Starting Grove in standalone mode");
39
40 let transport = Self::create_transport(&args)?;
42
43 let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
45
46 let host = ExtensionHostImpl::with_config(transport, host_config)
48 .await
49 .context("Failed to create extension host")?;
50
51 if let Some(extension_path) = args.extension {
53 let path = PathBuf::from(extension_path);
54 host.load_extension(&path).await?;
55 host.activate_all().await?;
56 } else {
57 info!("No extension specified, running in daemon mode");
58 }
59
60 Self::wait_for_shutdown().await;
62
63 host.shutdown().await?;
65
66 Ok(())
67 }
68
69 #[instrument(skip(_args))]
71 async fn run_service(_args:CliArgs) -> Result<()> {
72 info!("Starting Grove as service");
73
74 let _transport = Transport::default();
76
77 #[cfg(feature = "gRPC")]
79 {
80 match crate::Binary::Build::ServiceRegister::register_with_mountain(
81 "grove-host",
82 &args.mountain_address,
83 true, )
85 .await
86 {
87 Ok(_) => info!("Registered with Mountain"),
88 Err(e) => warn!("Failed to register with Mountain: {}", e),
89 }
90 }
91
92 #[cfg(not(feature = "gRPC"))]
93 {
94 info!("gRPC feature not enabled, skipping Mountain registration");
95 }
96
97 Self::wait_for_shutdown().await;
99
100 Ok(())
101 }
102
103 #[instrument(skip(args))]
105 async fn run_validation(args:CliArgs) -> Result<()> {
106 info!("Validating extension");
107
108 let extension_path = args
109 .extension
110 .ok_or_else(|| anyhow::anyhow!("Extension path required for validation"))?;
111
112 let path = PathBuf::from(extension_path);
113 let result = Self::validate_extension(&path, false).await?;
114
115 if result.is_valid {
116 info!("Extension validation passed");
117 Ok(())
118 } else {
119 error!("Extension validation failed");
120 Err(anyhow::anyhow!("Validation failed"))
121 }
122 }
123
124 pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
126 info!("Validating extension at: {:?}", path);
127
128 if !path.exists() {
130 return Ok(ValidationResult { is_valid:false, errors:vec![format!("Path does not exist: {:?}", path)] });
131 }
132
133 let mut errors = Vec::new();
134
135 let package_json_path = path.join("package.json");
137 if package_json_path.exists() {
138 match tokio::fs::read_to_string(&package_json_path).await {
139 Ok(content) => {
140 match serde_json::from_str::<serde_json::Value>(&content) {
141 Ok(_) => {
142 info!("Valid package.json found");
143 },
144 Err(e) => {
145 errors.push(format!("Invalid package.json: {}", e));
146 },
147 }
148 },
149 Err(e) => {
150 errors.push(format!("Failed to read package.json: {}", e));
151 },
152 }
153 } else {
154 errors.push("package.json not found".to_string());
155 }
156
157 let is_valid = errors.is_empty();
158
159 if detailed && !errors.is_empty() {
160 for error in &errors {
161 info!("Validation error: {}", error);
162 }
163 }
164
165 Ok(ValidationResult { is_valid, errors })
166 }
167
168 pub async fn build_wasm_module(
170 source:PathBuf,
171 output:PathBuf,
172 _opt_level:String,
173 _target:Option<String>,
174 ) -> Result<BuildResult> {
175 info!("Building WASM module from: {:?}", source);
176 info!("Output: {:?}", output);
177
178 Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
181 }
182
183 pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
185 info!("Listing extensions");
186
187 Ok(Vec::new())
190 }
191
192 fn create_transport(args:&CliArgs) -> Result<Transport> {
194 match args.transport.as_str() {
195 "grpc" => {
196 use crate::Transport::gRPCTransport::gRPCTransport;
197 Ok(Transport::gRPC(
198 gRPCTransport::New(&args.grpc_address)
199 .context("Failed to create gRPC transport")?,
200 ))
201 },
202 "ipc" => {
203 use crate::Transport::IPCTransport::IPCTransport;
204 Ok(Transport::IPC(
205 IPCTransport::New().context("Failed to create IPC transport")?,
206 ))
207 },
208 "wasm" => {
209 use crate::Transport::WASMTransport::WASMTransportImpl;
210 Ok(Transport::WASM(
211 WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
212 .context("Failed to create WASM transport")?,
213 ))
214 },
215 _ => Ok(Transport::default()),
216 }
217 }
218
219 async fn wait_for_shutdown() {
221 info!("Grove is running. Press Ctrl+C to stop.");
222
223 tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
224
225 info!("Shutdown signal received");
226 }
227}
228
229impl Default for Entry {
230 fn default() -> Self { Self }
231}
232
233#[derive(Debug, Clone)]
235pub struct ValidationResult {
236 pub is_valid:bool,
238 pub errors:Vec<String>,
240}
241
242#[derive(Debug, Clone)]
244pub struct BuildResult {
245 pub success:bool,
247 pub output_path:PathBuf,
249 pub compile_time_ms:u64,
251}
252
253impl BuildResult {
254 pub fn success(&self) -> bool { self.success }
256}
257
258#[derive(Debug, Clone)]
260pub struct ExtensionInfo {
261 pub name:String,
263 pub version:String,
265 pub path:PathBuf,
267 pub is_active:bool,
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[tokio::test]
276 async fn test_entry_default() {
277 let entry = Entry::default();
278 let _ = entry;
280 }
281
282 #[tokio::test]
283 async fn test_validate_extension_nonexistent() {
284 let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
285 .await
286 .unwrap();
287
288 assert!(!result.is_valid);
289 assert!(!result.errors.is_empty());
290 }
291
292 #[test]
293 fn test_cli_args_default() {
294 let args = CliArgs::default();
295 assert_eq!(args.mode, "standalone");
296 assert!(args.wasi);
297 }
298
299 #[test]
300 fn test_build_result() {
301 let result = BuildResult {
302 success:true,
303 output_path:PathBuf::from("/test/output.wasm"),
304 compile_time_ms:1000,
305 };
306
307 assert!(result.success());
308 assert_eq!(result.compile_time_ms, 1000);
309 }
310}