docs: Add Rust development specification

- Project structure and module design
- Code style and naming conventions
- Error handling patterns
- Async programming guidelines
- External process integration (Python scripts)
- Testing strategy
- Logging with tracing
- Performance optimization
- Security considerations
- CLI command design
- Version control guidelines
This commit is contained in:
accusys
2026-03-16 15:17:31 +08:00
parent de14bd6afa
commit ea2bbb9fd9

651
docs/RUST_DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,651 @@
# Rust 開發規範 - Momentry Core
本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。
## 1. 專案結構
### 1.1 目錄架構
```
src/
├── main.rs # CLI 入口點
├── lib.rs # 函式庫導出
├── cli/
│ ├── mod.rs
│ └── commands/ # CLI 命令模組
├── core/
│ ├── mod.rs
│ ├── chunk/ # 影片分段邏輯
│ │ ├── mod.rs
│ │ ├── splitter.rs
│ │ └── types.rs
│ ├── db/ # 資料庫抽象層
│ │ ├── mod.rs
│ │ ├── postgres_db.rs
│ │ ├── mongodb_db.rs
│ │ ├── redis_db.rs
│ │ └── qdrant_db.rs
│ ├── processor/ # 影片處理器
│ │ ├── mod.rs
│ │ ├── asr.rs # 語音識別
│ │ ├── asrx.rs # 說話者分離
│ │ ├── ocr.rs # 文字辨識
│ │ ├── yolo.rs # 物件偵測
│ │ ├── face.rs # 人臉偵測
│ │ └── pose.rs # 姿態估計
│ ├── embedding/ # 向量嵌入
│ ├── probe/ # ffprobe 整合
│ ├── storage/ # 檔案管理
│ └── thumbnail/ # 縮圖生成
├── api/ # HTTP API
│ ├── mod.rs
│ └── routes/
├── player/ # 影片播放
└── watcher/ # 檔案監控
```
### 1.2 模組設計原則
- **單一職責**: 每個模組專注於一項功能
- **介面抽象**: 使用 trait 定義資料庫、操作器等介面
- **依賴注入**: 透過建構函式注入依賴
```rust
pub trait VideoProcessor: Send + Sync {
async fn process(&self, video_path: &str) -> Result<ProcessResult>;
}
```
## 2. 程式碼風格
### 2.1 命名規範
| 類型 | 規範 | 範例 |
|------|------|------|
| 結構體/列舉 | PascalCase | `VideoRecord`, `ChunkType` |
| 函式/變數 | snake_case | `get_video_by_uuid` |
| Trait | PascalCase + er 尾碼 | `Database`, `ChunkStore` |
| 檔案 | snake_case | `postgres_db.rs` |
| 常量 | SCREAMING_SNAKE_CASE | `MAX_CHUNK_SIZE` |
| 模組 | snake_case | `chunk`, `processor` |
### 2.2 匯入順序
```rust
// 1. 標準庫
use std::path::Path;
use std::process::Command;
// 2. 外部庫
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::fs;
// 3. 內部模組
use crate::core::chunk::Chunk;
use crate::core::db::PostgresDb;
```
### 2.3 行寬與格式
- 最大行寬: 100 字元
- 使用 4 空格縮排
- 啟用 clippy 與 fmt
```bash
# 格式化
cargo fmt
# 檢查格式
cargo fmt -- --check
# Lint
cargo clippy --all-features
```
## 3. 錯誤處理
### 3.1 錯誤類型選擇
| 情境 | 錯誤類型 | 原因 |
|------|----------|------|
| 應用程式 | `anyhow::Result<T>` | 提供靈活的錯誤傳播 |
| 函式庫 | `thiserror` | 定義明確的錯誤類型 |
| API 錯誤 | 自定義 Error enum | 提供客戶端錯誤碼 |
### 3.2 錯誤處理範例
```rust
use anyhow::{Context, Result, bail};
fn process_video(video_path: &str) -> Result<VideoMetadata> {
// 使用 context 提供錯誤上下文
let output = Command::new("ffprobe")
.args(["-v", "quiet", "-print_format", "json", "-show_format", video_path])
.output()
.context("Failed to run ffprobe")?;
// 使用 bail 進行早期返回
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("ffprobe failed: {}", stderr);
}
// 解析輸出
let metadata: Metadata = serde_json::from_slice(&output.stdout)
.context("Failed to parse ffprobe output")?;
Ok(metadata)
}
```
### 3.3 自定義錯誤 (適用於函式庫)
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum VideoError {
#[error("Video not found: {0}")]
NotFound(String),
#[error("Invalid codec: {0}")]
InvalidCodec(String),
#[error("Processing failed: {0}")]
ProcessingError(#[from] std::io::Error),
}
```
## 4. 异步編程
### 4.1 Tokio 配置
```rust
// Cargo.toml
tokio = { version = "1", features = ["full"] }
```
### 4.2 Async Trait
```rust
use async_trait::async_trait;
#[async_trait]
pub trait Database: Send + Sync {
async fn init() -> Result<Self>
where Self: Sized;
async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
async fn store_chunk(&self, chunk: &Chunk) -> Result<()>;
}
```
### 4.3 避免常見陷阱
```rust
// ❌ 錯誤: 在同步上下文中調用 async 函式
fn bad_example() {
let result = db.get_video("xxx"); // 編譯錯誤
}
// ✅ 正確: 使用 #[tokio::main]
#[tokio::main]
async fn main() {
let result = db.get_video("xxx").await;
}
// ❌ 錯誤: 阻塞執行緒池
async fn bad_practice() {
let data = std::fs::read_to_string("file.txt").unwrap(); // 阻塞
}
// ✅ 正確: 使用 tokio::fs
async fn good_practice() {
let data = tokio::fs::read_to_string("file.txt").await.unwrap();
}
```
## 5. 外部程序整合
### 5.1 Python 腳本呼叫
當需要使用 Python 生態系工具 (如 faster-whisper, YOLO) 時:
```rust
pub async fn process_asr(video_path: &str, output_path: &str) -> Result<AsrResult> {
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("scripts")
.join("asr_processor.py");
// 使用 venv 中的 Python確保版本隔離
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("venv")
.join("bin")
.join("python");
// 執行腳本
let output = Command::new(venv_python)
.arg(script_path)
.arg(video_path)
.arg(output_path)
.output()
.context("Failed to run ASR processor")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("ASR failed: {}", stderr);
}
// 讀取輸出
let json_str = std::fs::read_to_string(output_path)
.context("Failed to read ASR output")?;
let result: AsrResult = serde_json::from_str(&json_str)
.context("Failed to parse ASR output")?;
Ok(result)
}
```
### 5.2 進度回報
透過 stderr 回報進度,供 Rust 端解析:
```python
# Python 腳本
import sys
print(f"ASR_START", file=sys.stderr)
print(f"ASR_LANGUAGE:{detected_lang}", file=sys.stderr)
print(f"ASR_PROGRESS:{count}", file=sys.stderr)
print(f"ASR_COMPLETE:{total}", file=sys.stderr)
```
```rust
// Rust 端解析
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stderr.lines() {
if line.starts_with("ASR_PROGRESS:") {
let count = line.trim_start_matches("ASR_PROGRESS:");
println!("[ASR] Processed {} segments...", count);
}
}
```
## 6. 測試策略
### 6.1 單元測試
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_creation() {
let chunk = Chunk::new(
"test-uuid".to_string(),
0,
ChunkType::Sentence,
0.0,
10.0,
serde_json::json!({"text": "Hello"}),
);
assert_eq!(chunk.uuid, "test-uuid");
assert_eq!(chunk.chunk_type, ChunkType::Sentence);
}
}
```
### 6.2 整合測試
```rust
#[cfg(test)]
mod integration {
use super::*;
#[tokio::test]
async fn test_database_connection() {
let db = PostgresDb::init().await.unwrap();
let videos = db.list_videos().await.unwrap();
assert!(videos.len() >= 0);
}
}
```
### 6.3 測試資料
- 使用測試資料庫隔離測試環境
- 避免在測試中使用真實敏感資料
- 使用 mock 物件模擬外部依賴
```rust
#[cfg(test)]
mod mocks {
pub struct MockVideoProcessor {
pub result: AsrResult,
}
impl VideoProcessor for MockVideoProcessor {
async fn process(&self, _video_path: &str) -> Result<AsrResult> {
Ok(self.result.clone())
}
}
}
```
## 7. 日誌與監控
### 7.1 日誌規範
- **使用 tracing**: 不要使用 `println!`
- **結構化日誌**: 使用訊息 + 欄位
```rust
use tracing::{info, warn, error};
fn process_video(uuid: &str) -> Result<()> {
info!(uuid = uuid, "Starting video processing");
match do_processing(uuid) {
Ok(_) => info!(uuid = uuid, "Processing completed"),
Err(e) => {
error!(uuid = uuid, error = %e, "Processing failed");
return Err(e);
}
}
}
```
### 7.2 初始化日誌
```rust
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
fn init_logging() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "momentry_core=info,tokio=warn".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}
```
## 8. 性能優化
### 8.1 避免拷貝
```rust
// ❌ 拷貝
let data = record.clone();
// ✅ 引用
fn process(data: &Data) { }
// ✅ 或使用 Arc 共用
use std::sync::Arc;
let shared = Arc::new(data);
```
### 8.2 批量操作
```rust
// ❌ 逐筆插入
for item in items {
db.insert(&item).await?;
}
// ✅ 批量插入
db.insert_batch(&items).await?;
```
### 8.3 連線池
```rust
// 使用 sqlx 連線池
let pool = SqlxPool::connect(&DATABASE_URL).await?;
let db = PostgresDb::new(pool);
```
## 9. 安全考量
### 9.1 敏感資訊
- **不要**將密碼、API Key 寫入程式碼
- 使用環境變數或設定檔
- .env 檔案加入 .gitignore
```rust
// ❌ 硬編碼密碼
let password = "secret123";
// ✅ 使用環境變數
let password = std::env::var("DATABASE_PASSWORD")
.context("DATABASE_PASSWORD must be set")?;
```
### 9.2 命令注入
```rust
// ❌ 危險: 直接使用使用者輸入
let cmd = format!("ffprobe {}", user_input);
// ✅ 安全: 使用參數化
Command::new("ffprobe")
.arg(user_input) // 自動轉義
.output();
```
## 10. 文件編寫
### 10.1 結構體/函式文件
```rust
/// 代表一個影片記錄
///
/// # Fields
/// * `id` - 資料庫 ID
/// * `uuid` - 唯一識別碼
/// * `duration` - 影片時長 (秒)
///
/// # Example
/// ```
/// let video = VideoRecord {
/// id: 1,
/// uuid: "abc123".to_string(),
/// duration: 120.5,
/// };
/// ```
pub struct VideoRecord {
pub id: i64,
pub uuid: String,
pub duration: f64,
}
```
### 10.2 API 文件
```rust
/// 取得影片記錄
///
/// # Arguments
/// * `uuid` - 影片的 UUID
///
/// # Returns
/// * `Ok(Some(VideoRecord))` - 找到影片
/// * `Ok(None)` - 影片不存在
/// * `Err` - 資料庫錯誤
///
/// # Errors
/// 如果資料庫連線失敗,返回資料庫錯誤
pub async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
```
## 11. CLI 命令設計
### 11.1 命令結構
使用 clap derive
```rust
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "momentry")]
#[command(about = "Digital asset management system")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Register a video file
Register {
/// Path to video file
path: String,
},
/// Process video
Process {
/// UUID or path
target: String,
},
/// Generate chunks
Chunk {
/// Video UUID
uuid: String,
},
}
```
### 11.2 錯誤處理
```rust
match &cli.command {
Commands::Register { path } => {
if !Path::new(path).exists() {
eprintln!("Error: File not found: {}", path);
std::process::exit(1);
}
// ...
}
}
```
## 12. 依賴管理
### 12.1 版本約束
```toml
# Cargo.toml
[dependencies]
anyhow = "1.0" # 精確版本
tokio = { version = "1", features = ["full"] } # 範圍版本
serde = "1.0" # 精確版本
```
### 12.2 避免依賴地獄
- 審查依賴數量
- 優先使用標準庫
- 選擇維護良好的套件
## 13. 建構與部署
### 13.1 建構命令
```bash
# 開發建構
cargo build
# 發布建構
cargo build --release
# 單一二進制
cargo build --bin momentry
```
### 13.2 檢查清單
```bash
# 格式化
cargo fmt -- --check
# Lint
cargo clippy --all-features -- -D warnings
# 類型檢查
cargo check --all-features
# 測試
cargo test
```
## 14. 版本控制
### 14.1 提交訊息規範
```
<type>(<scope>): <subject>
<body>
<footer>
```
類型:
- `feat`: 新功能
- `fix`: 錯誤修復
- `docs`: 文件變更
- `style`: 格式調整
- `refactor`: 重構
- `test`: 測試變更
- `chore`: 建構/工具變更
範例:
```
feat(processor): Add ASR progress reporting
- Add stderr parsing for progress updates
- Support ASR_START, ASR_PROGRESS, ASR_COMPLETE markers
Closes #123
```
### 14.2 分支策略
- `main`: 穩定版本
- `feature/*`: 新功能開發
- `fix/*`: 錯誤修復
- `refactor/*`: 重構
---
## 附錄: 快速參考
### 建構
```bash
cargo build --release
cargo run -- --help
```
### 品質檢查
```bash
cargo fmt -- --check
cargo clippy --all-features
cargo check --all-features
cargo test
```
### 依賴
```bash
cargo add <package>
cargo tree
cargo outdated
```