Claude 5a5a47e710 docs: add missing Japanese translations to complete zh-CN parity (ja-JP)
Add remaining files to match zh-CN documentation structure:
- hooks/README.md — hooks architecture and customization guide
- examples/ — 8 project CLAUDE.md templates (general, user, django, go, harmonyos, laravel, rust, saas-nextjs)
- CHANGELOG.md — version history
- the-openclaw-guide.md — OpenClaw guide (471 lines)

Total: 11 files, 2362 insertions
ja-JP now has full parity with zh-CN directory structure.
2026-05-17 02:31:40 -04:00

9.4 KiB
Raw Blame History

Rust API サービス — プロジェクト CLAUDE.md

Axum、PostgreSQL、Dockerを使用したRust APIサービスの実世界サンプル。 これをプロジェクトのルートにコピーしてサービスに合わせてカスタマイズしてください。

プロジェクト概要

スタック: Rust 1.78+, AxumWebフレームワーク, SQLx非同期データベース, PostgreSQL, Tokio非同期ランタイム, Docker

アーキテクチャ: ハンドラー → サービス → リポジトリの分離を持つレイヤードアーキテクチャ。HTTPにAxum、コンパイル時に型チェックされたSQLにSQLx、横断的関心事にTowerミドルウェアを使用。

重要なルール

Rust の規約

  • ライブラリエラーには thiserror を使用し、anyhow はバイナリクレートまたはテストのみ
  • 本番コードで .unwrap().expect() を使用しない — ? でエラーを伝播させる
  • 関数パラメーターでは String より &str を優先し、所有権が移転するときは String を返す
  • #![deny(clippy::all, clippy::pedantic)]clippy を使用 — すべての警告を修正する
  • すべての公開型に Debug を導出し、ClonePartialEq は必要な場合のみ導出する
  • // SAFETY: コメントによる正当化がない限り unsafe ブロックは使用しない

データベース

  • すべてのクエリはSQLxの query! または query_as! マクロを使用 — スキーマに対してコンパイル時に検証される
  • migrations/ のマイグレーションは sqlx migrate を使用 — データベースを直接変更しない
  • 共有状態として sqlx::Pool<Postgres> を使用 — リクエストごとにコネクションを作成しない
  • すべてのクエリはパラメータ化プレースホルダー($1, $2)を使用 — 文字列フォーマットは禁止
// 悪い例: 文字列補間SQLインジェクションリスク
let q = format!("SELECT * FROM users WHERE id = '{}'", id);

// 良い例: パラメータ化クエリ、コンパイル時チェック済み
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
    .fetch_optional(&pool)
    .await?;

エラーハンドリング

  • thiserror でモジュールごとにドメインエラーenumを定義する
  • IntoResponse でエラーをHTTPレスポンスにマップ — 内部詳細を公開しない
  • 構造化ロギングには tracing を使用 — println!eprintln! は使用しない
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("Resource not found")]
    NotFound,
    #[error("Validation failed: {0}")]
    Validation(String),
    #[error("Unauthorized")]
    Unauthorized,
    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            Self::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
            Self::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            Self::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
            Self::Internal(err) => {
                tracing::error!(?err, "internal error");
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into())
            }
        };
        (status, Json(json!({ "error": message }))).into_response()
    }
}

テスト

  • 各ソースファイル内の #[cfg(test)] モジュールにユニットテストを記述する
  • tests/ ディレクトリに実際のPostgreSQLTestcontainersまたはDockerを使用した統合テストを記述する
  • 自動マイグレーションとロールバック付きのデータベーステストには #[sqlx::test] を使用する
  • 外部サービスのモックには mockall または wiremock を使用する

コードスタイル

  • 最大行長: 100文字rustfmtにより強制
  • インポートのグループ化: std、外部クレート、crate/super — 空白行で区切る
  • モジュール: モジュールごとに1ファイル、mod.rs は再エクスポートのみ
  • 型: PascalCase、関数/変数: snake_case、定数: UPPER_SNAKE_CASE

ファイル構成

src/
  main.rs              # エントリーポイント、サーバーセットアップ、グレースフルシャットダウン
  lib.rs               # 統合テスト用の再エクスポート
  config.rs            # envyまたはfigmentによる環境設定
  router.rs            # すべてのルートを持つAxumルーター
  middleware/
    auth.rs            # JWT抽出とバリデーション
    logging.rs         # リクエスト/レスポンスのトレーシング
  handlers/
    mod.rs             # ルートハンドラー(薄く — サービスに委任)
    users.rs
    orders.rs
  services/
    mod.rs             # ビジネスロジック
    users.rs
    orders.rs
  repositories/
    mod.rs             # データベースアクセスSQLxクエリ
    users.rs
    orders.rs
  domain/
    mod.rs             # ドメイン型、エラーenum
    user.rs
    order.rs
migrations/
  001_create_users.sql
  002_create_orders.sql
tests/
  common/mod.rs        # 共有テストヘルパー、テストサーバーセットアップ
  api_users.rs         # ユーザーエンドポイントの統合テスト
  api_orders.rs        # 注文エンドポイントの統合テスト

主要なパターン

ハンドラー(薄く)

async fn create_user(
    State(ctx): State<AppState>,
    Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
    let user = ctx.user_service.create(payload).await?;
    Ok((StatusCode::CREATED, Json(UserResponse::from(user))))
}

サービス(ビジネスロジック)

impl UserService {
    pub async fn create(&self, req: CreateUserRequest) -> Result<User, AppError> {
        if self.repo.find_by_email(&req.email).await?.is_some() {
            return Err(AppError::Validation("Email already registered".into()));
        }

        let password_hash = hash_password(&req.password)?;
        let user = self.repo.insert(&req.email, &req.name, &password_hash).await?;

        Ok(user)
    }
}

リポジトリ(データアクセス)

impl UserRepository {
    pub async fn find_by_email(&self, email: &str) -> Result<Option<User>, sqlx::Error> {
        sqlx::query_as!(User, "SELECT * FROM users WHERE email = $1", email)
            .fetch_optional(&self.pool)
            .await
    }

    pub async fn insert(
        &self,
        email: &str,
        name: &str,
        password_hash: &str,
    ) -> Result<User, sqlx::Error> {
        sqlx::query_as!(
            User,
            r#"INSERT INTO users (email, name, password_hash)
               VALUES ($1, $2, $3) RETURNING *"#,
            email, name, password_hash,
        )
        .fetch_one(&self.pool)
        .await
    }
}

統合テスト

#[tokio::test]
async fn test_create_user() {
    let app = spawn_test_app().await;

    let response = app
        .client
        .post(&format!("{}/api/v1/users", app.address))
        .json(&json!({
            "email": "alice@example.com",
            "name": "Alice",
            "password": "securepassword123"
        }))
        .send()
        .await
        .expect("Failed to send request");

    assert_eq!(response.status(), StatusCode::CREATED);
    let body: serde_json::Value = response.json().await.unwrap();
    assert_eq!(body["email"], "alice@example.com");
}

#[tokio::test]
async fn test_create_user_duplicate_email() {
    let app = spawn_test_app().await;
    // 最初のユーザーを作成
    create_test_user(&app, "alice@example.com").await;
    // 重複を試みる
    let response = create_user_request(&app, "alice@example.com").await;
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}

環境変数

# サーバー
HOST=0.0.0.0
PORT=8080
RUST_LOG=info,tower_http=debug

# データベース
DATABASE_URL=postgres://user:pass@localhost:5432/myapp

# 認証
JWT_SECRET=your-secret-key-min-32-chars
JWT_EXPIRY_HOURS=24

# 任意
CORS_ALLOWED_ORIGINS=http://localhost:3000

テスト戦略

# すべてのテストを実行
cargo test

# 出力付きで実行
cargo test -- --nocapture

# 特定のテストモジュールを実行
cargo test api_users

# カバレッジチェックcargo-llvm-covが必要
cargo llvm-cov --html
open target/llvm-cov/html/index.html

# リント
cargo clippy -- -D warnings

# フォーマットチェック
cargo fmt -- --check

ECCワークフロー

# 計画
/plan "Add order fulfillment with Stripe payment"

# TDDによる開発
/tdd                    # cargo test ベースのTDDワークフロー

# レビュー
/code-review            # Rust固有のコードレビュー
/security-scan          # 依存関係監査 + unsafeスキャン

# 検証
/verify                 # ビルド、clippy、テスト、セキュリティスキャン

Git ワークフロー

  • feat: 新機能、fix: バグ修正、refactor: コード変更
  • main からフィーチャーブランチを切り、PRが必要
  • CI: cargo fmt --check, cargo clippy, cargo test, cargo audit
  • デプロイ: scratch または distroless ベースのDockerマルチステージビルド