1
0
bot/docs/http-client-api.md
syui a17d2c9d66
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 13m53s
Major refactoring: HTTP client unification and project restructuring
## 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>
2025-06-06 23:47:12 +09:00

334 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`文に置換
- [ ] エラーメッセージの追加