use crate::client::WOWSQLClient;
use crate::errors::WOWSQLError;
use crate::models::{
CreateResponse, DeleteResponse, FilterExpression, FilterOperator, QueryResponse, SortDirection,
UpdateResponse,
};
use serde::de::DeserializeOwned;
use serde_json::Value;
pub struct QueryBuilder<'a> {
client: &'a WOWSQLClient,
table_name: String,
selected_columns: Option<Vec<String>>,
filters: Vec<FilterExpression>,
order_column: Option<String>,
order_direction: Option<SortDirection>,
limit_value: Option<usize>,
offset_value: Option<usize>,
}
impl<'a> QueryBuilder<'a> {
pub(crate) fn new(client: &'a WOWSQLClient, table_name: &str) -> Self {
Self {
client,
table_name: table_name.to_string(),
selected_columns: None,
filters: Vec::new(),
order_column: None,
order_direction: None,
limit_value: None,
offset_value: None,
}
}
pub fn select(mut self, columns: &[&str]) -> Self {
self.selected_columns = Some(columns.iter().map(|s| s.to_string()).collect());
self
}
pub fn eq(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Eq,
value: Some(value),
});
self
}
pub fn neq(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Neq,
value: Some(value),
});
self
}
pub fn gt(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Gt,
value: Some(value),
});
self
}
pub fn gte(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Gte,
value: Some(value),
});
self
}
pub fn lt(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Lt,
value: Some(value),
});
self
}
pub fn lte(mut self, column: &str, value: Value) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Lte,
value: Some(value),
});
self
}
pub fn like(mut self, column: &str, pattern: &str) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::Like,
value: Some(Value::String(pattern.to_string())),
});
self
}
pub fn is_null(mut self, column: &str) -> Self {
self.filters.push(FilterExpression {
column: column.to_string(),
operator: FilterOperator::IsNull,
value: None,
});
self
}
pub fn order_by(mut self, column: &str, direction: SortDirection) -> Self {
self.order_column = Some(column.to_string());
self.order_direction = Some(direction);
self
}
pub fn limit(mut self, value: usize) -> Self {
self.limit_value = Some(value);
self
}
pub fn offset(mut self, value: usize) -> Self {
self.offset_value = Some(value);
self
}
pub async fn execute<T: DeserializeOwned>(self) -> Result<QueryResponse<T>, WOWSQLError> {
let mut url = format!("{}/api/v2/{}", self.client.base_url, self.table_name);
let params = self.build_query_params();
if !params.is_empty() {
let query_string: Vec<String> = params
.iter()
.map(|(k, v)| format!("{}={}", k, urlencoding::encode(&v.to_string())))
.collect();
url = format!("{}?{}", url, query_string.join("&"));
}
let response: Value = self.client.execute_request(&url, "GET", None).await?;
let data: Vec<T> = if let Some(data_array) = response.get("data").and_then(|v| v.as_array())
{
let mut results = Vec::new();
for item in data_array {
if let Ok(parsed) = serde_json::from_value(item.clone()) {
results.push(parsed);
}
}
results
} else {
Vec::new()
};
let count = response
.get("count")
.and_then(|v| v.as_u64())
.unwrap_or(data.len() as u64) as usize;
let total = response
.get("total")
.and_then(|v| v.as_u64())
.map(|v| v as usize);
let error = response
.get("error")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(QueryResponse {
data,
count,
total,
error,
})
}
pub async fn first<T: DeserializeOwned>(self) -> Result<Option<T>, WOWSQLError> {
Ok(self.limit(1).execute().await?.data.into_iter().next())
}
pub async fn create(self, data: Value) -> Result<CreateResponse, WOWSQLError> {
self.insert(data).await
}
pub async fn insert(self, data: Value) -> Result<CreateResponse, WOWSQLError> {
let url = format!("{}/api/v2/{}", self.client.base_url, self.table_name);
self.client.execute_request(&url, "POST", Some(data)).await
}
pub async fn update(self, data: Value) -> Result<UpdateResponse, WOWSQLError> {
let url = format!("{}/api/v2/{}", self.client.base_url, self.table_name);
let mut body = serde_json::json!({ "data": data });
if !self.filters.is_empty() {
let filters_json: Vec<Value> = self
.filters
.iter()
.map(|f| {
serde_json::json!({
"column": f.column,
"operator": f.operator,
"value": f.value
})
})
.collect();
body["filters"] = Value::Array(filters_json);
}
self.client.execute_request(&url, "PATCH", Some(body)).await
}
pub async fn delete(self) -> Result<DeleteResponse, WOWSQLError> {
let url = format!("{}/api/v2/{}", self.client.base_url, self.table_name);
let body = if !self.filters.is_empty() {
let filters_json: Vec<Value> = self
.filters
.iter()
.map(|f| {
serde_json::json!({
"column": f.column,
"operator": f.operator,
"value": f.value
})
})
.collect();
Some(serde_json::json!({ "filters": filters_json }))
} else {
None
};
self.client.execute_request(&url, "DELETE", body).await
}
fn build_query_params(&self) -> Vec<(&str, String)> {
let mut params = Vec::new();
if let Some(ref columns) = self.selected_columns {
params.push(("select", columns.join(",")));
}
if !self.filters.is_empty() {
let filter_strings: Vec<String> = self
.filters
.iter()
.map(|f| {
let value_str = f
.value
.as_ref()
.map(|v| v.to_string())
.unwrap_or_else(|| "null".to_string());
format!("{}.{}.{}", f.column, f.operator, value_str)
})
.collect();
params.push(("filter", filter_strings.join(",")));
}
if let Some(ref order_col) = self.order_column {
params.push(("order", order_col.clone()));
}
if let Some(ref order_dir) = self.order_direction {
params.push(("order_direction", format!("{:?}", order_dir).to_lowercase()));
}
if let Some(limit) = self.limit_value {
params.push(("limit", limit.to_string()));
}
if let Some(offset) = self.offset_value {
params.push(("offset", offset.to_string()));
}
params
}
#[allow(dead_code)]
fn build_query_body(&self) -> Value {
let mut body = serde_json::json!({});
if let Some(ref columns) = self.selected_columns {
body["columns"] =
Value::Array(columns.iter().map(|c| Value::String(c.clone())).collect());
}
if !self.filters.is_empty() {
let filters_json: Vec<Value> = self
.filters
.iter()
.map(|f| {
serde_json::json!({
"column": f.column,
"operator": f.operator,
"value": f.value
})
})
.collect();
body["filters"] = Value::Array(filters_json);
}
if let Some(ref order_col) = self.order_column {
body["order_by"] = Value::String(order_col.clone());
}
if let Some(ref order_dir) = self.order_direction {
body["order_direction"] = serde_json::to_value(order_dir).unwrap();
}
if let Some(limit) = self.limit_value {
body["limit"] = Value::Number(limit.into());
}
if let Some(offset) = self.offset_value {
body["offset"] = Value::Number(offset.into());
}
body
}
}