nap_core/provider/
remote.rs1use anyhow::{Context, Result};
8use tracing::info;
9
10use super::{Provider, ProviderStatus, ProviderType};
11
12pub struct RemoteProvider {
14 url_base: String,
15 workspace_id: String,
16 auth_token: Option<String>,
17 http_url: Option<String>,
18}
19
20impl RemoteProvider {
21 pub fn new(url_base: &str, workspace_id: &str) -> Self {
23 Self {
24 url_base: url_base.to_string(),
25 workspace_id: workspace_id.to_string(),
26 auth_token: std::env::var("NAP_REMOTE_AUTH_TOKEN").ok(),
27 http_url: None,
28 }
29 }
30
31 pub fn new_with_default_workspace(url_base: &str) -> Self {
33 Self {
34 url_base: url_base.to_string(),
35 workspace_id: super::get_default_workspace_id(),
36 auth_token: std::env::var("NAP_REMOTE_AUTH_TOKEN").ok(),
37 http_url: None,
38 }
39 }
40
41 pub fn with_http_url(mut self, url: &str) -> Result<Self> {
42 super::http::validate_origin(url)?;
43 self.http_url = Some(url.trim_end_matches('/').to_string());
44 Ok(self)
45 }
46
47 pub fn with_auth_token(mut self, token: &str) -> Self {
49 self.auth_token = Some(token.to_string());
50 self
51 }
52
53 async fn probe(&self) -> Result<()> {
54 let rpc = reqwest::Url::parse(&self.url_base)?;
55 let tls_edge = rpc.host_str() == Some("lore.portals.works")
56 || (matches!(rpc.scheme(), "grpcs" | "lores" | "https")
57 && matches!(rpc.port(), None | Some(443)));
58 if tls_edge {
59 tonic::transport::Endpoint::from_shared(super::http::default_origin(&self.url_base)?)?
62 .connect_timeout(std::time::Duration::from_secs(10))
63 .connect()
64 .await
65 .context("Failed to connect to Lore TLS endpoint")?;
66 } else {
67 reqwest::Client::builder()
68 .timeout(std::time::Duration::from_secs(10))
69 .build()?
70 .get(self.http_health_url()?)
71 .send()
72 .await
73 .context("Failed to connect to remote Lore server")?
74 .error_for_status()
75 .context("Remote Lore server health check failed")?;
76 }
77 Ok(())
78 }
79
80 fn http_health_url(&self) -> Result<String> {
85 Ok(format!(
86 "{}/health_check",
87 self.http_url
88 .clone()
89 .map(Ok)
90 .unwrap_or_else(|| super::http::default_origin(&self.url_base))?
91 ))
92 }
93}
94
95#[async_trait::async_trait]
96impl Provider for RemoteProvider {
97 fn provider_type(&self) -> ProviderType {
98 ProviderType::Remote
99 }
100
101 fn name(&self) -> &str {
102 "Remote Lore Server"
103 }
104
105 async fn initialize(&self) -> Result<()> {
106 info!("Initializing Remote provider for {}", self.url_base);
107 info!("Remote provider initialized");
108 Ok(())
109 }
110
111 async fn ensure_ready(&self) -> Result<()> {
112 info!("Ensuring Remote provider is ready");
113
114 self.initialize().await?;
115
116 self.probe().await?;
117
118 info!("Remote provider is ready");
119 Ok(())
120 }
121
122 fn lore_url_base(&self) -> Result<String> {
123 Ok(self.url_base.clone())
124 }
125
126 fn workspace_id(&self) -> &str {
127 &self.workspace_id
128 }
129
130 async fn health_check(&self) -> Result<bool> {
131 Ok(self.probe().await.is_ok())
132 }
133
134 async fn status(&self) -> Result<ProviderStatus> {
135 let healthy = self.health_check().await.unwrap_or(false);
136
137 let message = if healthy {
138 "Connected".to_string()
139 } else {
140 "Server unreachable".to_string()
141 };
142
143 Ok(ProviderStatus {
144 provider_type: self.provider_type(),
145 ready: healthy,
146 healthy,
147 url_base: self.url_base.clone(),
148 workspace_id: self.workspace_id.clone(),
149 message,
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_remote_provider_creation() {
160 let provider = RemoteProvider::new("lore://localhost:41337", "default");
161 assert_eq!(provider.provider_type(), ProviderType::Remote);
162 assert_eq!(provider.name(), "Remote Lore Server");
163 assert_eq!(provider.workspace_id(), "default");
164 assert_eq!(provider.url_base, "lore://localhost:41337");
165 }
166
167 #[test]
168 fn test_remote_provider_custom_auth() {
169 let provider = RemoteProvider::new("lore://localhost:41337", "default")
170 .with_auth_token("custom-token");
171 assert_eq!(provider.auth_token, Some("custom-token".to_string()));
172 }
173
174 #[test]
175 fn test_http_health_url() {
176 let provider = RemoteProvider::new("lore://localhost:41337", "default");
177 assert_eq!(
178 provider.http_health_url().unwrap(),
179 "http://localhost:41339/health_check"
180 );
181
182 let provider = RemoteProvider::new("lores://example.com:41337", "default");
183 assert_eq!(
184 provider.http_health_url().unwrap(),
185 "https://example.com:41339/health_check"
186 );
187
188 let provider = RemoteProvider::new("lore://192.168.0.27", "default");
190 assert_eq!(
191 provider.http_health_url().unwrap(),
192 "http://192.168.0.27:41339/health_check"
193 );
194 }
195}