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

8.3 KiB
Raw Permalink Blame History

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: リクエスト先のURL
  • json: シリアライズ可能な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: リクエスト先のURL
  • json: シリアライズ可能な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),
}

内部実装詳細

認証処理

認証付きメソッドは自動的に以下を実行します:

  1. data_refresh(&"access")でアクセストークンを取得
  2. Authorization: Bearer {token}ヘッダーを自動追加
  3. リクエストを実行

エラーハンドリング

  • ネットワークエラー
  • 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文に置換
  • エラーメッセージの追加