use crate::api_client::QueryResponse;
use crate::data::csv_datasource::CsvApiClient;
use crate::data::data_provider::DataProvider;
use std::fmt::Debug;
pub struct CsvClientAdapter<'a> {
client: &'a CsvApiClient,
cached_response: Option<QueryResponse>,
}
impl<'a> CsvClientAdapter<'a> {
#[must_use]
pub fn new(client: &'a CsvApiClient) -> Self {
Self {
client,
cached_response: None,
}
}
pub fn execute_query(&mut self, sql: &str) -> anyhow::Result<()> {
let response = self.client.query_csv(sql)?;
self.cached_response = Some(response);
Ok(())
}
}
impl Debug for CsvClientAdapter<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CsvClientAdapter")
.field("row_count", &self.get_row_count())
.field("column_count", &self.get_column_count())
.field("has_data", &self.cached_response.is_some())
.finish()
}
}
impl DataProvider for CsvClientAdapter<'_> {
fn get_row(&self, index: usize) -> Option<Vec<String>> {
self.cached_response.as_ref().and_then(|response| {
response.data.get(index).map(|json_value| {
if let Some(obj) = json_value.as_object() {
let columns = self.get_column_names();
columns
.iter()
.map(|col| {
obj.get(col)
.map(|v| {
match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
other => other.to_string(),
}
})
.unwrap_or_default()
})
.collect()
} else {
vec![json_value.to_string()]
}
})
})
}
fn get_column_names(&self) -> Vec<String> {
if let Some(ref response) = self.cached_response {
if let Some(first_row) = response.data.first() {
if let Some(obj) = first_row.as_object() {
return obj.keys().map(std::string::ToString::to_string).collect();
}
}
}
self.client
.get_schema()
.and_then(|schema| schema.values().next().cloned())
.unwrap_or_default()
}
fn get_row_count(&self) -> usize {
self.cached_response.as_ref().map_or(0, |r| r.data.len())
}
fn get_column_count(&self) -> usize {
self.get_column_names().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::csv_datasource::CsvApiClient;
use serde_json::json;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_csv_client_adapter_basic() {
let temp_dir = tempdir().unwrap();
let json_path = temp_dir.path().join("test_data.json");
let test_data = json!([
{
"id": 1,
"name": "Alice",
"age": 30
},
{
"id": 2,
"name": "Bob",
"age": 25
},
{
"id": 3,
"name": "Charlie",
"age": 35
}
]);
fs::write(&json_path, serde_json::to_string(&test_data).unwrap()).unwrap();
let mut client = CsvApiClient::new();
client.load_json(&json_path, "test").unwrap();
let mut adapter = CsvClientAdapter::new(&client);
adapter.execute_query("SELECT * FROM test").unwrap();
assert_eq!(adapter.get_row_count(), 3);
assert_eq!(adapter.get_column_count(), 3);
let col_names = adapter.get_column_names();
assert!(col_names.contains(&"id".to_string()));
assert!(col_names.contains(&"name".to_string()));
assert!(col_names.contains(&"age".to_string()));
let row = adapter.get_row(0).unwrap();
assert!(row.contains(&"1".to_string()));
assert!(row.contains(&"Alice".to_string()));
assert!(row.contains(&"30".to_string()));
let row = adapter.get_row(2).unwrap();
assert!(row.contains(&"3".to_string()));
assert!(row.contains(&"Charlie".to_string()));
assert!(row.contains(&"35".to_string()));
assert!(adapter.get_row(3).is_none());
}
#[test]
fn test_csv_client_adapter_empty() {
let client = CsvApiClient::new();
let adapter = CsvClientAdapter::new(&client);
assert_eq!(adapter.get_row_count(), 0);
assert_eq!(adapter.get_column_count(), 0);
assert!(adapter.get_row(0).is_none());
}
#[test]
fn test_csv_client_adapter_with_filter() {
let temp_dir = tempdir().unwrap();
let json_path = temp_dir.path().join("test_data.json");
let test_data = json!([
{
"id": 1,
"name": "Alice",
"status": "active"
},
{
"id": 2,
"name": "Bob",
"status": "inactive"
},
{
"id": 3,
"name": "Charlie",
"status": "active"
}
]);
fs::write(&json_path, serde_json::to_string(&test_data).unwrap()).unwrap();
let mut client = CsvApiClient::new();
client.load_json(&json_path, "test").unwrap();
let mut adapter = CsvClientAdapter::new(&client);
adapter
.execute_query("SELECT * FROM test WHERE status = 'active'")
.unwrap();
assert_eq!(adapter.get_row_count(), 2);
for i in 0..adapter.get_row_count() {
let row = adapter.get_row(i).unwrap();
assert!(row.contains(&"active".to_string()));
}
}
}