use crate::client::backend::QueryResult;
use crate::client::query_builder::{Condition, ConditionOperator, OrderDirection};
use serde_json::{Map, Value, json};
#[derive(Debug, Clone, Copy, Default)]
pub struct Gateway;
impl Gateway {
pub const FETCH_PATH: &str = "/gateway/fetch";
pub const INSERT_PATH: &str = "/gateway/insert";
pub const UPDATE_PATH: &str = "/gateway/update";
pub const DELETE_PATH: &str = "/gateway/delete";
pub const QUERY_PATH: &str = "/gateway/query";
pub const SQL_PATH: &str = "/gateway/sql";
pub const RPC_PATH: &str = "/gateway/rpc";
pub const LEGACY_SQL_PATH: &str = "/query/sql";
pub fn routes(base_url: &str) -> GatewayRoutes {
GatewayRoutes::for_base_url(base_url)
}
pub fn endpoint(base_url: &str, path: &str) -> String {
format!(
"{}/{}",
base_url.trim_end_matches('/'),
path.trim_start_matches('/')
)
}
pub fn path_for(operation: GatewayOperation) -> &'static str {
operation.gateway_path()
}
pub fn request() -> GatewayRequestFactory {
GatewayRequestFactory
}
pub const REQUEST: GatewayRequestFactory = GatewayRequestFactory;
}
pub const GATEWAY_FETCH_PATH: &str = Gateway::FETCH_PATH;
pub const GATEWAY_INSERT_PATH: &str = Gateway::INSERT_PATH;
pub const GATEWAY_UPDATE_PATH: &str = Gateway::UPDATE_PATH;
pub const GATEWAY_DELETE_PATH: &str = Gateway::DELETE_PATH;
pub const GATEWAY_QUERY_PATH: &str = Gateway::QUERY_PATH;
pub const GATEWAY_SQL_PATH: &str = Gateway::SQL_PATH;
pub const GATEWAY_RPC_PATH: &str = Gateway::RPC_PATH;
pub const LEGACY_SQL_PATH: &str = Gateway::LEGACY_SQL_PATH;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GatewayRoutes {
pub fetch: String,
pub insert: String,
pub update: String,
pub delete: String,
pub query: String,
pub sql: String,
pub rpc: String,
}
impl GatewayRoutes {
pub fn for_base_url(base_url: &str) -> Self {
Self {
fetch: Gateway::endpoint(base_url, Gateway::FETCH_PATH),
insert: Gateway::endpoint(base_url, Gateway::INSERT_PATH),
update: Gateway::endpoint(base_url, Gateway::UPDATE_PATH),
delete: Gateway::endpoint(base_url, Gateway::DELETE_PATH),
query: Gateway::endpoint(base_url, Gateway::QUERY_PATH),
sql: Gateway::endpoint(base_url, Gateway::SQL_PATH),
rpc: Gateway::endpoint(base_url, Gateway::RPC_PATH),
}
}
}
pub fn build_gateway_endpoint(base_url: &str, path: &str) -> String {
Gateway::endpoint(base_url, path)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayOperation {
Fetch,
Insert,
Update,
Delete,
Sql,
Rpc,
}
pub trait GatewayPath {
fn gateway_path(&self) -> &'static str;
}
pub trait GatewayDriverRequest: GatewayPath + Clone + Into<GatewayRequest> {
const OPERATION: GatewayOperation;
fn operation(&self) -> GatewayOperation {
Self::OPERATION
}
fn path() -> &'static str {
Gateway::path_for(Self::OPERATION)
}
}
impl GatewayPath for GatewayOperation {
fn gateway_path(&self) -> &'static str {
match self {
GatewayOperation::Fetch => Gateway::FETCH_PATH,
GatewayOperation::Insert => Gateway::INSERT_PATH,
GatewayOperation::Update => Gateway::UPDATE_PATH,
GatewayOperation::Delete => Gateway::DELETE_PATH,
GatewayOperation::Sql => Gateway::SQL_PATH,
GatewayOperation::Rpc => Gateway::RPC_PATH,
}
}
}
pub type GatewayQueryResult = QueryResult;
#[derive(Debug, Clone)]
pub enum GatewayRequestPayload {
Fetch(GatewayFetchRequest),
Insert(GatewayInsertRequest),
Update(GatewayUpdateRequest),
Delete(GatewayDeleteRequest),
Sql(GatewaySqlRequest),
Rpc(GatewayRpcRequest),
}
#[derive(Debug, Clone)]
pub struct GatewayRequest {
payload: GatewayRequestPayload,
}
impl GatewayRequest {
pub fn fetch(table: impl Into<String>) -> Self {
GatewayFetchRequest::new(table).into()
}
pub fn insert(table: impl Into<String>, payload: Value) -> Self {
GatewayInsertRequest::new(table, payload).into()
}
pub fn update(table: impl Into<String>, payload: Value) -> Self {
GatewayUpdateRequest::new(table, payload).into()
}
pub fn delete(table: impl Into<String>) -> Self {
GatewayDeleteRequest::new(table).into()
}
pub fn sql(query: impl Into<String>) -> Self {
GatewaySqlRequest::new(query).into()
}
pub fn rpc(function: impl Into<String>, args: Value) -> Self {
GatewayRpcRequest::new(function, args).into()
}
pub fn operation(&self) -> GatewayOperation {
match &self.payload {
GatewayRequestPayload::Fetch(_) => GatewayOperation::Fetch,
GatewayRequestPayload::Insert(_) => GatewayOperation::Insert,
GatewayRequestPayload::Update(_) => GatewayOperation::Update,
GatewayRequestPayload::Delete(_) => GatewayOperation::Delete,
GatewayRequestPayload::Sql(_) => GatewayOperation::Sql,
GatewayRequestPayload::Rpc(_) => GatewayOperation::Rpc,
}
}
pub fn path(&self) -> &'static str {
self.operation().gateway_path()
}
pub fn payload(&self) -> &GatewayRequestPayload {
&self.payload
}
pub fn into_payload(self) -> GatewayRequestPayload {
self.payload
}
}
pub mod request {
pub type Fetch = super::GatewayFetchRequest;
pub type Insert = super::GatewayInsertRequest;
pub type Update = super::GatewayUpdateRequest;
pub type Delete = super::GatewayDeleteRequest;
pub type Sql = super::GatewaySqlRequest;
pub type Rpc = super::GatewayRpcRequest;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GatewayRequestFactory;
impl GatewayRequestFactory {
pub fn fetch(&self, table: impl Into<String>) -> request::Fetch {
GatewayFetchRequest::new(table)
}
pub fn insert(&self, table: impl Into<String>, payload: Value) -> request::Insert {
GatewayInsertRequest::new(table, payload)
}
pub fn update(&self, table: impl Into<String>, payload: Value) -> request::Update {
GatewayUpdateRequest::new(table, payload)
}
pub fn delete(&self, table: impl Into<String>) -> request::Delete {
GatewayDeleteRequest::new(table)
}
pub fn sql(&self, query: impl Into<String>) -> request::Sql {
GatewaySqlRequest::new(query)
}
pub fn rpc(&self, function: impl Into<String>, args: Value) -> request::Rpc {
GatewayRpcRequest::new(function, args)
}
}
impl From<GatewayRequestPayload> for GatewayRequest {
fn from(payload: GatewayRequestPayload) -> Self {
Self { payload }
}
}
impl From<GatewayFetchRequest> for GatewayRequest {
fn from(request: GatewayFetchRequest) -> Self {
GatewayRequestPayload::Fetch(request).into()
}
}
impl From<GatewayInsertRequest> for GatewayRequest {
fn from(request: GatewayInsertRequest) -> Self {
GatewayRequestPayload::Insert(request).into()
}
}
impl From<GatewayUpdateRequest> for GatewayRequest {
fn from(request: GatewayUpdateRequest) -> Self {
GatewayRequestPayload::Update(request).into()
}
}
impl From<GatewayDeleteRequest> for GatewayRequest {
fn from(request: GatewayDeleteRequest) -> Self {
GatewayRequestPayload::Delete(request).into()
}
}
impl From<GatewaySqlRequest> for GatewayRequest {
fn from(request: GatewaySqlRequest) -> Self {
GatewayRequestPayload::Sql(request).into()
}
}
impl From<GatewayRpcRequest> for GatewayRequest {
fn from(request: GatewayRpcRequest) -> Self {
GatewayRequestPayload::Rpc(request).into()
}
}
#[derive(Debug, Clone)]
pub struct GatewayFetchRequest {
pub table: String,
pub columns: Vec<String>,
pub raw_select: Option<String>,
pub conditions: Vec<Condition>,
pub order_by: Vec<(String, OrderDirection)>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
impl GatewayFetchRequest {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
columns: Vec::new(),
raw_select: None,
conditions: Vec::new(),
order_by: Vec::new(),
limit: None,
offset: None,
}
}
pub fn fetch(table: impl Into<String>) -> Self {
Self::new(table)
}
pub fn columns(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.columns = columns.into_iter().map(Into::into).collect();
self
}
pub fn raw_select(mut self, select: impl Into<String>) -> Self {
self.raw_select = Some(select.into());
self
}
fn add_condition(
mut self,
column: impl Into<String>,
operator: ConditionOperator,
values: Vec<Value>,
) -> Self {
self.conditions
.push(Condition::new(column.into(), operator, values));
self
}
pub fn where_condition(mut self, condition: Condition) -> Self {
self.conditions.push(condition);
self
}
pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
self.conditions.extend(conditions);
self
}
pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
}
pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
}
pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
}
pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
}
pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
self.add_condition(column, ConditionOperator::In, values)
}
pub fn order_by(mut self, column: impl Into<String>, direction: OrderDirection) -> Self {
self.order_by.push((column.into(), direction));
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
}
impl GatewayPath for GatewayFetchRequest {
fn gateway_path(&self) -> &'static str {
Gateway::FETCH_PATH
}
}
impl GatewayDriverRequest for GatewayFetchRequest {
const OPERATION: GatewayOperation = GatewayOperation::Fetch;
}
#[derive(Debug, Clone, Default)]
pub struct GatewayInsertScope;
#[derive(Debug, Clone, Default)]
pub struct GatewayUpdateScope {
pub row_id: Option<String>,
pub conditions: Vec<Condition>,
pub allow_unfiltered: bool,
}
#[derive(Debug, Clone)]
pub struct GatewayMutationRequest<S> {
pub table: String,
pub payload: Value,
pub scope: S,
}
impl<S: Default> GatewayMutationRequest<S> {
pub fn new(table: impl Into<String>, payload: Value) -> Self {
Self {
table: table.into(),
payload,
scope: S::default(),
}
}
pub fn payload(mut self, payload: Value) -> Self {
self.payload = payload;
self
}
}
pub type GatewayInsertRequest = GatewayMutationRequest<GatewayInsertScope>;
impl GatewayInsertRequest {
pub fn insert(table: impl Into<String>, payload: Value) -> Self {
Self::new(table, payload)
}
}
impl GatewayPath for GatewayInsertRequest {
fn gateway_path(&self) -> &'static str {
Gateway::INSERT_PATH
}
}
impl GatewayDriverRequest for GatewayInsertRequest {
const OPERATION: GatewayOperation = GatewayOperation::Insert;
}
pub type GatewayUpdateRequest = GatewayMutationRequest<GatewayUpdateScope>;
impl GatewayUpdateRequest {
pub fn update(table: impl Into<String>, payload: Value) -> Self {
Self::new(table, payload)
}
pub fn row_id(mut self, row_id: impl Into<String>) -> Self {
self.scope.row_id = Some(row_id.into());
self
}
fn add_condition(
mut self,
column: impl Into<String>,
operator: ConditionOperator,
values: Vec<Value>,
) -> Self {
self.scope
.conditions
.push(Condition::new(column.into(), operator, values));
self
}
pub fn where_condition(mut self, condition: Condition) -> Self {
self.scope.conditions.push(condition);
self
}
pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
self.scope.conditions.extend(conditions);
self
}
pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
}
pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
}
pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
}
pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
}
pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
self.add_condition(column, ConditionOperator::In, values)
}
pub fn unsafe_unfiltered(mut self) -> Self {
self.scope.allow_unfiltered = true;
self
}
}
impl GatewayPath for GatewayUpdateRequest {
fn gateway_path(&self) -> &'static str {
Gateway::UPDATE_PATH
}
}
impl GatewayDriverRequest for GatewayUpdateRequest {
const OPERATION: GatewayOperation = GatewayOperation::Update;
}
#[derive(Debug, Clone)]
pub struct GatewayDeleteRequest {
pub table: String,
pub row_id: Option<String>,
pub conditions: Vec<Condition>,
pub allow_unfiltered: bool,
}
impl GatewayDeleteRequest {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
row_id: None,
conditions: Vec::new(),
allow_unfiltered: false,
}
}
pub fn delete(table: impl Into<String>) -> Self {
Self::new(table)
}
pub fn row_id(mut self, row_id: impl Into<String>) -> Self {
self.row_id = Some(row_id.into());
self
}
fn add_condition(
mut self,
column: impl Into<String>,
operator: ConditionOperator,
values: Vec<Value>,
) -> Self {
self.conditions
.push(Condition::new(column.into(), operator, values));
self
}
pub fn where_condition(mut self, condition: Condition) -> Self {
self.conditions.push(condition);
self
}
pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
self.conditions.extend(conditions);
self
}
pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
}
pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
}
pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
}
pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
}
pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
self.add_condition(column, ConditionOperator::In, values)
}
pub fn unsafe_unfiltered(mut self) -> Self {
self.allow_unfiltered = true;
self
}
}
impl GatewayPath for GatewayDeleteRequest {
fn gateway_path(&self) -> &'static str {
Gateway::DELETE_PATH
}
}
impl GatewayDriverRequest for GatewayDeleteRequest {
const OPERATION: GatewayOperation = GatewayOperation::Delete;
}
#[derive(Debug, Clone)]
pub struct GatewaySqlRequest {
pub query: String,
}
impl GatewaySqlRequest {
pub fn new(query: impl Into<String>) -> Self {
Self {
query: query.into(),
}
}
pub fn sql(query: impl Into<String>) -> Self {
Self::new(query)
}
}
impl GatewayPath for GatewaySqlRequest {
fn gateway_path(&self) -> &'static str {
Gateway::SQL_PATH
}
}
impl GatewayDriverRequest for GatewaySqlRequest {
const OPERATION: GatewayOperation = GatewayOperation::Sql;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayRpcFilterOperator {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
In,
Like,
ILike,
Is,
}
impl GatewayRpcFilterOperator {
pub fn as_str(self) -> &'static str {
match self {
Self::Eq => "eq",
Self::Neq => "neq",
Self::Gt => "gt",
Self::Gte => "gte",
Self::Lt => "lt",
Self::Lte => "lte",
Self::In => "in",
Self::Like => "like",
Self::ILike => "ilike",
Self::Is => "is",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GatewayRpcFilter {
pub column: String,
pub operator: GatewayRpcFilterOperator,
pub value: Value,
}
#[derive(Debug, Clone)]
pub struct GatewayRpcRequest {
pub function: String,
pub schema: String,
pub args: Value,
pub select: Option<String>,
pub filters: Vec<GatewayRpcFilter>,
pub count: Option<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub order: Option<(String, OrderDirection)>,
}
impl GatewayRpcRequest {
pub fn new(function: impl Into<String>, args: Value) -> Self {
Self {
function: function.into(),
schema: "public".to_string(),
args,
select: None,
filters: Vec::new(),
count: None,
limit: None,
offset: None,
order: None,
}
}
pub fn rpc(function: impl Into<String>, args: Value) -> Self {
Self::new(function, args)
}
pub fn schema(mut self, schema: impl Into<String>) -> Self {
self.schema = schema.into();
self
}
pub fn select(mut self, select: impl Into<String>) -> Self {
self.select = Some(select.into());
self
}
pub fn filter(
mut self,
column: impl Into<String>,
operator: GatewayRpcFilterOperator,
value: Value,
) -> Self {
self.filters.push(GatewayRpcFilter {
column: column.into(),
operator,
value,
});
self
}
pub fn count_exact(mut self) -> Self {
self.count = Some("exact".to_string());
self
}
pub fn order_by(mut self, column: impl Into<String>, direction: OrderDirection) -> Self {
self.order = Some((column.into(), direction));
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn to_body_json(&self) -> Value {
let mut object = Map::new();
object.insert("function".to_string(), Value::String(self.function.clone()));
object.insert("schema".to_string(), Value::String(self.schema.clone()));
object.insert("args".to_string(), self.args.clone());
if let Some(select) = self.select.clone() {
object.insert("select".to_string(), Value::String(select));
}
if !self.filters.is_empty() {
object.insert(
"filters".to_string(),
Value::Array(
self.filters
.iter()
.map(|filter| {
json!({
"column": filter.column,
"operator": filter.operator.as_str(),
"value": filter.value,
})
})
.collect(),
),
);
}
if let Some(count) = self.count.clone() {
object.insert("count".to_string(), Value::String(count));
}
if let Some(limit) = self.limit {
object.insert("limit".to_string(), json!(limit));
}
if let Some(offset) = self.offset {
object.insert("offset".to_string(), json!(offset));
}
if let Some((column, direction)) = &self.order {
object.insert(
"order".to_string(),
json!({
"column": column,
"ascending": matches!(direction, OrderDirection::Asc),
}),
);
}
Value::Object(object)
}
}
impl GatewayPath for GatewayRpcRequest {
fn gateway_path(&self) -> &'static str {
Gateway::RPC_PATH
}
}
impl GatewayDriverRequest for GatewayRpcRequest {
const OPERATION: GatewayOperation = GatewayOperation::Rpc;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn gateway_request_fetch_sets_fetch_shape() {
let request = GatewayRequest::fetch("users");
assert_eq!(request.operation(), GatewayOperation::Fetch);
let GatewayRequestPayload::Fetch(fetch) = request.payload() else {
panic!("expected fetch payload");
};
assert_eq!(fetch.table, "users");
assert!(fetch.columns.is_empty());
}
#[test]
fn gateway_request_insert_sets_insert_shape() {
let request = GatewayRequest::insert("users", json!({ "email": "a@example.com" }));
assert_eq!(request.operation(), GatewayOperation::Insert);
let GatewayRequestPayload::Insert(insert) = request.payload() else {
panic!("expected insert payload");
};
assert_eq!(insert.table, "users");
assert_eq!(insert.payload["email"], json!("a@example.com"));
}
#[test]
fn gateway_request_update_sets_update_shape() {
let request = GatewayRequest::update("users", json!({ "status": "active" }));
assert_eq!(request.operation(), GatewayOperation::Update);
let GatewayRequestPayload::Update(update) = request.payload() else {
panic!("expected update payload");
};
assert_eq!(update.table, "users");
assert_eq!(update.payload["status"], json!("active"));
}
#[test]
fn gateway_request_delete_sets_delete_shape() {
let request = GatewayRequest::delete("users");
assert_eq!(request.operation(), GatewayOperation::Delete);
let GatewayRequestPayload::Delete(delete) = request.payload() else {
panic!("expected delete payload");
};
assert_eq!(delete.table, "users");
assert!(delete.conditions.is_empty());
}
#[test]
fn gateway_request_sql_sets_sql_shape() {
let request = GatewayRequest::sql("select 1");
assert_eq!(request.operation(), GatewayOperation::Sql);
let GatewayRequestPayload::Sql(sql) = request.payload() else {
panic!("expected sql payload");
};
assert_eq!(sql.query, "select 1");
}
#[test]
fn gateway_request_rpc_sets_rpc_shape() {
let request = GatewayRequest::rpc("hello_world", json!({ "name": "Athena" }));
assert_eq!(request.operation(), GatewayOperation::Rpc);
let GatewayRequestPayload::Rpc(rpc) = request.payload() else {
panic!("expected rpc payload");
};
assert_eq!(rpc.function, "hello_world");
assert_eq!(rpc.args["name"], json!("Athena"));
}
#[test]
fn gateway_fetch_request_builder_sets_expected_shape() {
let request = GatewayFetchRequest::new("users")
.columns(["id", "email"])
.where_eq("status", json!("active"))
.order_by("id", OrderDirection::Desc)
.limit(10)
.offset(5);
assert_eq!(request.table, "users");
assert_eq!(request.columns, vec!["id".to_string(), "email".to_string()]);
assert_eq!(request.conditions.len(), 1);
assert_eq!(request.limit, Some(10));
assert_eq!(request.offset, Some(5));
}
#[test]
fn gateway_operation_resolves_canonical_paths() {
assert_eq!(GatewayOperation::Fetch.gateway_path(), Gateway::FETCH_PATH);
assert_eq!(
GatewayOperation::Insert.gateway_path(),
Gateway::INSERT_PATH
);
assert_eq!(
GatewayOperation::Update.gateway_path(),
Gateway::UPDATE_PATH
);
assert_eq!(
GatewayOperation::Delete.gateway_path(),
Gateway::DELETE_PATH
);
assert_eq!(GatewayOperation::Sql.gateway_path(), Gateway::SQL_PATH);
assert_eq!(GatewayOperation::Rpc.gateway_path(), Gateway::RPC_PATH);
}
#[test]
fn gateway_request_path_matches_operation_path() {
let request = GatewayRequest::delete("users");
assert_eq!(request.path(), Gateway::DELETE_PATH);
}
#[test]
fn gateway_request_factory_builds_typed_requests() {
let update = Gateway::request().update("users", json!({ "status": "active" }));
assert_eq!(update.table, "users");
assert_eq!(update.payload["status"], json!("active"));
let delete = Gateway::request().delete("users");
assert_eq!(delete.table, "users");
let delete_via_const = Gateway::REQUEST.delete("users");
assert_eq!(delete_via_const.table, "users");
let rpc = Gateway::request().rpc("hello_world", json!({}));
assert_eq!(rpc.function, "hello_world");
}
#[test]
fn rpc_request_serializes_filters_and_count() {
let request = GatewayRpcRequest::new("echo_all_cities", json!({}))
.schema("public")
.select("name,population")
.filter("name", GatewayRpcFilterOperator::ILike, json!("%shire%"))
.order_by("name", OrderDirection::Desc)
.limit(20)
.offset(5)
.count_exact();
let body = request.to_body_json();
assert_eq!(body["function"], json!("echo_all_cities"));
assert_eq!(body["schema"], json!("public"));
assert_eq!(body["select"], json!("name,population"));
assert_eq!(body["count"], json!("exact"));
assert_eq!(body["limit"], json!(20));
assert_eq!(body["offset"], json!(5));
assert_eq!(body["filters"][0]["column"], json!("name"));
assert_eq!(body["filters"][0]["operator"], json!("ilike"));
assert_eq!(body["order"]["column"], json!("name"));
assert_eq!(body["order"]["ascending"], json!(false));
}
#[test]
fn typed_request_trait_resolves_static_paths() {
assert_eq!(
<request::Fetch as GatewayDriverRequest>::path(),
Gateway::FETCH_PATH
);
assert_eq!(
<request::Insert as GatewayDriverRequest>::path(),
Gateway::INSERT_PATH
);
assert_eq!(
<request::Update as GatewayDriverRequest>::path(),
Gateway::UPDATE_PATH
);
assert_eq!(
<request::Delete as GatewayDriverRequest>::path(),
Gateway::DELETE_PATH
);
assert_eq!(
<request::Sql as GatewayDriverRequest>::path(),
Gateway::SQL_PATH
);
assert_eq!(
<request::Rpc as GatewayDriverRequest>::path(),
Gateway::RPC_PATH
);
}
}