Grove/Binary/Main/
Entry.rs1use 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).context("Failed to create gRPC transport")?,
199 ))
200 },
201 "ipc" => {
202 use crate::Transport::IPCTransport::IPCTransport;
203 Ok(Transport::IPC(IPCTransport::New().context("Failed to create IPC transport")?))
204 },
205 "wasm" => {
206 use crate::Transport::WASMTransport::WASMTransportImpl;
207 Ok(Transport::WASM(
208 WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
209 .context("Failed to create WASM transport")?,
210 ))
211 },
212 _ => Ok(Transport::default()),
213 }
214 }
215
216 async fn wait_for_shutdown() {
218 info!("Grove is running. Press Ctrl+C to stop.");
219
220 tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
221
222 info!("Shutdown signal received");
223 }
224}
225
226impl Default for Entry {
227 fn default() -> Self { Self }
228}
229
230#[derive(Debug, Clone)]
232pub struct ValidationResult {
233 pub is_valid:bool,
235 pub errors:Vec<String>,
237}
238
239#[derive(Debug, Clone)]
241pub struct BuildResult {
242 pub success:bool,
244 pub output_path:PathBuf,
246 pub compile_time_ms:u64,
248}
249
250impl BuildResult {
251 pub fn success(&self) -> bool { self.success }
253}
254
255#[derive(Debug, Clone)]
257pub struct ExtensionInfo {
258 pub name:String,
260 pub version:String,
262 pub path:PathBuf,
264 pub is_active:bool,
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[tokio::test]
273 async fn test_entry_default() {
274 let entry = Entry::default();
275 let _ = entry;
277 }
278
279 #[tokio::test]
280 async fn test_validate_extension_nonexistent() {
281 let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
282 .await
283 .unwrap();
284
285 assert!(!result.is_valid);
286 assert!(!result.errors.is_empty());
287 }
288
289 #[test]
290 fn test_cli_args_default() {
291 let args = CliArgs::default();
292 assert_eq!(args.mode, "standalone");
293 assert!(args.wasi);
294 }
295
296 #[test]
297 fn test_build_result() {
298 let result = BuildResult {
299 success:true,
300 output_path:PathBuf::from("/test/output.wasm"),
301 compile_time_ms:1000,
302 };
303
304 assert!(result.success());
305 assert_eq!(result.compile_time_ms, 1000);
306 }
307}