Skip to main content

Grove/Binary/Build/
ServiceRegister.rs

1//! Service Register Module
2//!
3//! Handles service registration with Mountain.
4//! Provides gRPC-based service discovery and registration.
5
6use anyhow::{Context, Result};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10	Protocol::{ProtocolConfig, SpineConnection::SpineConnectionImpl},
11	dev_log,
12};
13
14/// Service register for managing Grove's registration with Mountain
15pub struct ServiceRegister;
16
17/// Service registration information
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ServiceRegistration {
20	/// Service name
21	pub name:String,
22	/// Service type
23	pub service_type:ServiceType,
24	/// Service version
25	pub version:String,
26	/// Service endpoint
27	pub endpoint:String,
28	/// Service capabilities
29	pub capabilities:Vec<String>,
30	/// Metadata
31	pub metadata:serde_json::Value,
32}
33
34/// Service type
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum ServiceType {
37	/// Extension host service
38	ExtensionHost = 0,
39	/// Configuration service
40	Configuration = 1,
41	/// Logging service
42	Logging = 2,
43	/// Custom service
44	Custom = 99,
45}
46
47/// Service registration result
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ServiceRegistrationResult {
50	/// Registration success
51	pub success:bool,
52	/// Service ID assigned by Mountain
53	pub service_id:Option<String>,
54	/// Error message if registration failed
55	pub error:Option<String>,
56	/// Timestamp
57	pub timestamp:u64,
58}
59
60impl ServiceRegister {
61	/// Register Grove with Mountain
62	pub async fn register_with_mountain(
63		service_name:&str,
64		mountain_address:&str,
65		auto_reconnect:bool,
66	) -> Result<ServiceRegistrationResult> {
67		dev_log!(
68			"grove",
69			"Registering service '{}' with Mountain at {}",
70			service_name,
71			mountain_address
72		);
73
74		// Create Spine configuration
75		let spine_config = ProtocolConfig::new().with_mountain_endpoint(service_name.to_string());
76
77		// Create Spine connection
78		let mut connection = SpineConnectionImpl::new(spine_config);
79
80		// Connect to Mountain
81		connection.Connect().await.context("Failed to connect to Mountain")?;
82
83		// Prepare registration information
84		let registration = ServiceRegistration {
85			name:service_name.to_string(),
86			service_type:ServiceType::ExtensionHost,
87			version:env!("CARGO_PKG_VERSION").to_string(),
88			endpoint:mountain_address.to_string(),
89			capabilities:vec![
90				"wasm-runtime".to_string(),
91				"native-rust".to_string(),
92				"cocoon-compatible".to_string(),
93			],
94			metadata:serde_json::json!({
95				"host_type": "grove",
96				"features": ["wasm", "native", "ipc"]
97			}),
98		};
99
100		dev_log!("grove", "Service registration: {:?}", registration);
101
102		// Send registration request (placeholder - in real implementation, use gRPC)
103		let result = ServiceRegistrationResult {
104			success:true,
105			service_id:Some(format!("grove-{}", uuid::Uuid::new_v4())),
106			error:None,
107			timestamp:std::time::SystemTime::now()
108				.duration_since(std::time::UNIX_EPOCH)
109				.map(|d| d.as_secs())
110				.unwrap_or(0),
111		};
112
113		dev_log!("grove", "Service registration result: {:?}", result);
114
115		Ok(result)
116	}
117
118	/// Unregister Grove from Mountain
119	pub async fn unregister_from_mountain(service_id:&str) -> Result<()> {
120		dev_log!("grove", "Unregistering service from Mountain: {}", service_id);
121
122		// Placeholder - in real implementation, call Mountain's unregister service
123		dev_log!("grove", "Service unregistered: {}", service_id);
124
125		Ok(())
126	}
127
128	/// Heartbeat to keep service alive
129	pub async fn send_heartbeat(service_id:&str) -> Result<()> {
130		dev_log!("grove", "Sending heartbeat for service: {}", service_id);
131
132		// Placeholder - in real implementation, send heartbeat to Mountain
133		Ok(())
134	}
135
136	/// Update service information
137	pub async fn update_registration(
138		service_id:&str,
139		registration:ServiceRegistration,
140	) -> Result<ServiceRegistrationResult> {
141		dev_log!("grove", "Updating service registration: {}", service_id);
142
143		dev_log!("grove", "Updated registration: {:?}", registration);
144
145		Ok(ServiceRegistrationResult {
146			success:true,
147			service_id:Some(service_id.to_string()),
148			error:None,
149			timestamp:std::time::SystemTime::now()
150				.duration_since(std::time::UNIX_EPOCH)
151				.map(|d| d.as_secs())
152				.unwrap_or(0),
153		})
154	}
155
156	/// Query service information
157	pub async fn query_service(service_id:&str) -> Result<ServiceRegistration> {
158		dev_log!("grove", "Querying service information: {}", service_id);
159
160		// Placeholder - in real implementation, query Mountain for service info
161		Ok(ServiceRegistration {
162			name:service_id.to_string(),
163			service_type:ServiceType::ExtensionHost,
164			version:"0.1.0".to_string(),
165			endpoint:"127.0.0.1:50050".to_string(),
166			capabilities:Vec::new(),
167			metadata:serde_json::Value::Null,
168		})
169	}
170
171	/// List all registered services
172	pub async fn list_services() -> Result<Vec<ServiceRegistration>> {
173		dev_log!("grove", "Listing all registered services");
174
175		// Placeholder - in real implementation, query Mountain for all services
176		Ok(Vec::new())
177	}
178
179	/// Start heartbeat loop
180	pub async fn start_heartbeat_loop(service_id:&str, interval_sec:u64) -> Result<()> {
181		dev_log!(
182			"grove",
183			"Starting heartbeat loop for service: {} (interval: {}s)",
184			service_id,
185			interval_sec
186		);
187
188		let service_id_owned = service_id.to_string();
189		tokio::spawn(async move {
190			loop {
191				tokio::time::sleep(tokio::time::Duration::from_secs(interval_sec)).await;
192				if let Err(e) = Self::send_heartbeat(&service_id_owned).await {
193					dev_log!("grove", "warn: heartbeat failed: {}", e);
194				}
195			}
196		});
197
198		Ok(())
199	}
200}
201
202impl Default for ServiceRegister {
203	fn default() -> Self { Self }
204}
205
206#[cfg(test)]
207mod tests {
208	use super::*;
209
210	#[test]
211	fn test_service_register_default() {
212		let register = ServiceRegister::default();
213		// Just test that it can be created
214		let _ = register;
215	}
216
217	#[test]
218	fn test_service_type() {
219		assert_eq!(ServiceType::ExtensionHost as i32, 0);
220		assert_eq!(ServiceType::Configuration as i32, 1);
221		assert_eq!(ServiceType::Logging as i32, 2);
222		assert_eq!(ServiceType::Custom as i32, 99);
223	}
224
225	#[tokio::test]
226	async fn test_service_registration_creation() {
227		let registration = ServiceRegistration {
228			name:"test-service".to_string(),
229			service_type:ServiceType::ExtensionHost,
230			version:"1.0.0".to_string(),
231			endpoint:"127.0.0.1:50050".to_string(),
232			capabilities:vec!["test-capability".to_string()],
233			metadata:serde_json::Value::Null,
234		};
235
236		assert_eq!(registration.name, "test-service");
237		assert_eq!(registration.service_type, ServiceType::ExtensionHost);
238	}
239}