Files
momentry_core/docs/RUST_DEVELOPMENT.md
accusys f699d313c1 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
2026-03-25 14:53:41 +08:00

652 lines
13 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.
# 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
```