use reqwest::{self, header::HeaderValue, Client};
use serde_json::Value;
use urlencoding::encode;
use url::Url;
use crate::SupabaseClient;
#[derive(Debug, Clone, PartialEq)]
pub enum Operator {
Eq,
Neq,
Gt,
Lt,
Gte,
Lte,
Like,
}
impl Operator {
pub fn as_str(&self) -> &'static str {
match self {
Operator::Eq => "eq",
Operator::Neq => "neq",
Operator::Gt => "gt",
Operator::Lt => "lt",
Operator::Gte => "gte",
Operator::Lte => "lte",
Operator::Like => "like",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Filter {
pub column: String,
pub operator: Operator,
pub value: String,
}
impl Filter {
pub fn new(column: &str, operator: Operator, value: &str) -> Self {
Self {
column: column.to_string(),
operator,
value: value.to_string(),
}
}
pub fn to_query(&self) -> String {
format!(
"{}={}.{}",
encode(&self.column),
self.operator.as_str(),
encode(&self.value)
)
}
pub fn to_or_query(&self) -> String {
format!(
"{}.{}.{}",
encode(&self.column),
self.operator.as_str(),
encode(&self.value)
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogicalOperator {
And,
Or,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilterGroup {
pub operator: LogicalOperator,
pub filters: Vec<Filter>,
}
impl FilterGroup {
pub fn new(operator: LogicalOperator, filters: Vec<Filter>) -> Self {
Self { operator, filters }
}
pub fn to_query_string(&self) -> String {
match self.operator {
LogicalOperator::Or => {
let inner: Vec<String> = self.filters.iter().map(|f| f.to_or_query()).collect();
format!("or=({})", inner.join(","))
}
LogicalOperator::And => {
let inner: Vec<String> = self.filters.iter().map(|f| f.to_query()).collect();
inner.join("&")
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SortDirection {
Asc,
Desc,
}
impl SortDirection {
pub fn as_str(&self) -> &'static str {
match self {
SortDirection::Asc => "asc",
SortDirection::Desc => "desc",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sort {
pub column: String,
pub direction: SortDirection,
}
impl Sort {
pub fn new(column: &str, direction: SortDirection) -> Self {
Self {
column: column.to_string(),
direction,
}
}
pub fn to_query(&self) -> String {
format!("order={}.{}", self.column, self.direction.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectQuery {
pub filter: Option<FilterGroup>,
pub sorts: Vec<Sort>,
}
impl SelectQuery {
pub fn new() -> Self {
Self {
filter: None,
sorts: Vec::new(),
}
}
pub fn to_query_string(&self) -> String {
let mut parts = vec![format!("select={}", encode("*"))];
if let Some(ref group) = self.filter {
parts.push(group.to_query_string());
}
for sort in &self.sorts {
parts.push(sort.to_query());
}
parts.join("&")
}
pub fn sort(mut self, column: &str, direction: SortDirection) -> Self {
self.sorts.push(Sort::new(column, direction));
self
}
}
impl SupabaseClient {
pub async fn select(&self, table_name: &str, query: SelectQuery) -> Result<Vec<Value>, String> {
let mut url = Url::parse(&format!("{}/rest/v1/{}", self.url, table_name))
.map_err(|e| e.to_string())?;
let query_string = query.to_query_string();
url.set_query(Some(&query_string));
let client = Client::new();
let response = client
.get(url)
.header("apikey", HeaderValue::from_str(&self.api_key).map_err(|e| e.to_string())?)
.send()
.await
.map_err(|e| e.to_string())?;
let res_status = response.status();
if !res_status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(format!("Request failed with status: {}. Body: {}", res_status, body));
}
let json: Vec<Value> = response.json().await.map_err(|e| e.to_string())?;
Ok(json)
}
}
use std::ops::{BitAnd, BitOr};
#[derive(Debug, Clone, PartialEq)]
pub struct Field(pub String);
impl Field {
pub fn new(s: &str) -> Self {
Field(s.to_string())
}
pub fn eq(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Eq,
value: value.to_string(),
})
}
pub fn neq(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Neq,
value: value.to_string(),
})
}
pub fn gt(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Gt,
value: value.to_string(),
})
}
pub fn lt(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Lt,
value: value.to_string(),
})
}
pub fn gte(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Gte,
value: value.to_string(),
})
}
pub fn lte(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Lte,
value: value.to_string(),
})
}
pub fn like(self, value: impl ToString) -> Query {
Query::Condition(QueryExpr {
column: self.0,
operator: Operator::Like,
value: value.to_string(),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryExpr {
pub column: String,
pub operator: Operator,
pub value: String,
}
impl QueryExpr {
pub fn to_filter(&self) -> Filter {
Filter::new(&self.column, self.operator.clone(), &self.value)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Query {
Condition(QueryExpr),
And(Box<Query>, Box<Query>),
Or(Box<Query>, Box<Query>),
}
impl Query {
pub fn to_query(&self) -> SelectQuery {
let filter_group = self.to_filter_group();
SelectQuery {
filter: Some(filter_group),
sorts: Vec::new(),
}
}
pub fn to_filter_group(&self) -> FilterGroup {
match self {
Query::Condition(expr) => FilterGroup::new(LogicalOperator::And, vec![expr.to_filter()]),
Query::And(left, right) => {
let mut filters = left.to_filter_group().filters;
filters.extend(right.to_filter_group().filters);
FilterGroup::new(LogicalOperator::And, filters)
}
Query::Or(left, right) => {
let mut filters = left.to_filter_group().filters;
filters.extend(right.to_filter_group().filters);
FilterGroup::new(LogicalOperator::Or, filters)
}
}
}
}
impl BitAnd for Query {
type Output = Query;
fn bitand(self, rhs: Query) -> Query {
Query::And(Box::new(self), Box::new(rhs))
}
}
impl BitOr for Query {
type Output = Query;
fn bitor(self, rhs: Query) -> Query {
Query::Or(Box::new(self), Box::new(rhs))
}
}
#[macro_export]
macro_rules! query {
($col:tt == $val:expr) => {
$crate::select::Field::new($col).eq($val)
};
($col:tt != $val:expr) => {
$crate::select::Field::new($col).neq($val)
};
($col:tt > $val:expr) => {
$crate::select::Field::new($col).gt($val)
};
($col:tt < $val:expr) => {
$crate::select::Field::new($col).lt($val)
};
($col:tt >= $val:expr) => {
$crate::select::Field::new($col).gte($val)
};
($col:tt <= $val:expr) => {
$crate::select::Field::new($col).lte($val)
};
($left:tt & $right:tt) => {
($crate::select::q!($left)) & ($crate::select::q!($right))
};
($left:tt | $right:tt) => {
($crate::select::q!($left)) | ($crate::select::q!($right))
};
( ( $($inner:tt)+ ) ) => {
$crate::select::q!($($inner)+)
};
}