Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 13m53s
## HTTP Client Refactoring - Create unified HttpClient module (src/http_client.rs) - Refactor 24 files to use shared HTTP client - Replace .unwrap() with proper error handling - Eliminate code duplication in HTTP requests ## Project Restructuring - Rename package: ai → aibot - Add dual binary support: aibot (main) + ai (compatibility alias) - Migrate config directory: ~/.config/ai/ → ~/.config/syui/ai/bot/ - Implement backward compatibility with automatic migration ## Testing Infrastructure - Add unit tests for HttpClient - Create test infrastructure with cargo-make - Add test commands: test, test-quick, test-verbose ## Documentation - Complete migration guide with step-by-step instructions - Updated development guide with new structure - HTTP client API reference documentation - Comprehensive refactoring summary ## Files Changed - Modified: 24 source files (HTTP client integration) - Added: src/http_client.rs, src/alias.rs, src/tests/ - Added: 5 documentation files in docs/ - Added: migration setup script 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
8.3 KiB
8.3 KiB
HttpClient API リファレンス
概要
HttpClient
は、AT Protocol APIへの統一されたHTTPクライアントインターフェースです。認証、エラーハンドリング、リクエスト管理を自動化します。
基本的な使用方法
use crate::http_client::HttpClient;
let client = HttpClient::new();
// または
let client = HttpClient::default();
APIメソッド
認証付きリクエスト
get_with_auth
AT Protocolの認証が必要なGETリクエストを実行します。
pub async fn get_with_auth(&self, url: &str) -> Result<String, Error>
パラメータ:
url
: リクエスト先のURL
戻り値:
Ok(String)
: レスポンスボディ(文字列)Err(Error)
: リクエストエラー
使用例:
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)を実行します。
pub async fn post_json_with_auth<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
パラメータ:
url
: リクエスト先のURLjson
: シリアライズ可能なJSONデータ
戻り値:
Ok(String)
: レスポンスボディ(文字列)Err(Error)
: リクエストエラー
使用例:
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リクエストを実行します。
pub async fn delete_with_auth(&self, url: &str) -> Result<String, Error>
パラメータ:
url
: リクエスト先のURL
戻り値:
Ok(String)
: レスポンスボディ(文字列)Err(Error)
: リクエストエラー
使用例:
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リクエストを実行します。
pub async fn get(&self, url: &str) -> Result<String, Error>
使用例:
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)を実行します。ログイン処理などで使用。
pub async fn post_json<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
使用例:
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リクエストを実行します。
pub async fn post_with_headers<T: Serialize>(
&self,
url: &str,
json: &T,
headers: Vec<(&str, &str)>
) -> Result<String, Error>
パラメータ:
url
: リクエスト先のURLjson
: シリアライズ可能なJSONデータheaders
: ヘッダーのキーと値のペアのベクター
使用例:
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),
}
内部実装詳細
認証処理
認証付きメソッドは自動的に以下を実行します:
data_refresh(&"access")
でアクセストークンを取得Authorization: Bearer {token}
ヘッダーを自動追加- リクエストを実行
エラーハンドリング
- ネットワークエラー
- HTTPステータスエラー
- JSON解析エラー
- タイムアウトエラー
すべてreqwest::Error
として統一されて返されます。
使用上の注意
1. トークン管理
- アクセストークンは自動的に取得されます
- トークンの有効期限切れは呼び出し元で処理する必要があります
- リフレッシュトークンは
post_with_headers
で手動設定
2. エラー処理パターン
// 推奨パターン
match client.get_with_auth(&url).await {
Ok(response) => {
// 成功処理
response
},
Err(e) => {
eprintln!("API call failed: {}", e);
"err".to_string() // 既存コードとの互換性
}
}
3. パフォーマンス
HttpClient
の作成は軽量な操作です- 内部でrequwestクライアントを再利用しています
- 複数のリクエストで同じインスタンスを使い回すことも可能
4. デバッグ
リクエスト/レスポンスの詳細をログ出力したい場合:
// HttpClientモジュール内で適宜printlnを追加
println!("Request URL: {}", url);
println!("Response: {}", response);
実装例:新しいAPI呼び出しモジュール
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 (旧実装)
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 (新実装)
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
文に置換- エラーメッセージの追加