Major refactoring: HTTP client unification and project restructuring
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 13m53s
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>
This commit is contained in:
334
docs/http-client-api.md
Normal file
334
docs/http-client-api.md
Normal file
@ -0,0 +1,334 @@
|
||||
# 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`文に置換
|
||||
- [ ] エラーメッセージの追加
|
Reference in New Issue
Block a user