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    pub fn new(base_url: &str) -> Self {
56        Self {
57            base_url: base_url.to_string(),
58            client: reqwest::blocking::Client::new(),
59        }
60    }
61
62    pub fn query_trades(&self, sql: &str) -> Result<QueryResponse, Box<dyn Error>> {
63        // Parse the SQL to extract components
64        let (select_fields, where_clause, order_by) = self.parse_sql(sql)?;
65
66        let request = QueryRequest {
67            select: select_fields,
68            where_clause,
69            order_by,
70            skip: None,
71            take: Some(100),
72        };
73
74        // Build JSON request, only including non-null fields
75        let mut json_request = serde_json::json!({
76            "select": request.select,
77            "skip": request.skip,
78            "take": request.take,
79        });
80
81        if let Some(where_clause) = &request.where_clause {
82            json_request["where"] = serde_json::Value::String(where_clause.clone());
83        }
84
85        if let Some(order_by) = &request.order_by {
86            json_request["orderBy"] = serde_json::Value::String(order_by.clone());
87        }
88
89        // Debug logging removed to prevent TUI corruption
90
91        let response = self
92            .client
93            .post(format!("{}/api/trade/query", self.base_url))
94            .json(&json_request)
95            .send()?;
96
97        if !response.status().is_success() {
98            let error_text = response.text()?;
99            return Err(format!("API Error: {}", error_text).into());
100        }
101
102        let result: QueryResponse = response.json()?;
103        Ok(result)
104    }
105
106    pub fn get_schema(&self) -> Result<SchemaResponse, Box<dyn Error>> {
107        let response = self
108            .client
109            .get(format!("{}/api/trade/schema/trade_deal", self.base_url))
110            .send()?;
111
112        if !response.status().is_success() {
113            return Err("Failed to fetch schema".into());
114        }
115
116        let schema: SchemaResponse = response.json()?;
117        Ok(schema)
118    }
119
120    fn parse_sql(
121        &self,
122        sql: &str,
123    ) -> Result<(Vec<String>, Option<String>, Option<String>), Box<dyn Error>> {
124        let sql_lower = sql.to_lowercase();
125
126        // Extract SELECT fields
127        let select_start = sql_lower.find("select").ok_or("SELECT not found")? + 6;
128        let from_pos = sql_lower.find("from").ok_or("FROM not found")?;
129        let select_part = sql[select_start..from_pos].trim();
130
131        let select_fields: Vec<String> = if select_part == "*" {
132            vec!["*".to_string()]
133        } else {
134            select_part
135                .split(',')
136                .map(|s| s.trim().to_string())
137                .collect()
138        };
139
140        // Extract WHERE clause
141        let where_clause = if let Some(where_pos) = sql_lower.find("where") {
142            let where_start = where_pos + 5;
143            let order_pos = sql_lower.find("order by");
144            let where_end = order_pos.unwrap_or(sql.len());
145            Some(sql[where_start..where_end].trim().to_string())
146        } else {
147            None
148        };
149
150        // Extract ORDER BY
151        let order_by = if let Some(order_pos) = sql_lower.find("order by") {
152            let order_start = order_pos + 8;
153            Some(sql[order_start..].trim().to_string())
154        } else {
155            None
156        };
157
158        Ok((select_fields, where_clause, order_by))
159    }
160}