# HttpClient API リファレンス ## 概要 `HttpClient`は、AT Protocol APIへの統一されたHTTPクライアントインターフェースです。認証、エラーハンドリング、リクエスト管理を自動化します。 ## 基本的な使用方法 ```rust use crate::http_client::HttpClient; let client = HttpClient::new(); // または let client = HttpClient::default(); ``` ## APIメソッド ### 認証付きリクエスト #### get_with_auth AT Protocolの認証が必要なGETリクエストを実行します。 ```rust pub async fn get_with_auth(&self, url: &str) -> Result ``` **パラメータ:** - `url`: リクエスト先のURL **戻り値:** - `Ok(String)`: レスポンスボディ(文字列) - `Err(Error)`: リクエストエラー **使用例:** ```rust let client = HttpClient::new(); let url = "https://bsky.social/xrpc/app.bsky.actor.getProfile?actor=user.bsky.social"; match client.get_with_auth(&url).await { Ok(response) => println!("Response: {}", response), Err(e) => eprintln!("Error: {}", e), } ``` #### post_json_with_auth AT Protocolの認証が必要なPOSTリクエスト(JSON)を実行します。 ```rust pub async fn post_json_with_auth(&self, url: &str, json: &T) -> Result ``` **パラメータ:** - `url`: リクエスト先のURL - `json`: シリアライズ可能なJSONデータ **戻り値:** - `Ok(String)`: レスポンスボディ(文字列) - `Err(Error)`: リクエストエラー **使用例:** ```rust use serde_json::json; let client = HttpClient::new(); let url = "https://bsky.social/xrpc/com.atproto.repo.createRecord"; let payload = json!({ "repo": "user.bsky.social", "collection": "app.bsky.feed.post", "record": { "text": "Hello, World!", "createdAt": "2025-01-01T00:00:00Z" } }); match client.post_json_with_auth(&url, &payload).await { Ok(response) => println!("Post created: {}", response), Err(e) => eprintln!("Error: {}", e), } ``` #### delete_with_auth AT Protocolの認証が必要なDELETEリクエストを実行します。 ```rust pub async fn delete_with_auth(&self, url: &str) -> Result ``` **パラメータ:** - `url`: リクエスト先のURL **戻り値:** - `Ok(String)`: レスポンスボディ(文字列) - `Err(Error)`: リクエストエラー **使用例:** ```rust let client = HttpClient::new(); let url = "https://bsky.social/xrpc/com.atproto.repo.deleteRecord"; match client.delete_with_auth(&url).await { Ok(response) => println!("Deleted: {}", response), Err(e) => eprintln!("Error: {}", e), } ``` ### 認証なしリクエスト #### get 認証なしのGETリクエストを実行します。 ```rust pub async fn get(&self, url: &str) -> Result ``` **使用例:** ```rust let client = HttpClient::new(); let url = "https://public-api.example.com/data"; match client.get(&url).await { Ok(response) => println!("Response: {}", response), Err(e) => eprintln!("Error: {}", e), } ``` #### post_json 認証なしのPOSTリクエスト(JSON)を実行します。ログイン処理などで使用。 ```rust pub async fn post_json(&self, url: &str, json: &T) -> Result ``` **使用例:** ```rust use serde_json::json; let client = HttpClient::new(); let url = "https://bsky.social/xrpc/com.atproto.session.create"; let credentials = json!({ "identifier": "user.bsky.social", "password": "password" }); match client.post_json(&url, &credentials).await { Ok(response) => println!("Login successful: {}", response), Err(e) => eprintln!("Login failed: {}", e), } ``` ### カスタムヘッダー付きリクエスト #### post_with_headers カスタムヘッダーを指定したPOSTリクエストを実行します。 ```rust pub async fn post_with_headers( &self, url: &str, json: &T, headers: Vec<(&str, &str)> ) -> Result ``` **パラメータ:** - `url`: リクエスト先のURL - `json`: シリアライズ可能なJSONデータ - `headers`: ヘッダーのキーと値のペアのベクター **使用例:** ```rust use serde_json::json; let client = HttpClient::new(); let url = "https://bsky.social/xrpc/com.atproto.session.refresh"; let refresh_token = "refresh_token_value"; let auth_header = format!("Bearer {}", refresh_token); let headers = vec![("Authorization", auth_header.as_str())]; let empty_json = json!({}); match client.post_with_headers(&url, &empty_json, headers).await { Ok(response) => println!("Token refreshed: {}", response), Err(e) => eprintln!("Refresh failed: {}", e), } ``` ## 内部実装詳細 ### 認証処理 認証付きメソッドは自動的に以下を実行します: 1. `data_refresh(&"access")`でアクセストークンを取得 2. `Authorization: Bearer {token}`ヘッダーを自動追加 3. リクエストを実行 ### エラーハンドリング - ネットワークエラー - HTTPステータスエラー - JSON解析エラー - タイムアウトエラー すべて`reqwest::Error`として統一されて返されます。 ## 使用上の注意 ### 1. トークン管理 - アクセストークンは自動的に取得されます - トークンの有効期限切れは呼び出し元で処理する必要があります - リフレッシュトークンは`post_with_headers`で手動設定 ### 2. エラー処理パターン ```rust // 推奨パターン match client.get_with_auth(&url).await { Ok(response) => { // 成功処理 response }, Err(e) => { eprintln!("API call failed: {}", e); "err".to_string() // 既存コードとの互換性 } } ``` ### 3. パフォーマンス - `HttpClient`の作成は軽量な操作です - 内部でrequwestクライアントを再利用しています - 複数のリクエストで同じインスタンスを使い回すことも可能 ### 4. デバッグ リクエスト/レスポンスの詳細をログ出力したい場合: ```rust // HttpClientモジュール内で適宜printlnを追加 println!("Request URL: {}", url); println!("Response: {}", response); ``` ## 実装例:新しいAPI呼び出しモジュール ```rust use crate::http_client::HttpClient; use crate::{data_toml, url}; use serde_json::json; use iso8601_timestamp::Timestamp; pub async fn create_custom_record(data: String) -> String { let did = data_toml(&"did"); let handle = data_toml(&"handle"); let url = url(&"record_create"); let timestamp = Timestamp::now_utc().to_string(); let payload = json!({ "repo": handle, "did": did, "collection": "app.bsky.custom.record", "record": { "data": data, "createdAt": timestamp } }); let client = HttpClient::new(); match client.post_json_with_auth(&url, &payload).await { Ok(response) => response, Err(e) => { eprintln!("Error creating custom record: {}", e); "err".to_string() } } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_create_custom_record() { let result = create_custom_record("test data".to_string()).await; assert_ne!(result, "err"); } } ``` ## 移行ガイド ### 既存コードからの移行 #### Before (旧実装) ```rust extern crate reqwest; use crate::data_refresh; pub async fn old_request() -> String { let token = data_refresh(&"access"); let client = reqwest::Client::new(); let res = client .post(url) .json(&data) .header("Authorization", "Bearer ".to_owned() + &token) .send() .await .unwrap() .text() .await .unwrap(); res } ``` #### After (新実装) ```rust use crate::http_client::HttpClient; pub async fn new_request() -> String { let client = HttpClient::new(); match client.post_json_with_auth(&url, &data).await { Ok(response) => response, Err(e) => { eprintln!("Error in request: {}", e); "err".to_string() } } } ``` ### チェックリスト - [ ] `extern crate reqwest;`を削除 - [ ] `use crate::http_client::HttpClient;`を追加 - [ ] `data_refresh(&"access")`の手動呼び出しを削除 - [ ] `reqwest::Client::new()`を`HttpClient::new()`に置換 - [ ] `.unwrap()`を適切な`match`文に置換 - [ ] エラーメッセージの追加