grove/Binary/Main/
Entry.rs1use std::path::PathBuf;
7
8use anyhow::{Context, Result};
9use tracing::{error, info, instrument};
10
11use crate::{
12 Binary::Build::ServiceRegister,
13 Binary::Main::CliArgs,
14 Host::{HostConfig, ExtensionHost::ExtensionHostImpl},
15 Transport::Transport,
16 WASM::Runtime::{WASMConfig, WASMRuntime},
17};
18
19pub struct Entry;
21
22impl Entry {
23 #[instrument(skip(args))]
25 pub async fn run(args:CliArgs) -> Result<()> {
26 info!("Starting Grove v{}", env!("CARGO_PKG_VERSION"));
27 info!("Mode: {}", args.mode);
28
29 match args.mode.as_str() {
30 "standalone" => Self::run_standalone(args).await,
31 "service" => Self::run_service(args).await,
32 "validate" => Self::run_validation(args).await,
33 _ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
34 }
35 }
36
37 #[instrument(skip(args))]
39 async fn run_standalone(args:CliArgs) -> Result<()> {
40 info!("Starting Grove in standalone mode");
41
42 let transport = Self::create_transport(&args)?;
44
45 let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
47
48 let host = ExtensionHostImpl::with_config(transport, host_config)
50 .await
51 .context("Failed to create extension host")?;
52
53 if let Some(extension_path) = args.extension {
55 let path = PathBuf::from(extension_path);
56 host.load_extension(&path).await?;
57 host.activate_all().await?;
58 } else {
59 info!("No extension specified, running in daemon mode");
60 }
61
62 Self::wait_for_shutdown().await;
64
65 host.shutdown().await?;
67
68 Ok(())
69 }
70
71 #[instrument(skip(args))]
73 async fn run_service(args:CliArgs) -> Result<()> {
74 info!("Starting Grove as service");
75
76 let transport = Transport::default();
78
79 #[cfg(feature = "gRPC")]
81 {
82 match crate::Binary::Build::ServiceRegister::register_with_mountain(
83 "grove-host",
84 &args.mountain_address,
85 true, ).await {
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 info!("Optimization level: {}", opt_level);
178
179 Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
182 }
183
184 pub async fn list_extensions(detailed:bool) -> Result<Vec<ExtensionInfo>> {
186 info!("Listing extensions");
187
188 Ok(Vec::new())
191 }
192
193 fn create_transport(args:&CliArgs) -> Result<Transport> {
195 match args.transport.as_str() {
196 "grpc" => {
197 use crate::Transport::gRPCTransport::GrpcTransport;
198 Ok(Transport::gRPC(
199 GrpcTransport::new(&args.grpc_address)
200 .context("Failed to create gRPC transport")?,
201 ))
202 },
203 "ipc" => {
204 use crate::Transport::IPCTransport::IPCTransportImpl;
205 Ok(Transport::IPC(
206 IPCTransportImpl::new().context("Failed to create IPC transport")?,
207 ))
208 },
209 "wasm" => {
210 use crate::Transport::WASMTransport::WASMTransportImpl;
211 Ok(Transport::WASM(
212 WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
213 .context("Failed to create WASM transport")?,
214 ))
215 },
216 _ => Ok(Transport::default()),
217 }
218 }
219
220 async fn wait_for_shutdown() {
222 info!("Grove is running. Press Ctrl+C to stop.");
223
224 tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
225
226 info!("Shutdown signal received");
227 }
228}
229
230impl Default for Entry {
231 fn default() -> Self { Self }
232}
233
234#[derive(Debug, Clone)]
236pub struct ValidationResult {
237 pub is_valid:bool,
239 pub errors:Vec<String>,
241}
242
243#[derive(Debug, Clone)]
245pub struct BuildResult {
246 pub success:bool,
248 pub output_path:PathBuf,
250 pub compile_time_ms:u64,
252}
253
254impl BuildResult {
255 pub fn success(&self) -> bool { self.success }
257}
258
259#[derive(Debug, Clone)]
261pub struct ExtensionInfo {
262 pub name:String,
264 pub version:String,
266 pub path:PathBuf,
268 pub is_active:bool,
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[tokio::test]
277 async fn test_entry_default() {
278 let entry = Entry::default();
279 let _ = entry;
281 }
282
283 #[tokio::test]
284 async fn test_validate_extension_nonexistent() {
285 let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
286 .await
287 .unwrap();
288
289 assert!(!result.is_valid);
290 assert!(!result.errors.is_empty());
291 }
292
293 #[test]
294 fn test_cli_args_default() {
295 let args = CliArgs::default();
296 assert_eq!(args.mode, "standalone");
297 assert!(args.wasi);
298 }
299
300 #[test]
301 fn test_build_result() {
302 let result = BuildResult {
303 success:true,
304 output_path:PathBuf::from("/test/output.wasm"),
305 compile_time_ms:1000,
306 };
307
308 assert!(result.success());
309 assert_eq!(result.compile_time_ms, 1000);
310 }
311}