1use crate::errors::WOWSQLError;
2use crate::models::TableSchema;
3use crate::table::Table;
4use reqwest::Client;
5use serde_json::Value;
6use std::time::Duration;
7
8pub struct WOWSQLClientBuilder {
10 project_url: String,
11 api_key: String,
12 base_domain: String,
13 secure: bool,
14 timeout: u64,
15 verify_ssl: bool,
16}
17
18impl WOWSQLClientBuilder {
19 fn new(project_url: &str, api_key: &str) -> Self {
20 Self {
21 project_url: project_url.to_string(),
22 api_key: api_key.to_string(),
23 base_domain: "wowsql.com".to_string(),
24 secure: true,
25 timeout: 30,
26 verify_ssl: true,
27 }
28 }
29
30 pub fn base_domain(mut self, domain: &str) -> Self {
31 self.base_domain = domain.to_string();
32 self
33 }
34
35 pub fn secure(mut self, secure: bool) -> Self {
36 self.secure = secure;
37 self
38 }
39
40 pub fn timeout(mut self, seconds: u64) -> Self {
41 self.timeout = seconds;
42 self
43 }
44
45 pub fn verify_ssl(mut self, verify: bool) -> Self {
46 self.verify_ssl = verify;
47 self
48 }
49
50 pub fn build(self) -> Result<WOWSQLClient, WOWSQLError> {
51 let base_url =
52 WOWSQLClient::build_base_url(&self.project_url, &self.base_domain, self.secure);
53
54 let client = Client::builder()
55 .timeout(Duration::from_secs(self.timeout))
56 .danger_accept_invalid_certs(!self.verify_ssl)
57 .build()
58 .map_err(|e| {
59 WOWSQLError::new(&format!("Failed to create HTTP client: {}", e), None, None)
60 })?;
61
62 Ok(WOWSQLClient {
63 base_url,
64 api_key: self.api_key,
65 base_domain: self.base_domain,
66 secure: self.secure,
67 client,
68 })
69 }
70}
71
72pub struct WOWSQLClient {
74 pub(crate) base_url: String,
75 pub(crate) api_key: String,
76 #[allow(dead_code)]
77 pub(crate) base_domain: String,
78 #[allow(dead_code)]
79 pub(crate) secure: bool,
80 pub(crate) client: Client,
81}
82
83impl WOWSQLClient {
84 pub fn new(project_url: &str, api_key: &str) -> Result<Self, WOWSQLError> {
86 WOWSQLClientBuilder::new(project_url, api_key).build()
87 }
88
89 pub fn builder(project_url: &str, api_key: &str) -> WOWSQLClientBuilder {
91 WOWSQLClientBuilder::new(project_url, api_key)
92 }
93
94 pub(crate) fn build_base_url(project_url: &str, base_domain: &str, secure: bool) -> String {
96 let mut url = project_url.trim().to_string();
97
98 if url.ends_with('/') {
99 url.pop();
100 }
101
102 if url.starts_with("http://") || url.starts_with("https://") {
103 url = url.trim_end_matches('/').to_string();
104 if url.ends_with("/api") {
105 url = url.trim_end_matches("/api").to_string();
106 }
107 return url;
108 }
109
110 let protocol = if secure { "https" } else { "http" };
111
112 if url.contains(&format!(".{}", base_domain)) || url.ends_with(base_domain) {
113 url = format!("{}://{}", protocol, url);
114 } else {
115 url = format!("{}://{}.{}", protocol, url, base_domain);
116 }
117
118 url = url.trim_end_matches('/').to_string();
119 if url.ends_with("/api") {
120 url = url.trim_end_matches("/api").to_string();
121 }
122
123 url
124 }
125
126 pub fn table(&self, table_name: &str) -> Table<'_> {
128 Table::new(self, table_name)
129 }
130
131 pub async fn list_tables(&self) -> Result<Vec<String>, WOWSQLError> {
133 let url = format!("{}/api/v2/tables", self.base_url);
134 let response: Value = self.execute_request(&url, "GET", None).await?;
135
136 if let Some(tables) = response.get("tables") {
137 if let Some(tables_array) = tables.as_array() {
138 return Ok(tables_array
139 .iter()
140 .filter_map(|v| v.as_str().map(|s| s.to_string()))
141 .collect());
142 }
143 }
144
145 Ok(vec![])
146 }
147
148 pub async fn get_table_schema(&self, table_name: &str) -> Result<TableSchema, WOWSQLError> {
150 let url = format!("{}/api/v2/tables/{}/schema", self.base_url, table_name);
151 self.execute_request(&url, "GET", None).await
152 }
153
154 pub async fn query<T: serde::de::DeserializeOwned>(
156 &self,
157 sql: &str,
158 ) -> Result<Vec<T>, WOWSQLError> {
159 let url = format!("{}/api/v2/query", self.base_url);
160 let body = serde_json::json!({ "sql": sql });
161 let response: Value = self.execute_request(&url, "POST", Some(body)).await?;
162
163 if let Some(data) = response.get("data") {
164 if let Some(data_array) = data.as_array() {
165 let mut results = Vec::new();
166 for item in data_array {
167 if let Ok(parsed) = serde_json::from_value(item.clone()) {
168 results.push(parsed);
169 }
170 }
171 return Ok(results);
172 }
173 }
174
175 Ok(vec![])
176 }
177
178 pub async fn health(&self) -> Result<Value, WOWSQLError> {
180 let url = format!("{}/api/v2/health", self.base_url);
181 self.execute_request(&url, "GET", None).await
182 }
183
184 pub fn close(self) {
186 drop(self);
187 }
188
189 pub(crate) async fn execute_request<T: serde::de::DeserializeOwned>(
191 &self,
192 url: &str,
193 method: &str,
194 body: Option<Value>,
195 ) -> Result<T, WOWSQLError> {
196 let mut request = self
197 .client
198 .request(
199 method
200 .parse()
201 .map_err(|_| WOWSQLError::new("Invalid method", None, None))?,
202 url,
203 )
204 .header("Content-Type", "application/json")
205 .header("Accept", "application/json")
206 .header("Authorization", format!("Bearer {}", self.api_key));
207
208 if let Some(body) = body {
209 request = request.json(&body);
210 }
211
212 let response = request
213 .send()
214 .await
215 .map_err(|e| WOWSQLError::new(&format!("Request failed: {}", e), None, None))?;
216
217 let status = response.status();
218 let text = response.text().await.map_err(|e| {
219 WOWSQLError::new(&format!("Failed to read response: {}", e), None, None)
220 })?;
221
222 if !status.is_success() {
223 return Err(WOWSQLError::from_response(status.as_u16(), &text));
224 }
225
226 serde_json::from_str(&text)
227 .map_err(|e| WOWSQLError::new(&format!("Failed to parse response: {}", e), None, None))
228 }
229}