supabase_rust_postgrest/
lib.rs

1//! Supabase PostgREST client for Rust
2//!
3//! This crate provides database functionality for Supabase,
4//! allowing for querying, filtering, and manipulating data in PostgreSQL.
5//!
6//! # Features
7//!
8//! - Query API (`select`, `insert`, `update`, `delete`)
9//! - Filtering (`eq`, `gt`, `lt`, etc.)
10//! - Ordering and pagination
11//! - Transactions
12//! - RPC function calls
13//! - CSV export
14
15use 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/// PostgREST APIエラーの詳細情報
29#[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
37// エラー詳細を整形して表示するための Display 実装
38impl 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/// エラー型
58#[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/// ソート方向
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum SortOrder {
94    Ascending,
95    Descending,
96}
97
98/// トランザクションの分離レベル
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum IsolationLevel {
101    ReadCommitted,
102    RepeatableRead,
103    Serializable,
104}
105
106impl IsolationLevel {
107    /// 分離レベルを文字列に変換
108    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/// トランザクションの読み取り/書き込みモード
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum TransactionMode {
120    ReadWrite,
121    ReadOnly,
122}
123
124impl TransactionMode {
125    /// トランザクションモードを文字列に変換
126    fn display(&self) -> &'static str {
127        match self {
128            TransactionMode::ReadWrite => "read write",
129            TransactionMode::ReadOnly => "read only",
130        }
131    }
132}
133
134/// トランザクションの状態
135#[allow(dead_code)]
136enum TransactionState {
137    Inactive,
138    Active,
139    Committed,
140    RolledBack,
141}
142
143/// PostgreST クライアント
144pub 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    /// 新しい PostgreST クライアントを作成
161    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    /// RPCリクエストを作成
180    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    /// ヘッダーを追加
205    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        // ヘッダー名を文字列として所有し、HeaderNameに変換する
211        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    /// 認証トークンを設定
220    pub fn with_auth(self, token: &str) -> Result<Self, PostgrestError> {
221        self.with_header("Authorization", &format!("Bearer {}", token))
222    }
223
224    /// 取得するカラムを指定
225    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    /// 結合クエリ: 参照テーブルとの内部結合
232    pub fn inner_join(mut self, foreign_table: &str, column: &str, foreign_column: &str) -> Self {
233        // 選択列にリレーションを追加
234        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    /// 結合クエリ: 参照テーブルとの左外部結合
253    pub fn left_join(mut self, foreign_table: &str, column: &str, foreign_column: &str) -> Self {
254        // 選択列にリレーションを追加
255        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    /// 結合クエリ: 一対多関係の子テーブルを含める
274    pub fn include(
275        mut self,
276        foreign_table: &str,
277        _foreign_column: &str,
278        columns: Option<&str>,
279    ) -> Self {
280        // 選択列にリレーションを追加
281        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    /// 結合クエリ: 外部キーの参照先テーブルを含める
298    pub fn referenced_by(mut self, foreign_table: &str, foreign_column: &str) -> Self {
299        // 選択列にリレーションを追加
300        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    /// 等価フィルター
319    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    /// より大きいフィルター
326    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    /// 以上フィルター
333    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    /// より小さいフィルター
340    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    /// 以下フィルター
347    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    /// LIKE フィルター
354    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    /// ILIKE フィルター(大文字小文字を区別しない)
361    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    /// IN フィルター
368    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    /// NOT フィルター
376    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    /// JSON/JSONB カラムが指定した値を含むか (`cs`, `@>`) フィルター
383    /// value は serde_json::Value で指定します
384    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    /// JSON/JSONB カラムが指定した値に含まれるか (`cd`, `<@`) フィルター
392    /// value は serde_json::Value で指定します
393    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    /// ソート順を指定
401    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    /// 取得件数を制限
412    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    /// オフセットを指定
419    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    /// 全文検索
426    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    /// 地理空間データの距離ベース検索
437    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    /// グループ化
453    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    /// 行数カウント
460    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    /// RLS(行レベルセキュリティ)ポリシーを無視
468    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    /// スキーマを指定(デフォルトのpublicスキーマではない場合)
477    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    /// CSVとしてデータをエクスポート
484    pub async fn export_csv(&self) -> Result<String, PostgrestError> {
485        let mut url = self.build_url()?;
486
487        // CSVフォーマットを指定
488        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    /// データを取得
522    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            // Attempt to parse specific error details
541            if let Ok(details) = serde_json::from_str::<PostgrestApiErrorDetails>(&error_text) {
542                return Err(PostgrestError::ApiError { details, status });
543            } else {
544                // If parsing fails, return a less specific error with the raw message
545                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    /// データを挿入
559    pub async fn insert<T: Serialize>(&self, values: T) -> Result<Value, PostgrestError> {
560        let url = self.build_url()?;
561
562        // Clone headers and add the Prefer header
563        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) // Use modified headers
573            .json(&values)
574            .send()
575            .await
576            .map_err(PostgrestError::NetworkError)?;
577
578        let status = response.status();
579
580        // Check for success first (e.g., 201 Created)
581        if status.is_success() {
582            // Read the body as text first to handle potential empty responses
583            let body_text = response.text().await.map_err(|e| {
584                PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
585            })?;
586
587            // If body is empty but status was success (e.g., 201), return Null.
588            // PostgREST usually returns the inserted row(s), so empty is unexpected.
589            if body_text.trim().is_empty() {
590                // Consider returning Value::Array(vec![]) if an array is expected
591                Ok(Value::Null)
592            } else {
593                // If body is not empty, try to parse it as JSON
594                serde_json::from_str::<Value>(&body_text)
595                    .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
596            }
597        } else {
598            // Handle non-success status codes as before
599            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    /// データを更新
617    pub async fn update<T: Serialize>(&self, values: T) -> Result<Value, PostgrestError> {
618        let url = self.build_url()?;
619
620        // Clone headers and add the Prefer header
621        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) // Use modified headers
631            .json(&values)
632            .send()
633            .await
634            .map_err(PostgrestError::NetworkError)?;
635
636        let status = response.status();
637
638        // Check for success (e.g., 200 OK, 204 No Content)
639        if status.is_success() {
640            // Read the body as text first
641            let body_text = response.text().await.map_err(|e| {
642                PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
643            })?;
644
645            // If body is empty, return Null. Update might return 204 No Content.
646            if body_text.trim().is_empty() {
647                Ok(Value::Null)
648            } else {
649                // If body is not empty, try to parse it as JSON
650                serde_json::from_str::<Value>(&body_text)
651                    .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
652            }
653        } else {
654            // Handle non-success status codes
655            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    /// データを削除
673    pub async fn delete(&self) -> Result<Value, PostgrestError> {
674        let url = self.build_url()?;
675
676        // Clone headers and add the Prefer header
677        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) // Use modified headers
687            .send()
688            .await
689            .map_err(PostgrestError::NetworkError)?;
690
691        let status = response.status();
692
693        // Check for success (e.g., 200 OK, 204 No Content)
694        if status.is_success() {
695            // Read the body as text first
696            let body_text = response.text().await.map_err(|e| {
697                PostgrestError::DeserializationError(format!("Failed to read response body: {}", e))
698            })?;
699
700            // If body is empty, return Null. Delete often returns 204 No Content.
701            if body_text.trim().is_empty() {
702                Ok(Value::Null)
703            } else {
704                // If body is not empty, try to parse it as JSON
705                serde_json::from_str::<Value>(&body_text)
706                    .map_err(|e| PostgrestError::DeserializationError(e.to_string()))
707            }
708        } else {
709            // Handle non-success status codes
710            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    /// RPC関数を呼び出す (POSTリクエスト)
728    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        // RPCの場合はテーブル名が関数名として扱われる
735        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    // URLを構築
776    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    /// トランザクションを開始
787    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        // トランザクションオプションを構築
794        let isolation = isolation_level.unwrap_or(IsolationLevel::ReadCommitted);
795        let mode = transaction_mode.unwrap_or(TransactionMode::ReadWrite);
796
797        // トランザクション開始リクエストを構築
798        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        // トランザクション開始APIを呼び出し
808        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            // Transaction begin might not return standard PostgREST JSON error, treat as TransactionError
827            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        // トランザクションオブジェクトを作成して返す
844        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
854/// トランザクションクライアント
855pub 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>, // トランザクションがアクティブかどうか
862}
863
864impl PostgrestTransaction {
865    /// 新しいトランザクションを作成
866    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)), // トランザクションは初期状態でアクティブ
880        }
881    }
882
883    /// トランザクション内で指定したテーブルに対するクライアントを取得
884    pub fn from(&self, table: &str) -> PostgrestClient {
885        // トランザクションIDをクエリパラメータとして追加するクライアントを作成
886        let mut client = PostgrestClient::new(
887            &self.base_url,
888            &self.api_key,
889            table,
890            self.http_client.clone(),
891        );
892
893        // トランザクションヘッダーを設定
894        for (key, value) in self.headers.iter() {
895            // HeaderNameをStr形式に変換
896            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        // トランザクションIDをクエリパラメータに追加
911        client
912            .query_params
913            .insert("transaction".to_string(), self.transaction_id.clone());
914
915        client
916    }
917
918    /// トランザクションをコミット
919    pub async fn commit(&self) -> Result<(), PostgrestError> {
920        // トランザクションがアクティブかチェック
921        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        // コミットAPIを呼び出し
928        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            // Treat transaction commit/rollback errors specifically
951            return Err(PostgrestError::TransactionError(format!(
952                "Failed to commit transaction: {} (Status: {})",
953                error_text, status
954            )));
955        }
956
957        // トランザクションを非アクティブに設定
958        self.state.store(false, Ordering::SeqCst);
959
960        Ok(())
961    }
962
963    /// トランザクションをロールバック
964    pub async fn rollback(&self) -> Result<(), PostgrestError> {
965        // トランザクションがアクティブかチェック
966        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        // ロールバックAPIを呼び出し
973        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        // トランザクションを非アクティブに設定
1001        self.state.store(false, Ordering::SeqCst);
1002
1003        Ok(())
1004    }
1005
1006    /// セーブポイントを作成
1007    pub async fn savepoint(&self, name: &str) -> Result<(), PostgrestError> {
1008        // トランザクションがアクティブかチェック
1009        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        // セーブポイントAPIを呼び出し
1016        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    /// セーブポイントにロールバック
1047    pub async fn rollback_to_savepoint(&self, name: &str) -> Result<(), PostgrestError> {
1048        // トランザクションがアクティブかチェック
1049        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        // セーブポイントへのロールバックAPIを呼び出し
1056        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
1087// デストラクタに相当する実装(トランザクションが終了するとロールバック)
1088impl Drop for PostgrestTransaction {
1089    fn drop(&mut self) {
1090        // トランザクションがまだアクティブな場合は自動ロールバック
1091        if self.state.load(Ordering::SeqCst) {
1092            eprintln!("Warning: Active transaction is being dropped without commit or rollback. Performing automatic rollback.");
1093
1094            // ブロッキング呼び出しが推奨されませんが、Dropコンテキストでは非同期関数を呼び出せないため
1095            let url = format!("{}/rest/v1/rpc/rollback_transaction", self.base_url);
1096
1097            let client = Client::new();
1098            // Using drop to explicitly drop the future and avoid the warning
1099            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        // Selectクエリのモック
1122        Mock::given(method("GET"))
1123            .and(path("/rest/v1/items"))
1124            .and(query_param("select", "*")) // select=* を想定
1125            .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", // テーブル名
1137            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        // RPC呼び出しのモック (POST)
1170        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)) // リクエストボディを検証
1174            .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        // RPC 用クライアント作成
1183        let client = PostgrestClient::rpc(
1184            &mock_server.uri(),
1185            "fake-key",
1186            "my_rpc_function", // RPC関数名
1187            rpc_params.clone(),
1188            reqwest::Client::new(),
1189        );
1190        println!("Client created for RPC test");
1191
1192        // RPC呼び出し
1193        #[derive(Deserialize, Debug, PartialEq)]
1194        struct RpcResponse {
1195            result: String,
1196            data: i32,
1197        }
1198
1199        let result = client.call_rpc::<RpcResponse>().await; // 新しいメソッドを使用
1200
1201        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        // 結合クエリの戻り値をモック
1222        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        // 全文検索のモック
1282        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        // CSVエクスポートのモック
1319        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        // BEGIN トランザクションのモック
1353        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        // トランザクション内のINSERTのモック
1363        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        // トランザクション内のSELECTのモック
1375        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        // COMMITのモック
1387        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        // テスト実行
1400        let client = PostgrestClient::new(
1401            &mock_server.uri(),
1402            "fake-key",
1403            "users",
1404            reqwest::Client::new(),
1405        );
1406        println!("Client created");
1407
1408        // トランザクション開始
1409        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        // トランザクション内で挿入
1425        let insert_result = transaction
1426            .from("users")
1427            .insert(json!({
1428                "name": "テストユーザー"
1429            }))
1430            .await;
1431
1432        assert!(insert_result.is_ok());
1433
1434        // トランザクション内でクエリ
1435        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        // トランザクションをコミット
1452        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        // BEGIN トランザクションのモック
1461        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        // ROLLBACKのモック
1470        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        // テスト実行
1482        let client = PostgrestClient::new(
1483            &mock_server.uri(),
1484            "fake-key",
1485            "users",
1486            reqwest::Client::new(),
1487        );
1488
1489        // トランザクション開始
1490        let transaction = client.begin_transaction(None, None, None).await;
1491
1492        assert!(transaction.is_ok());
1493        let transaction = transaction.unwrap();
1494
1495        // トランザクションをロールバック
1496        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        // BEGIN トランザクションのモック
1505        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        // SAVEPOINTのモック
1514        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        // ROLLBACK TO SAVEPOINTのモック
1527        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        // COMMITのモック
1540        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        // テスト実行
1552        let client = PostgrestClient::new(
1553            &mock_server.uri(),
1554            "fake-key",
1555            "users",
1556            reqwest::Client::new(),
1557        );
1558
1559        // トランザクション開始
1560        let transaction = client.begin_transaction(None, None, None).await;
1561
1562        assert!(transaction.is_ok());
1563        let transaction = transaction.unwrap();
1564
1565        // セーブポイント作成
1566        let savepoint_result = transaction.savepoint("sp1").await;
1567        assert!(savepoint_result.is_ok());
1568
1569        // セーブポイントにロールバック
1570        let rollback_to_savepoint_result = transaction.rollback_to_savepoint("sp1").await;
1571        assert!(rollback_to_savepoint_result.is_ok());
1572
1573        // トランザクションをコミット
1574        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        // contains のモック
1586        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        // contained_by のモック
1594        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        // contains テスト
1609        let result_contains = PostgrestClient::new(
1610            // Re-create or adjust structure if needed
1611            &mock_server.uri(),
1612            "fake-key",
1613            "data",
1614            reqwest::Client::new(), // Assuming new client instance is ok for test
1615        )
1616        .contains("metadata", &contains_value)
1617        .unwrap() // Result from contains
1618        .execute::<serde_json::Value>()
1619        .await;
1620        assert!(result_contains.is_ok());
1621        assert_eq!(result_contains.unwrap().len(), 1);
1622
1623        // contained_by テスト
1624        let result_contained_by = PostgrestClient::new(
1625            // Re-create or adjust structure if needed
1626            &mock_server.uri(),
1627            "fake-key",
1628            "data",
1629            reqwest::Client::new(), // Assuming new client instance is ok for test
1630        )
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        // Related table filter のモック
1644        Mock::given(method("GET"))
1645            .and(path("/rest/v1/posts"))
1646            .and(query_param("author.name", "eq.Specific Author")) // authorテーブルのnameでフィルタ
1647            .and(query_param("select", "title,author!inner(name)")) // select句も設定
1648            .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)") // joinを含めておく
1663            .eq("author.name", "Specific Author") // 関連テーブルのカラムを指定してフィルタ
1664            .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 for gt filter
1816        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 for like filter
1827        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 for in_list filter
1838        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 for gte
1849        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 for lt
1860        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 for lte
1871        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 for ilike
1882        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 for not eq
1893        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        // Test gt
1908        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        // Test like
1915        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        // Test in_list
1926        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        // Test gte
1936        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        // Test lt
1947        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        // Test lte
1954        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        // Test ilike
1965        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        // Test not
1979        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 for ignore_rls
1998        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")) // Expect admin role header
2002            .respond_with(
2003                ResponseTemplate::new(200).set_body_json(json!([{ "id": 1, "data": "secret" }])),
2004            ) // Example response
2005            .mount(&mock_server)
2006            .await;
2007
2008        // Mock for order
2009        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 for limit
2020        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 for offset
2029        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 for limit and offset
2038        Mock::given(method("GET"))
2039            .and(path("/rest/v1/items"))
2040            .and(query_param("limit", "2"))
2041            .and(query_param("offset", "3")) // Added matcher for offset
2042            .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        // Test ignore_rls
2057        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        // Test order
2062        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        // Test limit
2080        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        // Test offset
2095        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); // Based on mock
2108
2109        // Test limit and offset
2110        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        // TODO: Add test for count() when execute() can return count information
2129    }
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 for 401 Unauthorized (select with bad key)
2139        Mock::given(method("GET"))
2140            .and(path("/rest/v1/items"))
2141            .and(header("apikey", "invalid-key")) // Expect invalid key
2142            .respond_with(ResponseTemplate::new(401).set_body_json(json!({
2143                "message": "Invalid API key"
2144            })))
2145            .mount(&mock_server)
2146            .await;
2147
2148        // Mock for 400 Bad Request (insert missing required field)
2149        let insert_bad_data = json!({ "value": 10 }); // Missing 'name'
2150        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 for 500 Internal Server Error (select returning plain text)
2166        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        // Test 401 Unauthorized on select
2174        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                // Handle case where details parsing might fail
2185                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        // Test 400 Bad Request on insert
2192        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        // Test 500 Internal Server Error on select
2209        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}