1use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
16use reqwest::Client;
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use std::collections::HashMap;
20use std::fmt;
21use thiserror::Error;
22use url::Url;
23
24use serde_json::json;
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::sync::Arc;
27
28#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
30pub struct PostgrestApiErrorDetails {
31 pub code: Option<String>,
32 pub message: Option<String>,
33 pub details: Option<String>,
34 pub hint: Option<String>,
35}
36
37impl fmt::Display for PostgrestApiErrorDetails {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 let mut parts = Vec::new();
41 if let Some(code) = &self.code {
42 parts.push(format!("Code: {}", code));
43 }
44 if let Some(message) = &self.message {
45 parts.push(format!("Message: {}", message));
46 }
47 if let Some(details) = &self.details {
48 parts.push(format!("Details: {}", details));
49 }
50 if let Some(hint) = &self.hint {
51 parts.push(format!("Hint: {}", hint));
52 }
53 write!(f, "{}", parts.join(", "))
54 }
55}
56
57#[derive(Error, Debug)]
59pub enum PostgrestError {
60 #[error("API error: {details} (Status: {status})")]
61 ApiError {
62 details: PostgrestApiErrorDetails,
63 status: reqwest::StatusCode,
64 },
65
66 #[error("API error (unparsed): {message} (Status: {status})")]
67 UnparsedApiError {
68 message: String,
69 status: reqwest::StatusCode,
70 },
71
72 #[error("Network error: {0}")]
73 NetworkError(#[from] reqwest::Error),
74
75 #[error("URL parse error: {0}")]
76 UrlParseError(#[from] url::ParseError),
77
78 #[error("JSON serialization error: {0}")]
79 SerializationError(#[from] serde_json::Error),
80
81 #[error("Invalid parameters: {0}")]
82 InvalidParameters(String),
83
84 #[error("Transaction error: {0}")]
85 TransactionError(String),
86
87 #[error("Deserialization error: {0}")]
88 DeserializationError(String),
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum SortOrder {
94 Ascending,
95 Descending,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum IsolationLevel {
101 ReadCommitted,
102 RepeatableRead,
103 Serializable,
104}
105
106impl IsolationLevel {
107 fn display(&self) -> &'static str {
109 match self {
110 IsolationLevel::ReadCommitted => "read committed",
111 IsolationLevel::RepeatableRead => "repeatable read",
112 IsolationLevel::Serializable => "serializable",
113 }
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum TransactionMode {
120 ReadWrite,
121 ReadOnly,
122}
123
124impl TransactionMode {
125 fn display(&self) -> &'static str {
127 match self {
128 TransactionMode::ReadWrite => "read write",
129 TransactionMode::ReadOnly => "read only",
130 }
131 }
132}
133
134#[allow(dead_code)]
136enum TransactionState {
137 Inactive,
138 Active,
139 Committed,
140 RolledBack,
141}
142
143pub struct PostgrestClient {
145 base_url: String,
146 api_key: String,
147 table: String,
148 http_client: Client,
149 headers: HeaderMap,
150 query_params: HashMap<String, String>,
151 #[allow(dead_code)]
152 path: Option<String>,
153 #[allow(dead_code)]
154 is_rpc: bool,
155 #[allow(dead_code)]
156 rpc_params: Option<Value>,
157}
158
159impl PostgrestClient {
160 pub fn new(base_url: &str, api_key: &str, table: &str, http_client: Client) -> Self {
162 let mut headers = HeaderMap::new();
163 headers.insert("apikey", HeaderValue::from_str(api_key).unwrap());
164 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
165
166 Self {
167 base_url: base_url.to_string(),
168 api_key: api_key.to_string(),
169 table: table.to_string(),
170 http_client,
171 headers,
172 query_params: HashMap::new(),
173 path: None,
174 is_rpc: false,
175 rpc_params: None,
176 }
177 }
178
179 pub fn rpc(
181 base_url: &str,
182 api_key: &str,
183 function_name: &str,
184 params: Value,
185 http_client: Client,
186 ) -> Self {
187 let mut headers = HeaderMap::new();
188 headers.insert("apikey", HeaderValue::from_str(api_key).unwrap());
189 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
190
191 Self {
192 base_url: base_url.to_string(),
193 api_key: api_key.to_string(),
194 table: function_name.to_string(),
195 http_client,
196 headers,
197 query_params: HashMap::new(),
198 path: None,
199 is_rpc: true,
200 rpc_params: Some(params),
201 }
202 }
203
204 pub fn with_header(mut self, key: &str, value: &str) -> Result<Self, PostgrestError> {
206 let header_value = HeaderValue::from_str(value).map_err(|_| {
207 PostgrestError::InvalidParameters(format!("Invalid header value: {}", value))
208 })?;
209
210 let header_name = HeaderName::from_bytes(key.as_bytes()).map_err(|_| {
212 PostgrestError::InvalidParameters(format!("Invalid header name: {}", key))
213 })?;
214
215 self.headers.insert(header_name, header_value);
216 Ok(self)
217 }
218
219 pub fn with_auth(self, token: &str) -> Result<Self, PostgrestError> {
221 self.with_header("Authorization", &format!("Bearer {}", token))
222 }
223
224 pub fn select(mut self, columns: &str) -> Self {
226 self.query_params
227 .insert("select".to_string(), columns.to_string());
228 self
229 }
230
231 pub fn inner_join(mut self, foreign_table: &str, column: &str, foreign_column: &str) -> Self {
233 let current_select = self
235 .query_params
236 .get("select")
237 .cloned()
238 .unwrap_or_else(|| "*".to_string());
239 let new_select = if current_select == "*" {
240 format!("*,{}!inner({})", foreign_table, foreign_column)
241 } else {
242 format!(
243 "{},{},{}!inner({})",
244 current_select, column, foreign_table, foreign_column
245 )
246 };
247
248 self.query_params.insert("select".to_string(), new_select);
249 self
250 }
251
252 pub fn left_join(mut self, foreign_table: &str, column: &str, foreign_column: &str) -> Self {
254 let current_select = self
256 .query_params
257 .get("select")
258 .cloned()
259 .unwrap_or_else(|| "*".to_string());
260 let new_select = if current_select == "*" {
261 format!("*,{}!left({})", foreign_table, foreign_column)
262 } else {
263 format!(
264 "{},{},{}!left({})",
265 current_select, column, foreign_table, foreign_column
266 )
267 };
268
269 self.query_params.insert("select".to_string(), new_select);
270 self
271 }
272
273 pub fn include(
275 mut self,
276 foreign_table: &str,
277 _foreign_column: &str,
278 columns: Option<&str>,
279 ) -> Self {
280 let current_select = self
282 .query_params
283 .get("select")
284 .cloned()
285 .unwrap_or_else(|| "*".to_string());
286 let columns_str = columns.unwrap_or("*");
287 let new_select = if current_select == "*" {
288 format!("*,{}({})", foreign_table, columns_str)
289 } else {
290 format!("{},{}({})", current_select, foreign_table, columns_str)
291 };
292
293 self.query_params.insert("select".to_string(), new_select);
294 self
295 }
296
297 pub fn referenced_by(mut self, foreign_table: &str, foreign_column: &str) -> Self {
299 let current_select = self
301 .query_params
302 .get("select")
303 .cloned()
304 .unwrap_or_else(|| "*".to_string());
305 let new_select = if current_select == "*" {
306 format!("*,{}!fk({})", foreign_table, foreign_column)
307 } else {
308 format!(
309 "{},{}!fk({})",
310 current_select, foreign_table, foreign_column
311 )
312 };
313
314 self.query_params.insert("select".to_string(), new_select);
315 self
316 }
317
318 pub fn eq(mut self, column: &str, value: &str) -> Self {
320 self.query_params
321 .insert(column.to_string(), format!("eq.{}", value));
322 self
323 }
324
325 pub fn gt(mut self, column: &str, value: &str) -> Self {
327 self.query_params
328 .insert(column.to_string(), format!("gt.{}", value));
329 self
330 }
331
332 pub fn gte(mut self, column: &str, value: &str) -> Self {
334 self.query_params
335 .insert(column.to_string(), format!("gte.{}", value));
336 self
337 }
338
339 pub fn lt(mut self, column: &str, value: &str) -> Self {
341 self.query_params
342 .insert(column.to_string(), format!("lt.{}", value));
343 self
344 }
345
346 pub fn lte(mut self, column: &str, value: &str) -> Self {
348 self.query_params
349 .insert(column.to_string(), format!("lte.{}", value));
350 self
351 }
352
353 pub fn like(mut self, column: &str, pattern: &str) -> Self {
355 self.query_params
356 .insert(column.to_string(), format!("like.{}", pattern));
357 self
358 }
359
360 pub fn ilike(mut self, column: &str, pattern: &str) -> Self {
362 self.query_params
363 .insert(column.to_string(), format!("ilike.{}", pattern));
364 self
365 }
366
367 pub fn in_list(mut self, column: &str, values: &[&str]) -> Self {
369 let value_list = values.join(",");
370 self.query_params
371 .insert(column.to_string(), format!("in.({})", value_list));
372 self
373 }
374
375 pub fn not(mut self, column: &str, operator_with_value: &str) -> Self {
377 self.query_params
378 .insert(column.to_string(), format!("not.{}", operator_with_value));
379 self
380 }
381
382 pub fn contains(mut self, column: &str, value: &Value) -> Result<Self, PostgrestError> {
385 let value_str = serde_json::to_string(value)?;
386 self.query_params
387 .insert(column.to_string(), format!("cs.{}", value_str));
388 Ok(self)
389 }
390
391 pub fn contained_by(mut self, column: &str, value: &Value) -> Result<Self, PostgrestError> {
394 let value_str = serde_json::to_string(value)?;
395 self.query_params
396 .insert(column.to_string(), format!("cd.{}", value_str));
397 Ok(self)
398 }
399
400 pub fn order(mut self, column: &str, order: SortOrder) -> Self {
402 let order_str = match order {
403 SortOrder::Ascending => "asc",
404 SortOrder::Descending => "desc",
405 };
406 self.query_params
407 .insert("order".to_string(), format!("{}.{}", column, order_str));
408 self
409 }
410
411 pub fn limit(mut self, count: i32) -> Self {
413 self.query_params
414 .insert("limit".to_string(), count.to_string());
415 self
416 }
417
418 pub fn offset(mut self, count: i32) -> Self {
420 self.query_params
421 .insert("offset".to_string(), count.to_string());
422 self
423 }
424
425 pub fn text_search(mut self, column: &str, query: &str, config: Option<&str>) -> Self {
427 let search_param = match config {
428 Some(cfg) => format!("fts({}).{}", cfg, query),
429 None => format!("fts.{}", query),
430 };
431
432 self.query_params.insert(column.to_string(), search_param);
433 self
434 }
435
436 pub fn geo_distance(
438 mut self,
439 column: &str,
440 lat: f64,
441 lng: f64,
442 distance: f64,
443 unit: &str,
444 ) -> Self {
445 self.query_params.insert(
446 column.to_string(),
447 format!("st_dwithin.POINT({} {}).{}.{}", lng, lat, distance, unit),
448 );
449 self
450 }
451
452 pub fn group_by(mut self, columns: &str) -> Self {
454 self.query_params
455 .insert("group".to_string(), columns.to_string());
456 self
457 }
458
459 pub fn count(mut self, exact: bool) -> Self {
461 let count_method = if exact { "exact" } else { "planned" };
462 self.query_params
463 .insert("count".to_string(), count_method.to_string());
464 self
465 }
466
467 pub fn ignore_rls(mut self) -> Self {
469 self.headers.insert(
470 reqwest::header::HeaderName::from_static("x-supabase-admin-role"),
471 reqwest::header::HeaderValue::from_static("service_role"),
472 );
473 self
474 }
475
476 pub fn schema(mut self, schema_name: &str) -> Self {
478 self.query_params
479 .insert("schema".to_string(), schema_name.to_string());
480 self
481 }
482
483 pub async fn export_csv(&self) -> Result<String, PostgrestError> {
485 let mut url = self.build_url()?;
486
487 if url.contains('?') {
489 url.push('&');
490 } else {
491 url.push('?');
492 }
493 url.push_str("accept=text/csv");
494
495 let mut headers = self.headers.clone();
496 headers.insert(
497 reqwest::header::ACCEPT,
498 reqwest::header::HeaderValue::from_static("text/csv"),
499 );
500
501 let response = self.http_client.get(url).headers(headers).send().await?;
502
503 let status = response.status();
504 if !status.is_success() {
505 let error_text = response.text().await?;
506 let details = serde_json::from_str::<PostgrestApiErrorDetails>(&error_text)
507 .unwrap_or_else(|_| PostgrestApiErrorDetails {
508 code: None,
509 message: Some(error_text.clone()),
510 details: None,
511 hint: None,
512 });
513 return Err(PostgrestError::ApiError { details, status });
514 }
515
516 let csv_data = response.text().await?;
517
518 Ok(csv_data)
519 }
520
521 pub async fn execute<T: for<'de> Deserialize<'de>>(&self) -> Result<Vec<T>, PostgrestError> {
523 let url = self.build_url()?;
524
525 let response = self
526 .http_client
527 .get(&url)
528 .headers(self.headers.clone())
529 .send()
530 .await
531 .map_err(PostgrestError::NetworkError)?;
532
533 let status = response.status();
534 if !status.is_success() {
535 let error_text = response
536 .text()
537 .await
538 .unwrap_or_else(|_| "Failed to read error response".to_string());
539
540 if let Ok(details) = serde_json::from_str::<PostgrestApiErrorDetails>(&error_text) {
542 return Err(PostgrestError::ApiError { details, status });
543 } else {
544 return Err(PostgrestError::UnparsedApiError {
546 message: error_text,
547 status,
548 });
549 }
550 }
551
552 response
553 .json::<Vec<T>>()
554 .await
555 .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
556 }
557
558 pub async fn insert<T: Serialize>(&self, values: T) -> Result<Value, PostgrestError> {
560 let url = self.build_url()?;
561
562 let mut headers = self.headers.clone();
564 headers.insert(
565 HeaderName::from_static("prefer"),
566 HeaderValue::from_static("return=representation"),
567 );
568
569 let response = self
570 .http_client
571 .post(&url)
572 .headers(headers) .json(&values)
574 .send()
575 .await
576 .map_err(PostgrestError::NetworkError)?;
577
578 let status = response.status();
579
580 if status.is_success() {
582 let body_text = response.text().await.map_err(|e| {
584 PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
585 })?;
586
587 if body_text.trim().is_empty() {
590 Ok(Value::Null)
592 } else {
593 serde_json::from_str::<Value>(&body_text)
595 .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
596 }
597 } else {
598 let error_text = response
600 .text()
601 .await
602 .unwrap_or_else(|_| "Failed to read error response".to_string());
603
604 let details_result: Result<PostgrestApiErrorDetails, _> =
605 serde_json::from_str(&error_text);
606 match details_result {
607 Ok(details) => Err(PostgrestError::ApiError { details, status }),
608 Err(_) => Err(PostgrestError::UnparsedApiError {
609 message: error_text,
610 status,
611 }),
612 }
613 }
614 }
615
616 pub async fn update<T: Serialize>(&self, values: T) -> Result<Value, PostgrestError> {
618 let url = self.build_url()?;
619
620 let mut headers = self.headers.clone();
622 headers.insert(
623 HeaderName::from_static("prefer"),
624 HeaderValue::from_static("return=representation"),
625 );
626
627 let response = self
628 .http_client
629 .patch(&url)
630 .headers(headers) .json(&values)
632 .send()
633 .await
634 .map_err(PostgrestError::NetworkError)?;
635
636 let status = response.status();
637
638 if status.is_success() {
640 let body_text = response.text().await.map_err(|e| {
642 PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
643 })?;
644
645 if body_text.trim().is_empty() {
647 Ok(Value::Null)
648 } else {
649 serde_json::from_str::<Value>(&body_text)
651 .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
652 }
653 } else {
654 let error_text = response
656 .text()
657 .await
658 .unwrap_or_else(|_| "Failed to read error response".to_string());
659
660 let details_result: Result<PostgrestApiErrorDetails, _> =
661 serde_json::from_str(&error_text);
662 match details_result {
663 Ok(details) => Err(PostgrestError::ApiError { details, status }),
664 Err(_) => Err(PostgrestError::UnparsedApiError {
665 message: error_text,
666 status,
667 }),
668 }
669 }
670 }
671
672 pub async fn delete(&self) -> Result<Value, PostgrestError> {
674 let url = self.build_url()?;
675
676 let mut headers = self.headers.clone();
678 headers.insert(
679 HeaderName::from_static("prefer"),
680 HeaderValue::from_static("return=representation"),
681 );
682
683 let response = self
684 .http_client
685 .delete(&url)
686 .headers(headers) .send()
688 .await
689 .map_err(PostgrestError::NetworkError)?;
690
691 let status = response.status();
692
693 if status.is_success() {
695 let body_text = response.text().await.map_err(|e| {
697 PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
698 })?;
699
700 if body_text.trim().is_empty() {
702 Ok(Value::Null)
703 } else {
704 serde_json::from_str::<Value>(&body_text)
706 .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
707 }
708 } else {
709 let error_text = response
711 .text()
712 .await
713 .unwrap_or_else(|_| "Failed to read error response".to_string());
714
715 let details_result: Result<PostgrestApiErrorDetails, _> =
716 serde_json::from_str(&error_text);
717 match details_result {
718 Ok(details) => Err(PostgrestError::ApiError { details, status }),
719 Err(_) => Err(PostgrestError::UnparsedApiError {
720 message: error_text,
721 status,
722 }),
723 }
724 }
725 }
726
727 pub async fn call_rpc<T: for<'de> Deserialize<'de>>(&self) -> Result<T, PostgrestError> {
729 if !self.is_rpc {
730 return Err(PostgrestError::InvalidParameters(
731 "Client was not created for RPC. Use PostgrestClient::rpc().".to_string(),
732 ));
733 }
734 let url = format!("{}/rest/v1/rpc/{}", self.base_url, self.table);
736 let params = self.rpc_params.as_ref().ok_or_else(|| {
737 PostgrestError::InvalidParameters("RPC parameters are missing.".to_string())
738 })?;
739
740 let response = self
741 .http_client
742 .post(&url)
743 .headers(self.headers.clone())
744 .json(params)
745 .send()
746 .await
747 .map_err(PostgrestError::NetworkError)?;
748
749 let status = response.status();
750 if !status.is_success() {
751 let error_text = response
752 .text()
753 .await
754 .unwrap_or_else(|_| "Failed to read error response".to_string());
755
756 let details_result: Result<PostgrestApiErrorDetails, _> =
757 serde_json::from_str(&error_text);
758 return match details_result {
759 Ok(details) => Err(PostgrestError::ApiError { details, status }),
760 Err(_) => Err(PostgrestError::UnparsedApiError {
761 message: error_text,
762 status,
763 }),
764 };
765 }
766
767 response.json::<T>().await.map_err(|e| {
768 PostgrestError::DeserializationError(format!(
769 "Failed to deserialize RPC response: {}",
770 e
771 ))
772 })
773 }
774
775 fn build_url(&self) -> Result<String, PostgrestError> {
777 let mut url = Url::parse(&format!("{}/rest/v1/{}", self.base_url, self.table))?;
778
779 for (key, value) in &self.query_params {
780 url.query_pairs_mut().append_pair(key, value);
781 }
782
783 Ok(url.to_string())
784 }
785
786 pub async fn begin_transaction(
788 &self,
789 isolation_level: Option<IsolationLevel>,
790 transaction_mode: Option<TransactionMode>,
791 timeout_seconds: Option<u64>,
792 ) -> Result<PostgrestTransaction, PostgrestError> {
793 let isolation = isolation_level.unwrap_or(IsolationLevel::ReadCommitted);
795 let mode = transaction_mode.unwrap_or(TransactionMode::ReadWrite);
796
797 let mut request_body = json!({
799 "isolation_level": isolation.display(),
800 "mode": mode.display(),
801 });
802
803 if let Some(timeout) = timeout_seconds {
804 request_body["timeout_seconds"] = json!(timeout);
805 }
806
807 let transaction_url = format!("{}/rpc/begin_transaction", self.base_url);
809
810 let response = self
811 .http_client
812 .post(&transaction_url)
813 .headers(self.headers.clone())
814 .json(&request_body)
815 .send()
816 .await
817 .map_err(PostgrestError::NetworkError)?;
818
819 let status = response.status();
820 if !status.is_success() {
821 let error_text = response
822 .text()
823 .await
824 .unwrap_or_else(|_| "Failed to read error response".to_string());
825
826 return Err(PostgrestError::TransactionError(format!(
828 "Failed to begin transaction: {} (Status: {})",
829 error_text, status
830 )));
831 }
832
833 #[derive(Debug, Deserialize)]
834 struct TransactionResponse {
835 transaction_id: String,
836 }
837
838 let response_data = response
839 .json::<TransactionResponse>()
840 .await
841 .map_err(|e| PostgrestError::DeserializationError(e.to_string()))?;
842
843 Ok(PostgrestTransaction::new(
845 &self.base_url,
846 &self.api_key,
847 self.http_client.clone(),
848 self.headers.clone(),
849 response_data.transaction_id,
850 ))
851 }
852}
853
854pub struct PostgrestTransaction {
856 base_url: String,
857 api_key: String,
858 http_client: Client,
859 headers: HeaderMap,
860 transaction_id: String,
861 state: Arc<AtomicBool>, }
863
864impl PostgrestTransaction {
865 fn new(
867 base_url: &str,
868 api_key: &str,
869 http_client: Client,
870 headers: HeaderMap,
871 transaction_id: String,
872 ) -> Self {
873 Self {
874 base_url: base_url.to_string(),
875 api_key: api_key.to_string(),
876 http_client,
877 headers,
878 transaction_id,
879 state: Arc::new(AtomicBool::new(true)), }
881 }
882
883 pub fn from(&self, table: &str) -> PostgrestClient {
885 let mut client = PostgrestClient::new(
887 &self.base_url,
888 &self.api_key,
889 table,
890 self.http_client.clone(),
891 );
892
893 for (key, value) in self.headers.iter() {
895 if let Ok(value_str) = value.to_str() {
897 if let Ok(client_with_header) = PostgrestClient::new(
898 &self.base_url,
899 &self.api_key,
900 table,
901 self.http_client.clone(),
902 )
903 .with_header(key.as_str(), value_str)
904 {
905 client = client_with_header;
906 }
907 }
908 }
909
910 client
912 .query_params
913 .insert("transaction".to_string(), self.transaction_id.clone());
914
915 client
916 }
917
918 pub async fn commit(&self) -> Result<(), PostgrestError> {
920 if !self.state.load(Ordering::SeqCst) {
922 return Err(PostgrestError::TransactionError(
923 "Cannot commit: transaction is no longer active".to_string(),
924 ));
925 }
926
927 let commit_url = format!("{}/rpc/commit_transaction", self.base_url);
929
930 let commit_body = json!({
931 "transaction_id": self.transaction_id
932 });
933
934 let response = self
935 .http_client
936 .post(&commit_url)
937 .headers(self.headers.clone())
938 .json(&commit_body)
939 .send()
940 .await
941 .map_err(PostgrestError::NetworkError)?;
942
943 let status = response.status();
944 if !status.is_success() {
945 let error_text = response
946 .text()
947 .await
948 .unwrap_or_else(|_| "Failed to read error response".to_string());
949
950 return Err(PostgrestError::TransactionError(format!(
952 "Failed to commit transaction: {} (Status: {})",
953 error_text, status
954 )));
955 }
956
957 self.state.store(false, Ordering::SeqCst);
959
960 Ok(())
961 }
962
963 pub async fn rollback(&self) -> Result<(), PostgrestError> {
965 if !self.state.load(Ordering::SeqCst) {
967 return Err(PostgrestError::TransactionError(
968 "Cannot rollback: transaction is no longer active".to_string(),
969 ));
970 }
971
972 let rollback_url = format!("{}/rpc/rollback_transaction", self.base_url);
974
975 let rollback_body = json!({
976 "transaction_id": self.transaction_id
977 });
978
979 let response = self
980 .http_client
981 .post(&rollback_url)
982 .headers(self.headers.clone())
983 .json(&rollback_body)
984 .send()
985 .await
986 .map_err(PostgrestError::NetworkError)?;
987
988 let status = response.status();
989 if !status.is_success() {
990 let error_text = response
991 .text()
992 .await
993 .unwrap_or_else(|_| "Failed to read error response".to_string());
994 return Err(PostgrestError::TransactionError(format!(
995 "Failed to rollback transaction: {} (Status: {})",
996 error_text, status
997 )));
998 }
999
1000 self.state.store(false, Ordering::SeqCst);
1002
1003 Ok(())
1004 }
1005
1006 pub async fn savepoint(&self, name: &str) -> Result<(), PostgrestError> {
1008 if !self.state.load(Ordering::SeqCst) {
1010 return Err(PostgrestError::TransactionError(
1011 "Cannot create savepoint: transaction is no longer active".to_string(),
1012 ));
1013 }
1014
1015 let savepoint_url = format!("{}/rpc/create_savepoint", self.base_url);
1017
1018 let savepoint_body = json!({
1019 "transaction_id": self.transaction_id,
1020 "name": name
1021 });
1022
1023 let response = self
1024 .http_client
1025 .post(&savepoint_url)
1026 .headers(self.headers.clone())
1027 .json(&savepoint_body)
1028 .send()
1029 .await
1030 .map_err(PostgrestError::NetworkError)?;
1031
1032 let status = response.status();
1033 if !status.is_success() {
1034 let error_text = response
1035 .text()
1036 .await
1037 .unwrap_or_else(|_| "Failed to read error response".to_string());
1038 return Err(PostgrestError::TransactionError(format!(
1039 "Failed to create savepoint '{}': {} (Status: {})",
1040 name, error_text, status
1041 )));
1042 }
1043 Ok(())
1044 }
1045
1046 pub async fn rollback_to_savepoint(&self, name: &str) -> Result<(), PostgrestError> {
1048 if !self.state.load(Ordering::SeqCst) {
1050 return Err(PostgrestError::TransactionError(
1051 "Cannot rollback to savepoint: transaction is no longer active".to_string(),
1052 ));
1053 }
1054
1055 let rollback_url = format!("{}/rpc/rollback_to_savepoint", self.base_url);
1057
1058 let rollback_body = json!({
1059 "transaction_id": self.transaction_id,
1060 "name": name
1061 });
1062
1063 let response = self
1064 .http_client
1065 .post(&rollback_url)
1066 .headers(self.headers.clone())
1067 .json(&rollback_body)
1068 .send()
1069 .await
1070 .map_err(PostgrestError::NetworkError)?;
1071
1072 let status = response.status();
1073 if !status.is_success() {
1074 let error_text = response
1075 .text()
1076 .await
1077 .unwrap_or_else(|_| "Failed to read error response".to_string());
1078 return Err(PostgrestError::TransactionError(format!(
1079 "Failed to rollback to savepoint '{}': {} (Status: {})",
1080 name, error_text, status
1081 )));
1082 }
1083 Ok(())
1084 }
1085}
1086
1087impl Drop for PostgrestTransaction {
1089 fn drop(&mut self) {
1090 if self.state.load(Ordering::SeqCst) {
1092 eprintln!("Warning: Active transaction is being dropped without commit or rollback. Performing automatic rollback.");
1093
1094 let url = format!("{}/rest/v1/rpc/rollback_transaction", self.base_url);
1096
1097 let client = Client::new();
1098 let future = client
1100 .post(url)
1101 .headers(self.headers.clone())
1102 .json(&json!({ "transaction_id": self.transaction_id }))
1103 .send();
1104 std::mem::drop(future);
1105 }
1106 }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use super::*;
1112 use serde_json::json;
1113 use wiremock::matchers::{body_json, header, method, path, query_param};
1114 use wiremock::{Mock, MockServer, ResponseTemplate};
1115
1116 #[tokio::test]
1117 async fn test_select() {
1118 let mock_server = MockServer::start().await;
1119 println!("Mock server started at: {}", mock_server.uri());
1120
1121 Mock::given(method("GET"))
1123 .and(path("/rest/v1/items"))
1124 .and(query_param("select", "*")) .respond_with(ResponseTemplate::new(200).set_body_json(json!([
1126 { "id": 1, "name": "Test Item 1" },
1127 { "id": 2, "name": "Test Item 2" }
1128 ])))
1129 .mount(&mock_server)
1130 .await;
1131 println!("Select mock set up");
1132
1133 let client = PostgrestClient::new(
1134 &mock_server.uri(),
1135 "fake-key",
1136 "items", reqwest::Client::new(),
1138 );
1139 println!("Client created for select test");
1140
1141 let result = client.select("*").execute::<serde_json::Value>().await;
1142
1143 if let Err(e) = &result {
1144 println!("Select query failed: {:?}", e);
1145 }
1146
1147 assert!(result.is_ok());
1148 let data = result.unwrap();
1149 assert_eq!(data.len(), 2);
1150 assert_eq!(
1151 data.first()
1152 .and_then(|v: &Value| v.get("name"))
1153 .and_then(Value::as_str),
1154 Some("Test Item 1")
1155 );
1156 assert_eq!(
1157 data.first()
1158 .and_then(|v: &Value| v.get("id"))
1159 .and_then(Value::as_i64),
1160 Some(1)
1161 );
1162 }
1163
1164 #[tokio::test]
1165 async fn test_rpc() {
1166 let mock_server = MockServer::start().await;
1167 println!("Mock server started at: {}", mock_server.uri());
1168
1169 let rpc_params = json!({ "arg1": "value1", "arg2": 123 });
1171 Mock::given(method("POST"))
1172 .and(path("/rest/v1/rpc/my_rpc_function"))
1173 .and(body_json(&rpc_params)) .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1175 "result": "success",
1176 "data": 456
1177 })))
1178 .mount(&mock_server)
1179 .await;
1180 println!("RPC mock set up");
1181
1182 let client = PostgrestClient::rpc(
1184 &mock_server.uri(),
1185 "fake-key",
1186 "my_rpc_function", rpc_params.clone(),
1188 reqwest::Client::new(),
1189 );
1190 println!("Client created for RPC test");
1191
1192 #[derive(Deserialize, Debug, PartialEq)]
1194 struct RpcResponse {
1195 result: String,
1196 data: i32,
1197 }
1198
1199 let result = client.call_rpc::<RpcResponse>().await; if let Err(e) = &result {
1202 println!("RPC call failed: {:?}", e);
1203 }
1204
1205 assert!(result.is_ok());
1206 let response_data = result.unwrap();
1207 assert_eq!(
1208 response_data,
1209 RpcResponse {
1210 result: "success".to_string(),
1211 data: 456
1212 }
1213 );
1214 }
1215
1216 #[tokio::test]
1217 async fn test_join_queries() {
1218 let mock_server = MockServer::start().await;
1219 println!("Mock server started at: {}", mock_server.uri());
1220
1221 Mock::given(method("GET"))
1223 .and(path("/rest/v1/posts"))
1224 .respond_with(ResponseTemplate::new(200).set_body_json(json!([
1225 {
1226 "id": 1,
1227 "title": "First Post",
1228 "content": "Content",
1229 "comments": [
1230 { "id": 1, "text": "Comment 1", "user_id": 2 },
1231 { "id": 2, "text": "Comment 2", "user_id": 3 }
1232 ],
1233 "users": { "id": 1 }
1234 }
1235 ])))
1236 .mount(&mock_server)
1237 .await;
1238 println!("Join query mock set up");
1239
1240 let client = PostgrestClient::new(
1241 &mock_server.uri(),
1242 "fake-key",
1243 "posts",
1244 reqwest::Client::new(),
1245 );
1246 println!("Client created");
1247
1248 let result = client
1249 .select("id,title,content")
1250 .include("comments", "post_id", Some("id,text,user_id"))
1251 .inner_join("users", "user_id", "id")
1252 .execute::<serde_json::Value>()
1253 .await;
1254
1255 if let Err(e) = &result {
1256 println!("Join query failed: {:?}", e);
1257 }
1258
1259 assert!(result.is_ok());
1260 let data = result.unwrap();
1261 assert_eq!(data.len(), 1);
1262 assert_eq!(
1263 data.first()
1264 .and_then(|v: &Value| v.get("title"))
1265 .and_then(Value::as_str),
1266 Some("First Post")
1267 );
1268 assert_eq!(
1269 data.first()
1270 .and_then(|v: &Value| v.get("comments"))
1271 .and_then(Value::as_array)
1272 .map(|a| a.len()),
1273 Some(2)
1274 );
1275 }
1276
1277 #[tokio::test]
1278 async fn test_text_search() {
1279 let mock_server = MockServer::start().await;
1280
1281 Mock::given(method("GET"))
1283 .and(path("/rest/v1/articles"))
1284 .and(query_param("content", "fts(english).search terms"))
1285 .respond_with(ResponseTemplate::new(200).set_body_json(json!([
1286 { "id": 1, "title": "Search Result", "content": "This is a search result" }
1287 ])))
1288 .mount(&mock_server)
1289 .await;
1290
1291 let client = PostgrestClient::new(
1292 &mock_server.uri(),
1293 "fake-key",
1294 "articles",
1295 reqwest::Client::new(),
1296 );
1297
1298 let result = client
1299 .text_search("content", "search terms", Some("english"))
1300 .execute::<serde_json::Value>()
1301 .await;
1302
1303 assert!(result.is_ok());
1304 let data = result.unwrap();
1305 assert_eq!(data.len(), 1);
1306 assert_eq!(
1307 data.first()
1308 .and_then(|v: &Value| v.get("title"))
1309 .and_then(Value::as_str),
1310 Some("Search Result")
1311 );
1312 }
1313
1314 #[tokio::test]
1315 async fn test_csv_export() {
1316 let mock_server = MockServer::start().await;
1317
1318 Mock::given(method("GET"))
1320 .and(path("/rest/v1/users"))
1321 .respond_with(
1322 ResponseTemplate::new(200)
1323 .set_body_string(
1324 "id,name,email\n1,User 1,user1@example.com\n2,User 2,user2@example.com",
1325 )
1326 .append_header("Content-Type", "text/csv"),
1327 )
1328 .mount(&mock_server)
1329 .await;
1330
1331 let client = PostgrestClient::new(
1332 &mock_server.uri(),
1333 "fake-key",
1334 "users",
1335 reqwest::Client::new(),
1336 );
1337
1338 let result = client.export_csv().await;
1339
1340 assert!(result.is_ok());
1341 let csv_data = result.unwrap();
1342 assert!(csv_data.contains("id,name,email"));
1343 assert!(csv_data.contains("User 1"));
1344 assert!(csv_data.contains("User 2"));
1345 }
1346
1347 #[tokio::test]
1348 async fn test_transaction() {
1349 let mock_server = MockServer::start().await;
1350 println!("Mock server started at: {}", mock_server.uri());
1351
1352 Mock::given(method("POST"))
1354 .and(path("/rpc/begin_transaction"))
1355 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1356 "transaction_id": "tx-12345"
1357 })))
1358 .mount(&mock_server)
1359 .await;
1360 println!("Begin transaction mock set up");
1361
1362 Mock::given(method("POST"))
1364 .and(path("/rest/v1/users"))
1365 .and(query_param("transaction", "tx-12345"))
1366 .respond_with(ResponseTemplate::new(201).set_body_json(json!([{
1367 "id": 1,
1368 "name": "テストユーザー"
1369 }])))
1370 .mount(&mock_server)
1371 .await;
1372 println!("Insert mock set up");
1373
1374 Mock::given(method("GET"))
1376 .and(path("/rest/v1/users"))
1377 .and(query_param("transaction", "tx-12345"))
1378 .respond_with(ResponseTemplate::new(200).set_body_json(json!([{
1379 "id": 1,
1380 "name": "テストユーザー"
1381 }])))
1382 .mount(&mock_server)
1383 .await;
1384 println!("Select mock set up");
1385
1386 Mock::given(method("POST"))
1388 .and(path("/rpc/commit_transaction"))
1389 .and(body_json(json!({
1390 "transaction_id": "tx-12345"
1391 })))
1392 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1393 "success": true
1394 })))
1395 .mount(&mock_server)
1396 .await;
1397 println!("Commit mock set up");
1398
1399 let client = PostgrestClient::new(
1401 &mock_server.uri(),
1402 "fake-key",
1403 "users",
1404 reqwest::Client::new(),
1405 );
1406 println!("Client created");
1407
1408 let transaction = client
1410 .begin_transaction(
1411 Some(IsolationLevel::ReadCommitted),
1412 Some(TransactionMode::ReadWrite),
1413 Some(30),
1414 )
1415 .await;
1416
1417 if let Err(e) = &transaction {
1418 println!("Transaction failed: {:?}", e);
1419 }
1420
1421 assert!(transaction.is_ok());
1422 let transaction = transaction.unwrap();
1423
1424 let insert_result = transaction
1426 .from("users")
1427 .insert(json!({
1428 "name": "テストユーザー"
1429 }))
1430 .await;
1431
1432 assert!(insert_result.is_ok());
1433
1434 let query_result = transaction
1436 .from("users")
1437 .select("id, name")
1438 .execute::<serde_json::Value>()
1439 .await;
1440
1441 assert!(query_result.is_ok());
1442 let users = query_result.unwrap();
1443 assert_eq!(
1444 users
1445 .first()
1446 .and_then(|v: &Value| v.get("name"))
1447 .and_then(Value::as_str),
1448 Some("テストユーザー")
1449 );
1450
1451 let commit_result = transaction.commit().await;
1453 assert!(commit_result.is_ok());
1454 }
1455
1456 #[tokio::test]
1457 async fn test_transaction_rollback() {
1458 let mock_server = MockServer::start().await;
1459
1460 Mock::given(method("POST"))
1462 .and(path("/rpc/begin_transaction"))
1463 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1464 "transaction_id": "tx-67890"
1465 })))
1466 .mount(&mock_server)
1467 .await;
1468
1469 Mock::given(method("POST"))
1471 .and(path("/rpc/rollback_transaction"))
1472 .and(body_json(json!({
1473 "transaction_id": "tx-67890"
1474 })))
1475 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1476 "success": true
1477 })))
1478 .mount(&mock_server)
1479 .await;
1480
1481 let client = PostgrestClient::new(
1483 &mock_server.uri(),
1484 "fake-key",
1485 "users",
1486 reqwest::Client::new(),
1487 );
1488
1489 let transaction = client.begin_transaction(None, None, None).await;
1491
1492 assert!(transaction.is_ok());
1493 let transaction = transaction.unwrap();
1494
1495 let rollback_result = transaction.rollback().await;
1497 assert!(rollback_result.is_ok());
1498 }
1499
1500 #[tokio::test]
1501 async fn test_transaction_savepoint() {
1502 let mock_server = MockServer::start().await;
1503
1504 Mock::given(method("POST"))
1506 .and(path("/rpc/begin_transaction"))
1507 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1508 "transaction_id": "tx-savepoint"
1509 })))
1510 .mount(&mock_server)
1511 .await;
1512
1513 Mock::given(method("POST"))
1515 .and(path("/rpc/create_savepoint"))
1516 .and(body_json(json!({
1517 "transaction_id": "tx-savepoint",
1518 "name": "sp1"
1519 })))
1520 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1521 "success": true
1522 })))
1523 .mount(&mock_server)
1524 .await;
1525
1526 Mock::given(method("POST"))
1528 .and(path("/rpc/rollback_to_savepoint"))
1529 .and(body_json(json!({
1530 "transaction_id": "tx-savepoint",
1531 "name": "sp1"
1532 })))
1533 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1534 "success": true
1535 })))
1536 .mount(&mock_server)
1537 .await;
1538
1539 Mock::given(method("POST"))
1541 .and(path("/rpc/commit_transaction"))
1542 .and(body_json(json!({
1543 "transaction_id": "tx-savepoint"
1544 })))
1545 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1546 "success": true
1547 })))
1548 .mount(&mock_server)
1549 .await;
1550
1551 let client = PostgrestClient::new(
1553 &mock_server.uri(),
1554 "fake-key",
1555 "users",
1556 reqwest::Client::new(),
1557 );
1558
1559 let transaction = client.begin_transaction(None, None, None).await;
1561
1562 assert!(transaction.is_ok());
1563 let transaction = transaction.unwrap();
1564
1565 let savepoint_result = transaction.savepoint("sp1").await;
1567 assert!(savepoint_result.is_ok());
1568
1569 let rollback_to_savepoint_result = transaction.rollback_to_savepoint("sp1").await;
1571 assert!(rollback_to_savepoint_result.is_ok());
1572
1573 let commit_result = transaction.commit().await;
1575 assert!(commit_result.is_ok());
1576 }
1577
1578 #[tokio::test]
1579 async fn test_jsonb_filters() {
1580 let mock_server = MockServer::start().await;
1581
1582 let contains_value = json!({ "key": "value" });
1583 let contained_by_value = json!(["a", "b"]);
1584
1585 Mock::given(method("GET"))
1587 .and(path("/rest/v1/data"))
1588 .and(query_param("metadata", format!("cs.{}", contains_value)))
1589 .respond_with(ResponseTemplate::new(200).set_body_json(json!([{"id": 1}])))
1590 .mount(&mock_server)
1591 .await;
1592
1593 Mock::given(method("GET"))
1595 .and(path("/rest/v1/data"))
1596 .and(query_param("tags", format!("cd.{}", contained_by_value)))
1597 .respond_with(ResponseTemplate::new(200).set_body_json(json!([{"id": 2}])))
1598 .mount(&mock_server)
1599 .await;
1600
1601 let _base_client = PostgrestClient::new(
1602 &mock_server.uri(),
1603 "fake-key",
1604 "data",
1605 reqwest::Client::new(),
1606 );
1607
1608 let result_contains = PostgrestClient::new(
1610 &mock_server.uri(),
1612 "fake-key",
1613 "data",
1614 reqwest::Client::new(), )
1616 .contains("metadata", &contains_value)
1617 .unwrap() .execute::<serde_json::Value>()
1619 .await;
1620 assert!(result_contains.is_ok());
1621 assert_eq!(result_contains.unwrap().len(), 1);
1622
1623 let result_contained_by = PostgrestClient::new(
1625 &mock_server.uri(),
1627 "fake-key",
1628 "data",
1629 reqwest::Client::new(), )
1631 .contained_by("tags", &contained_by_value)
1632 .unwrap()
1633 .execute::<serde_json::Value>()
1634 .await;
1635 assert!(result_contained_by.is_ok());
1636 assert_eq!(result_contained_by.unwrap().len(), 1);
1637 }
1638
1639 #[tokio::test]
1640 async fn test_filter_on_related_table() {
1641 let mock_server = MockServer::start().await;
1642
1643 Mock::given(method("GET"))
1645 .and(path("/rest/v1/posts"))
1646 .and(query_param("author.name", "eq.Specific Author")) .and(query_param("select", "title,author!inner(name)")) .respond_with(ResponseTemplate::new(200).set_body_json(json!([
1649 { "title": "Post by Specific Author", "author": { "name": "Specific Author" } }
1650 ])))
1651 .mount(&mock_server)
1652 .await;
1653
1654 let client = PostgrestClient::new(
1655 &mock_server.uri(),
1656 "fake-key",
1657 "posts",
1658 reqwest::Client::new(),
1659 );
1660
1661 let result = client
1662 .select("title,author!inner(name)") .eq("author.name", "Specific Author") .execute::<serde_json::Value>()
1665 .await;
1666
1667 if let Err(e) = &result {
1668 println!("Join query failed: {:?}", e);
1669 }
1670
1671 assert!(result.is_ok(), "Request failed: {:?}", result.err());
1672 let data = result.unwrap();
1673 assert_eq!(data.len(), 1);
1674 let post = data
1675 .first()
1676 .expect("Post should exist in related table test");
1677 assert_eq!(
1678 post.get("title").and_then(Value::as_str),
1679 Some("Post by Specific Author")
1680 );
1681 let author_obj: Option<&Value> = post.get("author");
1682 let name_val = author_obj
1683 .and_then(|a: &Value| a.get("name"))
1684 .and_then(Value::as_str);
1685 assert_eq!(name_val, Some("Specific Author"));
1686 }
1687
1688 #[tokio::test]
1689 async fn test_insert() {
1690 let mock_server = MockServer::start().await;
1691 println!(
1692 "Mock server started for insert test at: {}",
1693 mock_server.uri()
1694 );
1695
1696 let insert_data = json!({ "name": "New Item", "value": 10 });
1697 let expected_response = json!([{ "id": 3, "name": "New Item", "value": 10 }]);
1698
1699 Mock::given(method("POST"))
1700 .and(path("/rest/v1/items"))
1701 .and(header("apikey", "fake-key"))
1702 .and(header("content-type", "application/json"))
1703 .and(header("Prefer", "return=representation"))
1704 .and(body_json(&insert_data))
1705 .respond_with(ResponseTemplate::new(201).set_body_json(&expected_response))
1706 .mount(&mock_server)
1707 .await;
1708 println!("Insert mock set up");
1709
1710 let client = PostgrestClient::new(
1711 &mock_server.uri(),
1712 "fake-key",
1713 "items",
1714 reqwest::Client::new(),
1715 );
1716 println!("Client created for insert test");
1717
1718 let result = client.insert(&insert_data).await;
1719
1720 if let Err(e) = &result {
1721 println!("Insert query failed: {:?}", e);
1722 }
1723
1724 assert!(result.is_ok());
1725 let data = result.unwrap();
1726 assert_eq!(data, expected_response);
1727 }
1728
1729 #[tokio::test]
1730 async fn test_update() {
1731 let mock_server = MockServer::start().await;
1732 println!(
1733 "Mock server started for update test at: {}",
1734 mock_server.uri()
1735 );
1736
1737 let update_data = json!({ "value": 20 });
1738 let expected_response = json!([{ "id": 1, "name": "Updated Item", "value": 20 }]);
1739
1740 Mock::given(method("PATCH"))
1741 .and(path("/rest/v1/items"))
1742 .and(query_param("id", "eq.1"))
1743 .and(header("apikey", "fake-key"))
1744 .and(header("content-type", "application/json"))
1745 .and(header("Prefer", "return=representation"))
1746 .and(body_json(&update_data))
1747 .respond_with(ResponseTemplate::new(200).set_body_json(&expected_response))
1748 .mount(&mock_server)
1749 .await;
1750 println!("Update mock set up");
1751
1752 let client = PostgrestClient::new(
1753 &mock_server.uri(),
1754 "fake-key",
1755 "items",
1756 reqwest::Client::new(),
1757 );
1758 println!("Client created for update test");
1759
1760 let result = client.eq("id", "1").update(&update_data).await;
1761
1762 if let Err(e) = &result {
1763 println!("Update query failed: {:?}", e);
1764 }
1765
1766 assert!(result.is_ok());
1767 let data = result.unwrap();
1768 assert_eq!(data, expected_response);
1769 }
1770
1771 #[tokio::test]
1772 async fn test_delete() {
1773 let mock_server = MockServer::start().await;
1774 println!(
1775 "Mock server started for delete test at: {}",
1776 mock_server.uri()
1777 );
1778
1779 let expected_response = json!([{ "id": 1, "name": "Deleted Item", "value": 10 }]);
1780
1781 Mock::given(method("DELETE"))
1782 .and(path("/rest/v1/items"))
1783 .and(query_param("id", "eq.1"))
1784 .and(header("apikey", "fake-key"))
1785 .and(header("content-type", "application/json"))
1786 .and(header("Prefer", "return=representation"))
1787 .respond_with(ResponseTemplate::new(200).set_body_json(&expected_response))
1788 .mount(&mock_server)
1789 .await;
1790 println!("Delete mock set up");
1791
1792 let client = PostgrestClient::new(
1793 &mock_server.uri(),
1794 "fake-key",
1795 "items",
1796 reqwest::Client::new(),
1797 );
1798 println!("Client created for delete test");
1799
1800 let result = client.eq("id", "1").delete().await;
1801
1802 if let Err(e) = &result {
1803 println!("Delete query failed: {:?}", e);
1804 }
1805
1806 assert!(result.is_ok());
1807 let data = result.unwrap();
1808 assert_eq!(data, expected_response);
1809 }
1810
1811 #[tokio::test]
1812 async fn test_filters() {
1813 let mock_server = MockServer::start().await;
1814
1815 Mock::given(method("GET"))
1817 .and(path("/rest/v1/items"))
1818 .and(query_param("id", "gt.10"))
1819 .and(header("apikey", "fake-key"))
1820 .respond_with(
1821 ResponseTemplate::new(200).set_body_json(json!([{ "id": 11, "name": "Item 11" }])),
1822 )
1823 .mount(&mock_server)
1824 .await;
1825
1826 Mock::given(method("GET"))
1828 .and(path("/rest/v1/items"))
1829 .and(query_param("name", "like.*test*"))
1830 .and(header("apikey", "fake-key"))
1831 .respond_with(
1832 ResponseTemplate::new(200).set_body_json(json!([{ "id": 1, "name": "test item" }])),
1833 )
1834 .mount(&mock_server)
1835 .await;
1836
1837 Mock::given(method("GET"))
1839 .and(path("/rest/v1/items"))
1840 .and(query_param("status", "in.(active,pending)"))
1841 .and(header("apikey", "fake-key"))
1842 .respond_with(
1843 ResponseTemplate::new(200).set_body_json(json!([{ "id": 5, "status": "active" }])),
1844 )
1845 .mount(&mock_server)
1846 .await;
1847
1848 Mock::given(method("GET"))
1850 .and(path("/rest/v1/items"))
1851 .and(query_param("value", "gte.50"))
1852 .and(header("apikey", "fake-key"))
1853 .respond_with(
1854 ResponseTemplate::new(200).set_body_json(json!([{ "id": 3, "value": 50 }])),
1855 )
1856 .mount(&mock_server)
1857 .await;
1858
1859 Mock::given(method("GET"))
1861 .and(path("/rest/v1/items"))
1862 .and(query_param("id", "lt.5"))
1863 .and(header("apikey", "fake-key"))
1864 .respond_with(
1865 ResponseTemplate::new(200).set_body_json(json!([{ "id": 4, "name": "Item 4" }])),
1866 )
1867 .mount(&mock_server)
1868 .await;
1869
1870 Mock::given(method("GET"))
1872 .and(path("/rest/v1/items"))
1873 .and(query_param("value", "lte.100"))
1874 .and(header("apikey", "fake-key"))
1875 .respond_with(
1876 ResponseTemplate::new(200).set_body_json(json!([{ "id": 7, "value": 100 }])),
1877 )
1878 .mount(&mock_server)
1879 .await;
1880
1881 Mock::given(method("GET"))
1883 .and(path("/rest/v1/items"))
1884 .and(query_param("name", "ilike.*CASE*"))
1885 .and(header("apikey", "fake-key"))
1886 .respond_with(
1887 ResponseTemplate::new(200).set_body_json(json!([{ "id": 8, "name": "Case Test" }])),
1888 )
1889 .mount(&mock_server)
1890 .await;
1891
1892 Mock::given(method("GET"))
1894 .and(path("/rest/v1/items"))
1895 .and(query_param("status", "not.eq.archived"))
1896 .and(header("apikey", "fake-key"))
1897 .respond_with(
1898 ResponseTemplate::new(200).set_body_json(json!([{ "id": 9, "status": "active" }])),
1899 )
1900 .mount(&mock_server)
1901 .await;
1902
1903 let base_uri = mock_server.uri();
1904 let api_key = "fake-key";
1905 let table_name = "items";
1906
1907 let client_gt =
1909 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1910 let result_gt = client_gt.gt("id", "10").execute::<Value>().await;
1911 assert!(result_gt.is_ok(), "GT filter failed: {:?}", result_gt.err());
1912 assert_eq!(result_gt.unwrap().len(), 1);
1913
1914 let client_like =
1916 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1917 let result_like = client_like.like("name", "*test*").execute::<Value>().await;
1918 assert!(
1919 result_like.is_ok(),
1920 "LIKE filter failed: {:?}",
1921 result_like.err()
1922 );
1923 assert_eq!(result_like.unwrap().len(), 1);
1924
1925 let client_in =
1927 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1928 let result_in = client_in
1929 .in_list("status", &["active", "pending"])
1930 .execute::<Value>()
1931 .await;
1932 assert!(result_in.is_ok(), "IN filter failed: {:?}", result_in.err());
1933 assert_eq!(result_in.unwrap().len(), 1);
1934
1935 let client_gte =
1937 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1938 let result_gte = client_gte.gte("value", "50").execute::<Value>().await;
1939 assert!(
1940 result_gte.is_ok(),
1941 "GTE filter failed: {:?}",
1942 result_gte.err()
1943 );
1944 assert_eq!(result_gte.unwrap().len(), 1);
1945
1946 let client_lt =
1948 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1949 let result_lt = client_lt.lt("id", "5").execute::<Value>().await;
1950 assert!(result_lt.is_ok(), "LT filter failed: {:?}", result_lt.err());
1951 assert_eq!(result_lt.unwrap().len(), 1);
1952
1953 let client_lte =
1955 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1956 let result_lte = client_lte.lte("value", "100").execute::<Value>().await;
1957 assert!(
1958 result_lte.is_ok(),
1959 "LTE filter failed: {:?}",
1960 result_lte.err()
1961 );
1962 assert_eq!(result_lte.unwrap().len(), 1);
1963
1964 let client_ilike =
1966 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1967 let result_ilike = client_ilike
1968 .ilike("name", "*CASE*")
1969 .execute::<Value>()
1970 .await;
1971 assert!(
1972 result_ilike.is_ok(),
1973 "ILIKE filter failed: {:?}",
1974 result_ilike.err()
1975 );
1976 assert_eq!(result_ilike.unwrap().len(), 1);
1977
1978 let client_not =
1980 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
1981 let result_not = client_not
1982 .not("status", "eq.archived")
1983 .execute::<Value>()
1984 .await;
1985 assert!(
1986 result_not.is_ok(),
1987 "NOT filter failed: {:?}",
1988 result_not.err()
1989 );
1990 assert_eq!(result_not.unwrap().len(), 1);
1991 }
1992
1993 #[tokio::test]
1994 async fn test_modifiers() {
1995 let mock_server = MockServer::start().await;
1996
1997 Mock::given(method("GET"))
1999 .and(path("/rest/v1/protected_items"))
2000 .and(header("apikey", "fake-key"))
2001 .and(header("x-supabase-admin-role", "service_role")) .respond_with(
2003 ResponseTemplate::new(200).set_body_json(json!([{ "id": 1, "data": "secret" }])),
2004 ) .mount(&mock_server)
2006 .await;
2007
2008 Mock::given(method("GET"))
2010 .and(path("/rest/v1/items"))
2011 .and(query_param("order", "name.desc"))
2012 .and(header("apikey", "fake-key"))
2013 .respond_with(
2014 ResponseTemplate::new(200).set_body_json(json!([{ "id": 1, "name": "Zebra" }])),
2015 )
2016 .mount(&mock_server)
2017 .await;
2018
2019 Mock::given(method("GET"))
2021 .and(path("/rest/v1/items"))
2022 .and(query_param("limit", "5"))
2023 .and(header("apikey", "fake-key"))
2024 .respond_with(ResponseTemplate::new(200).set_body_json(json!([{}, {}, {}, {}, {}])))
2025 .mount(&mock_server)
2026 .await;
2027
2028 Mock::given(method("GET"))
2030 .and(path("/rest/v1/items"))
2031 .and(query_param("offset", "10"))
2032 .and(header("apikey", "fake-key"))
2033 .respond_with(ResponseTemplate::new(200).set_body_json(json!([{ "id": 11 }])))
2034 .mount(&mock_server)
2035 .await;
2036
2037 Mock::given(method("GET"))
2039 .and(path("/rest/v1/items"))
2040 .and(query_param("limit", "2"))
2041 .and(query_param("offset", "3")) .and(header("apikey", "fake-key"))
2043 .respond_with(
2044 ResponseTemplate::new(200).set_body_json(json!([{ "id": 4 }, { "id": 5 }])),
2045 )
2046 .mount(&mock_server)
2047 .await;
2048
2049 let client = PostgrestClient::new(
2050 &mock_server.uri(),
2051 "fake-key",
2052 "protected_items",
2053 reqwest::Client::new(),
2054 );
2055
2056 let result_rls = client.ignore_rls().execute::<Value>().await;
2058 assert!(result_rls.is_ok());
2059 assert_eq!(result_rls.unwrap().len(), 1);
2060
2061 let client_order = PostgrestClient::new(
2063 &mock_server.uri(),
2064 "fake-key",
2065 "items",
2066 reqwest::Client::new(),
2067 );
2068 let result_order = client_order
2069 .order("name", SortOrder::Descending)
2070 .execute::<Value>()
2071 .await;
2072 assert!(
2073 result_order.is_ok(),
2074 "Order modifier failed: {:?}",
2075 result_order.err()
2076 );
2077 assert_eq!(result_order.unwrap().len(), 1);
2078
2079 let client_limit = PostgrestClient::new(
2081 &mock_server.uri(),
2082 "fake-key",
2083 "items",
2084 reqwest::Client::new(),
2085 );
2086 let result_limit = client_limit.limit(5).execute::<Value>().await;
2087 assert!(
2088 result_limit.is_ok(),
2089 "Limit modifier failed: {:?}",
2090 result_limit.err()
2091 );
2092 assert_eq!(result_limit.unwrap().len(), 5);
2093
2094 let client_offset = PostgrestClient::new(
2096 &mock_server.uri(),
2097 "fake-key",
2098 "items",
2099 reqwest::Client::new(),
2100 );
2101 let result_offset = client_offset.offset(10).execute::<Value>().await;
2102 assert!(
2103 result_offset.is_ok(),
2104 "Offset modifier failed: {:?}",
2105 result_offset.err()
2106 );
2107 assert_eq!(result_offset.unwrap().len(), 1); let client_limit_offset = PostgrestClient::new(
2111 &mock_server.uri(),
2112 "fake-key",
2113 "items",
2114 reqwest::Client::new(),
2115 );
2116 let result_limit_offset = client_limit_offset
2117 .limit(2)
2118 .offset(3)
2119 .execute::<Value>()
2120 .await;
2121 assert!(
2122 result_limit_offset.is_ok(),
2123 "Limit/Offset modifier failed: {:?}",
2124 result_limit_offset.err()
2125 );
2126 assert_eq!(result_limit_offset.unwrap().len(), 2);
2127
2128 }
2130
2131 #[tokio::test]
2132 async fn test_error_handling() {
2133 let mock_server = MockServer::start().await;
2134 let base_uri = mock_server.uri();
2135 let api_key = "fake-key";
2136 let table_name = "items";
2137
2138 Mock::given(method("GET"))
2140 .and(path("/rest/v1/items"))
2141 .and(header("apikey", "invalid-key")) .respond_with(ResponseTemplate::new(401).set_body_json(json!({
2143 "message": "Invalid API key"
2144 })))
2145 .mount(&mock_server)
2146 .await;
2147
2148 let insert_bad_data = json!({ "value": 10 }); Mock::given(method("POST"))
2151 .and(path("/rest/v1/items"))
2152 .and(header("apikey", api_key))
2153 .and(header("content-type", "application/json"))
2154 .and(header("prefer", "return=representation"))
2155 .and(body_json(&insert_bad_data))
2156 .respond_with(ResponseTemplate::new(400).set_body_json(json!({
2157 "code": "23502",
2158 "message": "null value in column \"name\" violates not-null constraint",
2159 "details": null,
2160 "hint": null
2161 })))
2162 .mount(&mock_server)
2163 .await;
2164
2165 Mock::given(method("GET"))
2167 .and(path("/rest/v1/server_error"))
2168 .and(header("apikey", api_key))
2169 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
2170 .mount(&mock_server)
2171 .await;
2172
2173 let client_401 =
2175 PostgrestClient::new(&base_uri, "invalid-key", table_name, reqwest::Client::new());
2176 let result_401 = client_401.select("*").execute::<Value>().await;
2177 assert!(result_401.is_err());
2178 match result_401.err().unwrap() {
2179 PostgrestError::ApiError { details, status } => {
2180 assert_eq!(status, reqwest::StatusCode::UNAUTHORIZED);
2181 assert_eq!(details.message, Some("Invalid API key".to_string()));
2182 }
2183 PostgrestError::UnparsedApiError { message, status } => {
2184 assert_eq!(status, reqwest::StatusCode::UNAUTHORIZED);
2186 assert!(message.contains("Invalid API key"));
2187 }
2188 e => panic!("Expected ApiError or UnparsedApiError for 401, got {:?}", e),
2189 }
2190
2191 let client_400 =
2193 PostgrestClient::new(&base_uri, api_key, table_name, reqwest::Client::new());
2194 let result_400 = client_400.insert(&insert_bad_data).await;
2195 assert!(result_400.is_err());
2196 match result_400.err().unwrap() {
2197 PostgrestError::ApiError { details, status } => {
2198 assert_eq!(status, reqwest::StatusCode::BAD_REQUEST);
2199 assert_eq!(details.code, Some("23502".to_string()));
2200 assert!(details
2201 .message
2202 .unwrap()
2203 .contains("violates not-null constraint"));
2204 }
2205 e => panic!("Expected ApiError for 400, got {:?}", e),
2206 }
2207
2208 let client_500 =
2210 PostgrestClient::new(&base_uri, api_key, "server_error", reqwest::Client::new());
2211 let result_500 = client_500.select("*").execute::<Value>().await;
2212 assert!(result_500.is_err());
2213 match result_500.err().unwrap() {
2214 PostgrestError::UnparsedApiError { message, status } => {
2215 assert_eq!(status, reqwest::StatusCode::INTERNAL_SERVER_ERROR);
2216 assert_eq!(message, "Internal Server Error");
2217 }
2218 e => panic!("Expected UnparsedApiError for 500, got {:?}", e),
2219 }
2220 }
2221}