supabase_rust_functions/
lib.rs

1//! Supabase Edge Functions client for Rust
2//!
3//! This crate provides functionality for invoking Supabase Edge Functions.
4
5use base64::Engine;
6use bytes::{BufMut, Bytes, BytesMut};
7use futures_util::{Stream, StreamExt};
8use reqwest::{Client, Response, StatusCode};
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12use std::pin::Pin;
13use std::time::Duration;
14use thiserror::Error;
15use url::Url;
16
17/// エラー型の詳細
18#[derive(Debug, Clone, Deserialize)]
19pub struct FunctionErrorDetails {
20    pub message: Option<String>,
21    pub status: Option<u16>,
22    pub code: Option<String>,
23    pub details: Option<Value>,
24}
25
26/// エラー型
27#[derive(Debug, Error)]
28pub enum FunctionsError {
29    #[error("Request error: {0}")]
30    RequestError(#[from] reqwest::Error),
31
32    #[error("URL parse error: {0}")]
33    UrlError(#[from] url::ParseError),
34
35    #[error("JSON error: {0}")]
36    JsonError(#[from] serde_json::Error),
37
38    #[error("Function error (status: {status}): {message}")]
39    FunctionError {
40        message: String,
41        status: StatusCode,
42        details: Option<FunctionErrorDetails>,
43    },
44
45    #[error("Timeout error: Function execution exceeded timeout limit")]
46    TimeoutError,
47
48    #[error("Invalid response: {0}")]
49    InvalidResponse(String),
50}
51
52impl FunctionsError {
53    pub fn new(message: String) -> Self {
54        Self::FunctionError {
55            message,
56            status: StatusCode::INTERNAL_SERVER_ERROR,
57            details: None,
58        }
59    }
60
61    pub fn from_response(response: &Response) -> Self {
62        Self::FunctionError {
63            message: format!("Function returned error status: {}", response.status()),
64            status: response.status(),
65            details: None,
66        }
67    }
68
69    pub fn with_details(response: &Response, details: FunctionErrorDetails) -> Self {
70        Self::FunctionError {
71            message: details.message.as_ref().map_or_else(
72                || format!("Function returned error status: {}", response.status()),
73                |msg| msg.clone(),
74            ),
75            status: response.status(),
76            details: Some(details),
77        }
78    }
79}
80
81pub type Result<T> = std::result::Result<T, FunctionsError>;
82
83/// 関数呼び出しオプション
84#[derive(Clone, Debug)]
85pub struct FunctionOptions {
86    /// カスタムHTTPヘッダー
87    pub headers: Option<HashMap<String, String>>,
88
89    /// 関数タイムアウト(秒)
90    pub timeout_seconds: Option<u64>,
91
92    /// レスポンスのコンテンツタイプを指定(デフォルトはJSONとして処理)
93    pub response_type: ResponseType,
94
95    /// リクエストのコンテンツタイプ
96    pub content_type: Option<String>,
97}
98
99impl Default for FunctionOptions {
100    fn default() -> Self {
101        Self {
102            headers: None,
103            timeout_seconds: None,
104            response_type: ResponseType::Json,
105            content_type: None,
106        }
107    }
108}
109
110/// レスポンスの処理方法
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum ResponseType {
113    /// JSONとしてパースする(デフォルト)
114    Json,
115
116    /// テキストとして処理する
117    Text,
118
119    /// バイトデータとして処理する
120    Binary,
121
122    /// ストリームとして処理する
123    Stream,
124}
125
126/// ストリーミングレスポンス用の型
127pub type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes>> + Send>>;
128
129/// 関数レスポンス
130#[derive(Debug, Clone)]
131pub struct FunctionResponse<T> {
132    /// レスポンスデータ
133    pub data: T,
134
135    /// HTTPステータスコード
136    pub status: StatusCode,
137
138    /// レスポンスヘッダー
139    pub headers: HashMap<String, String>,
140}
141
142/// Edge Functions クライアント
143pub struct FunctionsClient {
144    base_url: String,
145    api_key: String,
146    http_client: Client,
147}
148
149/// 関数リクエストを表す構造体
150pub struct FunctionRequest<'a, T> {
151    client: &'a FunctionsClient,
152    function_name: String,
153    _response_type: std::marker::PhantomData<T>,
154}
155
156impl<'a, T: DeserializeOwned> FunctionRequest<'a, T> {
157    /// 関数を実行する
158    pub async fn execute<B: Serialize>(
159        &self,
160        body: Option<B>,
161        options: Option<FunctionOptions>,
162    ) -> Result<T> {
163        let result = self
164            .client
165            .invoke::<T, B>(&self.function_name, body, options)
166            .await?;
167        Ok(result.data)
168    }
169}
170
171impl FunctionsClient {
172    /// 新しい Edge Functions クライアントを作成
173    pub fn new(supabase_url: &str, supabase_key: &str, http_client: Client) -> Self {
174        Self {
175            base_url: supabase_url.to_string(),
176            api_key: supabase_key.to_string(),
177            http_client,
178        }
179    }
180
181    /// Edge Function を呼び出す
182    pub async fn invoke<T: DeserializeOwned, B: Serialize>(
183        &self,
184        function_name: &str,
185        body: Option<B>,
186        options: Option<FunctionOptions>,
187    ) -> Result<FunctionResponse<T>> {
188        let opts = options.unwrap_or_default();
189
190        // URLの構築
191        let mut url = Url::parse(&self.base_url)?;
192        url.path_segments_mut()
193            .map_err(|_| FunctionsError::UrlError(url::ParseError::EmptyHost))?
194            .push("functions")
195            .push("v1")
196            .push(function_name);
197
198        // リクエストの構築
199        let mut request_builder = self
200            .http_client
201            .post(url)
202            .header("apikey", &self.api_key)
203            .header("Authorization", format!("Bearer {}", &self.api_key));
204
205        // リクエストタイムアウトの設定
206        if let Some(timeout) = opts.timeout_seconds {
207            request_builder = request_builder.timeout(Duration::from_secs(timeout));
208        }
209
210        // コンテンツタイプの設定
211        if let Some(content_type) = opts.content_type {
212            request_builder = request_builder.header("Content-Type", content_type);
213        }
214
215        // カスタムヘッダーの追加
216        if let Some(headers) = opts.headers {
217            for (key, value) in headers {
218                request_builder = request_builder.header(key, value);
219            }
220        }
221
222        // リクエストボディの追加
223        if let Some(body_data) = body {
224            request_builder = request_builder.json(&body_data);
225        }
226
227        // リクエストの送信
228        let response = request_builder.send().await.map_err(|e| {
229            if e.is_timeout() {
230                FunctionsError::TimeoutError
231            } else {
232                FunctionsError::from(e)
233            }
234        })?;
235
236        // ステータスコードの確認
237        let status = response.status();
238        if !status.is_success() {
239            // レスポンスのクローンを作成してエラー処理に使用
240            let status_copy = status;
241
242            // エラーレスポンスのパース
243            let error_body = response
244                .text()
245                .await
246                .unwrap_or_else(|_| "Failed to read error response".to_string());
247
248            if let Ok(error_details) = serde_json::from_str::<FunctionErrorDetails>(&error_body) {
249                return Err(FunctionsError::FunctionError {
250                    message: error_details.message.as_ref().map_or_else(
251                        || format!("Function returned error status: {}", status_copy),
252                        |msg| msg.clone(),
253                    ),
254                    status: status_copy,
255                    details: Some(error_details),
256                });
257            } else {
258                return Err(FunctionsError::FunctionError {
259                    message: error_body,
260                    status: status_copy,
261                    details: None,
262                });
263            }
264        }
265
266        // レスポンスヘッダーの抽出
267        let headers = response
268            .headers()
269            .iter()
270            .map(|(name, value)| (name.to_string(), value.to_str().unwrap_or("").to_string()))
271            .collect::<HashMap<String, String>>();
272
273        // レスポンスタイプに応じた処理
274        match opts.response_type {
275            ResponseType::Json => {
276                let data = response.json::<T>().await.map_err(|e| {
277                    FunctionsError::JsonError(serde_json::from_str::<T>("{}").err().unwrap_or_else(
278                        || {
279                            serde_json::Error::io(std::io::Error::new(
280                                std::io::ErrorKind::InvalidData,
281                                e.to_string(),
282                            ))
283                        },
284                    ))
285                })?;
286
287                Ok(FunctionResponse {
288                    data,
289                    status,
290                    headers,
291                })
292            }
293            ResponseType::Text => {
294                // テキスト処理
295                let text = response.text().await?;
296
297                // テキストからデシリアライズを試みる
298                let data: T = serde_json::from_str(&text).unwrap_or_else(|_| {
299                    panic!("Failed to deserialize text response as requested type")
300                });
301
302                Ok(FunctionResponse {
303                    data,
304                    status,
305                    headers,
306                })
307            }
308            ResponseType::Binary => {
309                // バイナリデータ処理
310                let bytes = response.bytes().await?;
311
312                // Base64エンコード(非推奨API対応)
313                let binary_str = base64::engine::general_purpose::STANDARD.encode(&bytes);
314
315                // バイナリデータをデシリアライズ
316                let data: T =
317                    serde_json::from_str(&format!("\"{}\"", binary_str)).unwrap_or_else(|_| {
318                        panic!("Failed to deserialize binary response as requested type")
319                    });
320
321                Ok(FunctionResponse {
322                    data,
323                    status,
324                    headers,
325                })
326            }
327            ResponseType::Stream => {
328                // ストリームレスポンスの場合、通常のデシリアライズではなく
329                // 別のストリーム処理用のメソッドを使用する必要がある
330                Err(FunctionsError::InvalidResponse(
331                    "Stream response type cannot be handled by invoke(). Use invoke_stream() instead.".to_string()
332                ))
333            }
334        }
335    }
336
337    /// JSONを返すファンクションを呼び出す(シンプルなラッパー)
338    pub async fn invoke_json<T: DeserializeOwned, B: Serialize>(
339        &self,
340        function_name: &str,
341        body: Option<B>,
342    ) -> Result<T> {
343        let options = FunctionOptions {
344            response_type: ResponseType::Json,
345            ..Default::default()
346        };
347
348        let response = self
349            .invoke::<T, B>(function_name, body, Some(options))
350            .await?;
351        Ok(response.data)
352    }
353
354    /// テキストを返すファンクションを呼び出す(シンプルなラッパー)
355    pub async fn invoke_text<B: Serialize>(
356        &self,
357        function_name: &str,
358        body: Option<B>,
359    ) -> Result<String> {
360        let options = FunctionOptions {
361            response_type: ResponseType::Text,
362            ..Default::default()
363        };
364
365        // URLの構築 (invokeからコピー&修正)
366        let mut url = Url::parse(&self.base_url)?;
367        url.path_segments_mut()
368            .map_err(|_| FunctionsError::UrlError(url::ParseError::EmptyHost))?
369            .push("functions")
370            .push("v1")
371            .push(function_name);
372
373        // リクエストの構築 (invokeからコピー&修正)
374        let mut request_builder = self
375            .http_client
376            .post(url)
377            .header("apikey", &self.api_key)
378            .header("Authorization", format!("Bearer {}", &self.api_key));
379
380        // リクエストタイムアウトの設定
381        if let Some(timeout) = options.timeout_seconds {
382            request_builder = request_builder.timeout(Duration::from_secs(timeout));
383        }
384
385        // コンテンツタイプの設定
386        if let Some(content_type) = options.content_type {
387            request_builder = request_builder.header("Content-Type", content_type);
388        } else {
389            request_builder = request_builder.header("Content-Type", "application/json");
390        }
391
392        // Accept ヘッダーを設定 (テキストを期待)
393        request_builder = request_builder.header("Accept", "text/plain, */*;q=0.9");
394
395        // カスタムヘッダーの追加
396        if let Some(headers) = options.headers {
397            for (key, value) in headers {
398                request_builder = request_builder.header(key, value);
399            }
400        }
401
402        // リクエストボディの追加
403        if let Some(body_data) = body {
404            request_builder = request_builder.json(&body_data);
405        }
406
407        // リクエストの送信
408        let response = request_builder.send().await.map_err(|e| {
409            if e.is_timeout() {
410                FunctionsError::TimeoutError
411            } else {
412                FunctionsError::from(e)
413            }
414        })?;
415
416        // ステータスコードの確認
417        let status = response.status();
418        if !status.is_success() {
419            // エラーレスポンスのパース (invokeからコピー)
420            let error_body = response
421                .text()
422                .await
423                .unwrap_or_else(|_| "Failed to read error response".to_string());
424            if let Ok(error_details) = serde_json::from_str::<FunctionErrorDetails>(&error_body) {
425                return Err(FunctionsError::FunctionError {
426                    message: error_details.message.as_ref().map_or_else(
427                        || format!("Function returned error status: {}", status),
428                        |msg| msg.clone(),
429                    ),
430                    status,
431                    details: Some(error_details),
432                });
433            } else {
434                return Err(FunctionsError::FunctionError {
435                    message: error_body,
436                    status,
437                    details: None,
438                });
439            }
440        }
441
442        // テキストを直接取得
443        response.text().await.map_err(FunctionsError::from)
444    }
445
446    /// バイナリ形式で関数レスポンスを取得
447    pub async fn invoke_binary<B: Serialize>(
448        &self,
449        function_name: &str,
450        body: Option<B>,
451        options: Option<FunctionOptions>,
452    ) -> Result<Bytes> {
453        let options = options.unwrap_or_else(|| FunctionOptions {
454            response_type: ResponseType::Binary,
455            ..Default::default()
456        });
457
458        // URLの構築
459        let mut url = Url::parse(&self.base_url)?;
460        url.path_segments_mut()
461            .map_err(|_| FunctionsError::UrlError(url::ParseError::EmptyHost))?
462            .push("functions")
463            .push("v1")
464            .push(function_name);
465
466        // リクエストの構築
467        let mut request_builder = self
468            .http_client
469            .post(url)
470            .header("apikey", &self.api_key)
471            .header("Authorization", format!("Bearer {}", &self.api_key));
472
473        // リクエストタイムアウトの設定
474        if let Some(timeout) = options.timeout_seconds {
475            request_builder = request_builder.timeout(Duration::from_secs(timeout));
476        }
477
478        // コンテンツタイプの設定
479        if let Some(content_type) = options.content_type {
480            request_builder = request_builder.header("Content-Type", content_type);
481        } else {
482            // デフォルトはJSON
483            request_builder = request_builder.header("Content-Type", "application/json");
484        }
485
486        // Accept ヘッダーを設定
487        request_builder = request_builder.header("Accept", "application/octet-stream");
488
489        // カスタムヘッダーの追加
490        if let Some(headers) = options.headers {
491            for (key, value) in headers {
492                request_builder = request_builder.header(key, value);
493            }
494        }
495
496        // リクエストボディの追加
497        if let Some(body_data) = body {
498            request_builder = request_builder.json(&body_data);
499        }
500
501        // リクエストの送信
502        let response = request_builder.send().await.map_err(|e| {
503            if e.is_timeout() {
504                FunctionsError::TimeoutError
505            } else {
506                FunctionsError::from(e)
507            }
508        })?;
509
510        // ステータスコードの確認
511        let status = response.status();
512        if !status.is_success() {
513            // エラーレスポンスのパース
514            let error_body = response
515                .text()
516                .await
517                .unwrap_or_else(|_| "Failed to read error response".to_string());
518
519            if let Ok(error_details) = serde_json::from_str::<FunctionErrorDetails>(&error_body) {
520                return Err(FunctionsError::FunctionError {
521                    message: error_details.message.as_ref().map_or_else(
522                        || format!("Function returned error status: {}", status),
523                        |msg| msg.clone(),
524                    ),
525                    status,
526                    details: Some(error_details),
527                });
528            } else {
529                return Err(FunctionsError::FunctionError {
530                    message: error_body,
531                    status,
532                    details: None,
533                });
534            }
535        }
536
537        // バイナリデータを返す
538        response.bytes().await.map_err(FunctionsError::from)
539    }
540
541    /// バイナリストリームを取得するメソッド(大きなバイナリデータに最適)
542    pub async fn invoke_binary_stream<B: Serialize>(
543        &self,
544        function_name: &str,
545        body: Option<B>,
546        options: Option<FunctionOptions>,
547    ) -> Result<ByteStream> {
548        let opts = options.unwrap_or_else(|| FunctionOptions {
549            response_type: ResponseType::Stream,
550            content_type: Some("application/octet-stream".to_string()),
551            ..Default::default()
552        });
553
554        let mut custom_opts = opts;
555        let mut headers = custom_opts.headers.unwrap_or_default();
556        headers.insert("Accept".to_string(), "application/octet-stream".to_string());
557        custom_opts.headers = Some(headers);
558
559        self.invoke_stream(function_name, body, Some(custom_opts))
560            .await
561    }
562
563    /// チャンク単位でバイナリを処理する補助メソッド
564    pub fn process_binary_chunks<F>(
565        &self,
566        stream: ByteStream,
567        chunk_size: usize,
568        mut processor: F,
569    ) -> Pin<Box<dyn Stream<Item = Result<Bytes>> + Send + '_>>
570    where
571        F: FnMut(&[u8]) -> std::result::Result<Bytes, String> + Send + 'static,
572    {
573        Box::pin(async_stream::stream! {
574            let mut buffer = BytesMut::new();
575
576            tokio::pin!(stream);
577            while let Some(chunk_result) = stream.next().await {
578                match chunk_result {
579                    Ok(chunk) => {
580                        // バッファに追加
581                        buffer.extend_from_slice(&chunk);
582
583                        // chunk_sizeを超えたら処理
584                        while buffer.len() >= chunk_size {
585                            let chunk_to_process = buffer.split_to(chunk_size);
586                            match processor(&chunk_to_process) {
587                                Ok(processed) => yield Ok(processed),
588                                Err(err) => {
589                                    yield Err(FunctionsError::InvalidResponse(err));
590                                    return;
591                                }
592                            }
593                        }
594                    },
595                    Err(e) => {
596                        yield Err(e);
597                        return;
598                    }
599                }
600            }
601
602            // 残りのバッファを処理
603            if !buffer.is_empty() {
604                match processor(&buffer) {
605                    Ok(processed) => yield Ok(processed),
606                    Err(err) => yield Err(FunctionsError::InvalidResponse(err)),
607                }
608            }
609        })
610    }
611
612    /// ストリーミングレスポンスを取得するメソッド
613    pub async fn invoke_stream<B: Serialize>(
614        &self,
615        function_name: &str,
616        body: Option<B>,
617        options: Option<FunctionOptions>,
618    ) -> Result<ByteStream> {
619        let opts = options.unwrap_or_else(|| FunctionOptions {
620            response_type: ResponseType::Stream,
621            ..Default::default()
622        });
623
624        // URLの構築
625        let mut url = Url::parse(&self.base_url)?;
626        url.path_segments_mut()
627            .map_err(|_| FunctionsError::UrlError(url::ParseError::EmptyHost))?
628            .push("functions")
629            .push("v1")
630            .push(function_name);
631
632        // リクエストの構築
633        let mut request_builder = self
634            .http_client
635            .post(url)
636            .header("apikey", &self.api_key)
637            .header("Authorization", format!("Bearer {}", &self.api_key));
638
639        // リクエストタイムアウトの設定
640        if let Some(timeout) = opts.timeout_seconds {
641            request_builder = request_builder.timeout(Duration::from_secs(timeout));
642        }
643
644        // コンテンツタイプの設定
645        if let Some(content_type) = opts.content_type {
646            request_builder = request_builder.header("Content-Type", content_type);
647        } else {
648            // デフォルトはJSON
649            request_builder = request_builder.header("Content-Type", "application/json");
650        }
651
652        // カスタムヘッダーの追加
653        if let Some(headers) = opts.headers {
654            for (key, value) in headers {
655                request_builder = request_builder.header(key, value);
656            }
657        }
658
659        // リクエストボディの追加
660        if let Some(body_data) = body {
661            request_builder = request_builder.json(&body_data);
662        }
663
664        // リクエストの送信
665        let response = request_builder.send().await.map_err(|e| {
666            if e.is_timeout() {
667                FunctionsError::TimeoutError
668            } else {
669                FunctionsError::from(e)
670            }
671        })?;
672
673        // ステータスコードの確認
674        let status = response.status();
675        if !status.is_success() {
676            // ステータスコードのコピーを保持
677            let status_copy = status;
678
679            // エラーレスポンスのパース
680            let error_body = response
681                .text()
682                .await
683                .unwrap_or_else(|_| "Failed to read error response".to_string());
684
685            if let Ok(error_details) = serde_json::from_str::<FunctionErrorDetails>(&error_body) {
686                return Err(FunctionsError::FunctionError {
687                    message: error_details.message.as_ref().map_or_else(
688                        || format!("Function returned error status: {}", status_copy),
689                        |msg| msg.clone(),
690                    ),
691                    status: status_copy,
692                    details: Some(error_details),
693                });
694            } else {
695                return Err(FunctionsError::FunctionError {
696                    message: error_body,
697                    status: status_copy,
698                    details: None,
699                });
700            }
701        }
702
703        // ストリームを返す
704        Ok(Box::pin(
705            response
706                .bytes_stream()
707                .map(|result| result.map_err(FunctionsError::from)),
708        ))
709    }
710
711    /// JSONストリームを取得するメソッド(SSE形式のJSONイベントを扱う)
712    pub async fn invoke_json_stream<B: Serialize>(
713        &self,
714        function_name: &str,
715        body: Option<B>,
716        options: Option<FunctionOptions>,
717    ) -> Result<Pin<Box<dyn Stream<Item = Result<Value>> + Send + '_>>> {
718        let byte_stream = self.invoke_stream(function_name, body, options).await?;
719        let json_stream = self.byte_stream_to_json(byte_stream);
720        Ok(json_stream)
721    }
722
723    /// バイトストリームをJSONストリームに変換する
724    fn byte_stream_to_json(
725        &self,
726        stream: ByteStream,
727    ) -> Pin<Box<dyn Stream<Item = Result<Value>> + Send + '_>> {
728        Box::pin(async_stream::stream! {
729            let mut line_stream = self.stream_to_lines(stream);
730
731            while let Some(line_result) = line_stream.next().await {
732                match line_result {
733                    Ok(line) => {
734                        // 空行はスキップ
735                        if line.trim().is_empty() {
736                            continue;
737                        }
738
739                        // JSON解析を試みる
740                        match serde_json::from_str::<Value>(&line) {
741                            Ok(json_value) => {
742                                yield Ok(json_value);
743                            },
744                            Err(err) => {
745                                yield Err(FunctionsError::JsonError(err));
746                            }
747                        }
748                    },
749                    Err(err) => {
750                        yield Err(err);
751                        break;
752                    }
753                }
754            }
755        })
756    }
757
758    /// ストリームを行に変換する
759    pub fn stream_to_lines(
760        &self,
761        stream: ByteStream,
762    ) -> Pin<Box<dyn Stream<Item = Result<String>> + Send + '_>> {
763        Box::pin(async_stream::stream! {
764            let mut buf = BytesMut::new();
765
766            // 行ごとに処理
767            tokio::pin!(stream);
768            while let Some(chunk_result) = stream.next().await {
769                match chunk_result {
770                    Ok(chunk) => {
771                        buf.extend_from_slice(&chunk);
772
773                        // バッファから完全な行を探して処理
774                        while let Some(i) = buf.iter().position(|&b| b == b'\n') {
775                            let line = if i > 0 && buf[i - 1] == b'\r' {
776                                // CRLF改行の処理
777                                let line = String::from_utf8_lossy(&buf[..i - 1]).to_string();
778                                unsafe { buf.advance_mut(i + 1); }
779                                line
780                            } else {
781                                // LF改行の処理
782                                let line = String::from_utf8_lossy(&buf[..i]).to_string();
783                                unsafe { buf.advance_mut(i + 1); }
784                                line
785                            };
786
787                            yield Ok(line);
788                        }
789                    },
790                    Err(e) => {
791                        yield Err(e);
792                        break;
793                    }
794                }
795            }
796
797            // 最後の行が改行で終わっていない場合も処理
798            if !buf.is_empty() {
799                let line = String::from_utf8_lossy(&buf).to_string();
800                yield Ok(line);
801            }
802        })
803    }
804
805    /// 関数リクエストを作成する
806    pub fn create_request<T: DeserializeOwned>(
807        &self,
808        function_name: &str,
809    ) -> FunctionRequest<'_, T> {
810        FunctionRequest {
811            client: self,
812            function_name: function_name.to_string(),
813            _response_type: std::marker::PhantomData,
814        }
815    }
816}
817
818#[cfg(test)]
819mod tests {
820    use super::*; // Import necessary items from parent module
821    use serde_json::json;
822    use wiremock::matchers::{body_json, header, method, path};
823    use wiremock::{Mock, MockServer, ResponseTemplate};
824
825    // Helper struct for testing
826    #[derive(Debug, Serialize, Deserialize, PartialEq)]
827    struct TestPayload {
828        message: String,
829    }
830
831    #[tokio::test]
832    async fn test_invoke() {
833        // TODO: モック実装を用いたテスト
834    }
835
836    // Test successful JSON invocation
837    #[tokio::test]
838    async fn test_invoke_json_success() {
839        // Arrange: Start mock server
840        let server = MockServer::start().await;
841        let mock_uri = server.uri();
842        let api_key = "test-key";
843        let function_name = "hello-world";
844
845        // Arrange: Prepare request and expected response
846        let request_body = json!({ "name": "Rust" });
847        let expected_response = TestPayload {
848            message: "Hello Rust".to_string(),
849        };
850
851        // Arrange: Mock the API endpoint
852        Mock::given(method("POST"))
853            .and(path(format!("/functions/v1/{}", function_name)))
854            .and(header("apikey", api_key))
855            .and(header(
856                "Authorization",
857                format!("Bearer {}", api_key).as_str(),
858            ))
859            .and(header("Content-Type", "application/json"))
860            .and(body_json(&request_body))
861            .respond_with(ResponseTemplate::new(200).set_body_json(&expected_response))
862            .mount(&server)
863            .await;
864
865        // Act: Create client and invoke function
866        let client = FunctionsClient::new(&mock_uri, api_key, reqwest::Client::new());
867        let result = client
868            .invoke_json::<TestPayload, Value>(function_name, Some(request_body))
869            .await;
870
871        // Assert: Check the result
872        assert!(result.is_ok());
873        let data = result.unwrap();
874        assert_eq!(data, expected_response);
875        server.verify().await;
876    }
877
878    // Test error response with details
879    #[tokio::test]
880    async fn test_invoke_json_error_with_details() {
881        // Arrange: Start mock server
882        let server = MockServer::start().await;
883        let mock_uri = server.uri();
884        let api_key = "test-key";
885        let function_name = "error-func";
886
887        // Arrange: Prepare request and error response
888        let request_body = json!({ "input": "invalid" });
889        let error_response_body = json!({
890            "message": "Something went wrong!",
891            "code": "FUNC_ERROR",
892            "details": { "reason": "Internal failure" }
893        });
894
895        // Arrange: Mock the API endpoint to return an error
896        Mock::given(method("POST"))
897            .and(path(format!("/functions/v1/{}", function_name)))
898            .and(header("apikey", api_key))
899            .and(header(
900                "Authorization",
901                format!("Bearer {}", api_key).as_str(),
902            ))
903            .and(body_json(&request_body))
904            .respond_with(
905                ResponseTemplate::new(500)
906                    .set_body_json(&error_response_body)
907                    .insert_header("Content-Type", "application/json"),
908            )
909            .mount(&server)
910            .await;
911
912        // Act: Create client and invoke function
913        let client = FunctionsClient::new(&mock_uri, api_key, reqwest::Client::new());
914        // Use a placeholder type like Value for the expected success type T,
915        // as we expect an error anyway.
916        let result = client
917            .invoke_json::<Value, Value>(function_name, Some(request_body))
918            .await;
919
920        // Assert: Check the error result
921        assert!(result.is_err());
922        match result.err().unwrap() {
923            FunctionsError::FunctionError {
924                message,
925                status,
926                details,
927            } => {
928                assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
929                assert_eq!(message, "Something went wrong!");
930                assert!(details.is_some());
931                let details_unwrapped = details.unwrap();
932                assert_eq!(
933                    details_unwrapped.message,
934                    Some("Something went wrong!".to_string())
935                );
936                assert_eq!(details_unwrapped.code, Some("FUNC_ERROR".to_string()));
937                assert!(details_unwrapped.details.is_some());
938                assert_eq!(
939                    details_unwrapped.details.unwrap(),
940                    json!({ "reason": "Internal failure" })
941                );
942            }
943            _ => panic!("Expected FunctionError, got different error type"),
944        }
945        server.verify().await;
946    }
947
948    // Test successful text invocation
949    #[tokio::test]
950    async fn test_invoke_text_success() {
951        // Arrange: Start mock server
952        let server = MockServer::start().await;
953        let mock_uri = server.uri();
954        let api_key = "test-key";
955        let function_name = "plain-text-func";
956
957        // Arrange: Prepare request and expected response
958        let request_body = json!({ "format": "text" });
959        let expected_response_text = "This is a plain text response.";
960
961        // Arrange: Mock the API endpoint
962        Mock::given(method("POST"))
963            .and(path(format!("/functions/v1/{}", function_name)))
964            .and(header("apikey", api_key))
965            .and(header(
966                "Authorization",
967                format!("Bearer {}", api_key).as_str(),
968            ))
969            .and(header("Content-Type", "application/json")) // Default for invoke_text wrapper
970            .and(body_json(&request_body))
971            .respond_with(
972                ResponseTemplate::new(200)
973                    .set_body_string(expected_response_text)
974                    .insert_header("Content-Type", "text/plain"), // Server responds with text
975            )
976            .mount(&server)
977            .await;
978
979        // Act: Create client and invoke function
980        let client = FunctionsClient::new(&mock_uri, api_key, reqwest::Client::new());
981        let result = client
982            .invoke_text::<Value>(function_name, Some(request_body)) // Body type is generic
983            .await;
984
985        // Assert: Check the result
986        assert!(result.is_ok());
987        let data = result.unwrap();
988        assert_eq!(data, expected_response_text);
989        server.verify().await;
990    }
991}