use anyhow::{Context, Result};
use tracing::info;
use super::{Provider, ProviderStatus, ProviderType};
pub struct RemoteProvider {
url_base: String,
workspace_id: String,
auth_token: Option<String>,
http_url: Option<String>,
}
impl RemoteProvider {
pub fn new(url_base: &str, workspace_id: &str) -> Self {
Self {
url_base: url_base.to_string(),
workspace_id: workspace_id.to_string(),
auth_token: std::env::var("NAP_REMOTE_AUTH_TOKEN").ok(),
http_url: None,
}
}
pub fn new_with_default_workspace(url_base: &str) -> Self {
Self {
url_base: url_base.to_string(),
workspace_id: super::get_default_workspace_id(),
auth_token: std::env::var("NAP_REMOTE_AUTH_TOKEN").ok(),
http_url: None,
}
}
pub fn with_http_url(mut self, url: &str) -> Result<Self> {
super::http::validate_origin(url)?;
self.http_url = Some(url.trim_end_matches('/').to_string());
Ok(self)
}
pub fn with_auth_token(mut self, token: &str) -> Self {
self.auth_token = Some(token.to_string());
self
}
async fn probe(&self) -> Result<()> {
let rpc = reqwest::Url::parse(&self.url_base)?;
let tls_edge = rpc.host_str() == Some("lore.portals.works")
|| (matches!(rpc.scheme(), "grpcs" | "lores" | "https")
&& matches!(rpc.port(), None | Some(443)));
if tls_edge {
tonic::transport::Endpoint::from_shared(super::http::default_origin(&self.url_base)?)?
.connect_timeout(std::time::Duration::from_secs(10))
.connect()
.await
.context("Failed to connect to Lore TLS endpoint")?;
} else {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()?
.get(self.http_health_url()?)
.send()
.await
.context("Failed to connect to remote Lore server")?
.error_for_status()
.context("Remote Lore server health check failed")?;
}
Ok(())
}
fn http_health_url(&self) -> Result<String> {
Ok(format!(
"{}/health_check",
self.http_url
.clone()
.map(Ok)
.unwrap_or_else(|| super::http::default_origin(&self.url_base))?
))
}
}
#[async_trait::async_trait]
impl Provider for RemoteProvider {
fn provider_type(&self) -> ProviderType {
ProviderType::Remote
}
fn name(&self) -> &str {
"Remote Lore Server"
}
async fn initialize(&self) -> Result<()> {
info!("Initializing Remote provider for {}", self.url_base);
info!("Remote provider initialized");
Ok(())
}
async fn ensure_ready(&self) -> Result<()> {
info!("Ensuring Remote provider is ready");
self.initialize().await?;
self.probe().await?;
info!("Remote provider is ready");
Ok(())
}
fn lore_url_base(&self) -> Result<String> {
Ok(self.url_base.clone())
}
fn workspace_id(&self) -> &str {
&self.workspace_id
}
async fn health_check(&self) -> Result<bool> {
Ok(self.probe().await.is_ok())
}
async fn status(&self) -> Result<ProviderStatus> {
let healthy = self.health_check().await.unwrap_or(false);
let message = if healthy {
"Connected".to_string()
} else {
"Server unreachable".to_string()
};
Ok(ProviderStatus {
provider_type: self.provider_type(),
ready: healthy,
healthy,
url_base: self.url_base.clone(),
workspace_id: self.workspace_id.clone(),
message,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remote_provider_creation() {
let provider = RemoteProvider::new("lore://localhost:41337", "default");
assert_eq!(provider.provider_type(), ProviderType::Remote);
assert_eq!(provider.name(), "Remote Lore Server");
assert_eq!(provider.workspace_id(), "default");
assert_eq!(provider.url_base, "lore://localhost:41337");
}
#[test]
fn test_remote_provider_custom_auth() {
let provider = RemoteProvider::new("lore://localhost:41337", "default")
.with_auth_token("custom-token");
assert_eq!(provider.auth_token, Some("custom-token".to_string()));
}
#[test]
fn test_http_health_url() {
let provider = RemoteProvider::new("lore://localhost:41337", "default");
assert_eq!(
provider.http_health_url().unwrap(),
"http://localhost:41339/health_check"
);
let provider = RemoteProvider::new("lores://example.com:41337", "default");
assert_eq!(
provider.http_health_url().unwrap(),
"https://example.com:41339/health_check"
);
let provider = RemoteProvider::new("lore://192.168.0.27", "default");
assert_eq!(
provider.http_health_url().unwrap(),
"http://192.168.0.27:41339/health_check"
);
}
}