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>
334 lines
8.3 KiB
Markdown
334 lines
8.3 KiB
Markdown
# 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<String, Error>
|
||
```
|
||
|
||
**パラメータ:**
|
||
- `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<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
|
||
```
|
||
|
||
**パラメータ:**
|
||
- `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<String, Error>
|
||
```
|
||
|
||
**パラメータ:**
|
||
- `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<String, Error>
|
||
```
|
||
|
||
**使用例:**
|
||
```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<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
|
||
```
|
||
|
||
**使用例:**
|
||
```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<T: Serialize>(
|
||
&self,
|
||
url: &str,
|
||
json: &T,
|
||
headers: Vec<(&str, &str)>
|
||
) -> Result<String, Error>
|
||
```
|
||
|
||
**パラメータ:**
|
||
- `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`文に置換
|
||
- [ ] エラーメッセージの追加 |