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