sql_cli/
api_client.rs

1use reqwest;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::error::Error;
5
6#[derive(Debug, Serialize)]
7pub struct QueryRequest {
8    pub select: Vec<String>,
9    pub where_clause: Option<String>,
10    pub order_by: Option<String>,
11    pub skip: Option<usize>,
12    pub take: Option<usize>,
13}
14
15#[derive(Debug, Deserialize, Clone)]
16pub struct QueryResponse {
17    pub data: Vec<Value>,
18    pub count: usize,
19    pub query: QueryInfo,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub source: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub table: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub cached: Option<bool>,
26}
27
28#[derive(Debug, Deserialize, Clone)]
29pub struct QueryInfo {
30    pub select: Vec<String>,
31    pub where_clause: Option<String>,
32    pub order_by: Option<String>,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct SchemaResponse {
37    pub table_name: String,
38    pub columns: Vec<ColumnInfo>,
39}
40
41#[derive(Debug, Deserialize)]
42pub struct ColumnInfo {
43    pub name: String,
44    pub r#type: String,
45    pub is_nullable: bool,
46}
47
48#[derive(Clone)]
49pub struct ApiClient {
50    pub base_url: String,
51    client: reqwest::blocking::Client,
52}
53
54impl ApiClient {
55    #[must_use]
56    pub fn new(base_url: &str) -> Self {
57        Self {
58            base_url: base_url.to_string(),
59            client: reqwest::blocking::Client::new(),
60        }
61    }
62
63    pub fn query_trades(&self, sql: &str) -> Result<QueryResponse, Box<dyn Error>> {
64        // Parse the SQL to extract components
65        let (select_fields, where_clause, order_by) = self.parse_sql(sql)?;
66
67        let request = QueryRequest {
68            select: select_fields,
69            where_clause,
70            order_by,
71            skip: None,
72            take: Some(100),
73        };
74
75        // Build JSON request, only including non-null fields
76        let mut json_request = serde_json::json!({
77            "select": request.select,
78            "skip": request.skip,
79            "take": request.take,
80        });
81
82        if let Some(where_clause) = &request.where_clause {
83            json_request["where"] = serde_json::Value::String(where_clause.clone());
84        }
85
86        if let Some(order_by) = &request.order_by {
87            json_request["orderBy"] = serde_json::Value::String(order_by.clone());
88        }
89
90        // Debug logging removed to prevent TUI corruption
91
92        let response = self
93            .client
94            .post(format!("{}/api/trade/query", self.base_url))
95            .json(&json_request)
96            .send()?;
97
98        if !response.status().is_success() {
99            let error_text = response.text()?;
100            return Err(format!("API Error: {error_text}").into());
101        }
102
103        let result: QueryResponse = response.json()?;
104        Ok(result)
105    }
106
107    pub fn get_schema(&self) -> Result<SchemaResponse, Box<dyn Error>> {
108        let response = self
109            .client
110            .get(format!("{}/api/trade/schema/trade_deal", self.base_url))
111            .send()?;
112
113        if !response.status().is_success() {
114            return Err("Failed to fetch schema".into());
115        }
116
117        let schema: SchemaResponse = response.json()?;
118        Ok(schema)
119    }
120
121    fn parse_sql(
122        &self,
123        sql: &str,
124    ) -> Result<(Vec<String>, Option<String>, Option<String>), Box<dyn Error>> {
125        let sql_lower = sql.to_lowercase();
126
127        // Extract SELECT fields
128        let select_start = sql_lower.find("select").ok_or("SELECT not found")? + 6;
129        let from_pos = sql_lower.find("from").ok_or("FROM not found")?;
130        let select_part = sql[select_start..from_pos].trim();
131
132        let select_fields: Vec<String> = if select_part == "*" {
133            vec!["*".to_string()]
134        } else {
135            select_part
136                .split(',')
137                .map(|s| s.trim().to_string())
138                .collect()
139        };
140
141        // Extract WHERE clause
142        let where_clause = if let Some(where_pos) = sql_lower.find("where") {
143            let where_start = where_pos + 5;
144            let order_pos = sql_lower.find("order by");
145            let where_end = order_pos.unwrap_or(sql.len());
146            Some(sql[where_start..where_end].trim().to_string())
147        } else {
148            None
149        };
150
151        // Extract ORDER BY
152        let order_by = if let Some(order_pos) = sql_lower.find("order by") {
153            let order_start = order_pos + 8;
154            Some(sql[order_start..].trim().to_string())
155        } else {
156            None
157        };
158
159        Ok((select_fields, where_clause, order_by))
160    }
161}