1use async_trait::async_trait;
6use reqwest::Client;
7
8use crate::client::GraphmindClient;
9use crate::error::{GraphmindError, GraphmindResult};
10use crate::models::{QueryResult, ServerStatus};
11
12pub struct RemoteClient {
16 http_base_url: String,
17 http_client: Client,
18}
19
20impl RemoteClient {
21 pub fn new(http_base_url: &str) -> Self {
29 Self {
30 http_base_url: http_base_url.trim_end_matches('/').to_string(),
31 http_client: Client::new(),
32 }
33 }
34
35 async fn post_query(&self, graph: &str, cypher: &str) -> GraphmindResult<QueryResult> {
37 let url = format!("{}/api/query", self.http_base_url);
38 let body = serde_json::json!({ "query": cypher, "graph": graph });
39
40 let response = self.http_client.post(&url).json(&body).send().await?;
41
42 if response.status().is_success() {
43 let result: QueryResult = response.json().await?;
44 Ok(result)
45 } else {
46 let error_body: serde_json::Value = response
47 .json()
48 .await
49 .unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
50 let msg = error_body
51 .get("error")
52 .and_then(|v| v.as_str())
53 .unwrap_or("Unknown error")
54 .to_string();
55 Err(GraphmindError::QueryError(msg))
56 }
57 }
58}
59
60#[async_trait]
61impl GraphmindClient for RemoteClient {
62 async fn query(&self, graph: &str, cypher: &str) -> GraphmindResult<QueryResult> {
63 self.post_query(graph, cypher).await
64 }
65
66 async fn query_readonly(&self, graph: &str, cypher: &str) -> GraphmindResult<QueryResult> {
67 self.post_query(graph, cypher).await
68 }
69
70 async fn delete_graph(&self, graph: &str) -> GraphmindResult<()> {
71 self.post_query(graph, "MATCH (n) DELETE n").await?;
74 Ok(())
75 }
76
77 async fn list_graphs(&self) -> GraphmindResult<Vec<String>> {
78 Ok(vec!["default".to_string()])
80 }
81
82 async fn status(&self, graph: &str) -> GraphmindResult<ServerStatus> {
83 let url = format!("{}/api/status?graph={}", self.http_base_url, graph);
84 let response = self.http_client.get(&url).send().await?;
85
86 if response.status().is_success() {
87 let status: ServerStatus = response.json().await?;
88 Ok(status)
89 } else {
90 Err(GraphmindError::ConnectionError(format!(
91 "Status endpoint returned {}",
92 response.status()
93 )))
94 }
95 }
96
97 async fn ping(&self) -> GraphmindResult<String> {
98 let status = self.status("default").await?;
99 if status.status == "healthy" {
100 Ok("PONG".to_string())
101 } else {
102 Err(GraphmindError::ConnectionError(format!(
103 "Server unhealthy: {}",
104 status.status
105 )))
106 }
107 }
108
109 async fn schema(&self, graph: &str) -> GraphmindResult<String> {
110 let url = format!("{}/api/schema?graph={}", self.http_base_url, graph);
111 let response = self.http_client.get(&url).send().await?;
112
113 if response.status().is_success() {
114 let body: serde_json::Value = response.json().await?;
115 Ok(serde_json::to_string_pretty(&body).unwrap_or_else(|_| body.to_string()))
116 } else {
117 Err(GraphmindError::ConnectionError(format!(
118 "Schema endpoint returned {}",
119 response.status()
120 )))
121 }
122 }
123
124 async fn explain(&self, graph: &str, cypher: &str) -> GraphmindResult<QueryResult> {
125 let prefixed = if cypher.trim().to_uppercase().starts_with("EXPLAIN") {
126 cypher.to_string()
127 } else {
128 format!("EXPLAIN {}", cypher)
129 };
130 self.post_query(graph, &prefixed).await
131 }
132
133 async fn profile(&self, graph: &str, cypher: &str) -> GraphmindResult<QueryResult> {
134 let prefixed = if cypher.trim().to_uppercase().starts_with("PROFILE") {
135 cypher.to_string()
136 } else {
137 format!("PROFILE {}", cypher)
138 };
139 self.post_query(graph, &prefixed).await
140 }
141}