# AGENTS.md
Rust 库 crate:实现 PostgreSQL wire protocol v3,支持 SCRAM-SHA-256/MD5/明文认证。同步 API,通过全局静态 `DB_POOL` 实现连接池。错误类型:`PgsqlError` 枚举。
## 命令
```bash
# 构建
cargo build
# 格式化(检查 / 修复)
cargo fmt --check
cargo fmt
# Lint(完成前必须通过)
cargo clippy --all-targets --all-features -- -D warnings
# 更严格的 lint(可选)
cargo clippy --all-targets --all-features -- -D warnings -W clippy::pedantic
# 测试 — 仅单元测试(无需 PostgreSQL,约 1.4 秒)
cargo test --lib
# 测试 — 按名称子串运行单个测试
cargo test --lib test_name_substring
# 示例: cargo test --lib hostport_out_of_range
# 测试 — 运行单个模块的测试
cargo test --lib config::tests
cargo test --lib pools::tests
# 测试 — 完整测试套件(需要 PostgreSQL 运行在 localhost:5432,用户名=postgres,密码=111111)
cargo test
# 覆盖率
cargo llvm-cov --lib
cargo llvm-cov --lib --show-missing-lines
# 发布
cargo publish --dry-run
```
## 项目结构
```
src/
├── packet.rs # 2816 行 — 协议报文打包/解包(最大文件,含 unsafe)
├── connect.rs # 1190 — TCP 生命周期、启动/认证、查询/执行、参数化查询、带重试的 read()
├── pools.rs # 512 — 连接池、ConnectionGuard、全局 DB_POOL、重试逻辑
├── format.rs # 511 — OID→JsonValue 类型转换、PG 数组解析
├── lib.rs # 284 — 公开 API:Pgsql 结构体、connect()、pools()
├── error.rs # 199 — PgsqlError 枚举,实现 Display、Error、From
└── config.rs # 75 — Config 结构体、默认值、URL 拼接、端口校验
tests/index.rs — 集成测试(需要运行中的 PostgreSQL)
examples/index.rs — 使用 tokio-postgres,不是本 crate(容易误导)
```
## 改哪里找哪里
| 添加 PG 消息类型 | `packet.rs` | `MessageType::form()` — 按字节标签匹配 |
| 添加字段类型转换 | `format.rs` | `FieldFormat::from_u16()` — 按 (code, type_oid) 匹配 |
| 修改认证流程 | `connect.rs` | `authenticate()`、`scram_auth()`、`md5_auth()` |
| 修改连接池行为 | `pools.rs` | `get_connect()`、`release_conn()` |
| 修改默认值 | `config.rs` | `Config::new()` — localhost:5432, postgres/111111 |
| 添加错误变体 | `error.rs` | `PgsqlError` 枚举 — 需实现 Display + From |
| 修改读取重试 | `connect.rs` | `read()` — MAX_RETRIES、deadline、MAX_MESSAGE_SIZE |
| 参数化查询 | `connect.rs` + `packet.rs` | `query_params()`、`execute_params()`、`pack_query_params()`、`Query::Bind` |
| 参数化查询(便捷) | `connect.rs` | `query_str()`、`execute_str()` — 所有参数非 NULL 的简化版 |
## 模块边界(禁止越界)
| `packet.rs` | 协议编解码 | 网络 I/O、连接池逻辑 |
| `connect.rs` | Socket 生命周期、读写 | 连接池策略 |
| `pools.rs` | 连接池管理、重试 | 协议细节 |
| `format.rs` | OID→JsonValue 转换 | 报文解析 |
| `error.rs` | 仅错误类型 | 业务逻辑 |
| `config.rs` | 配置解析 | 连接逻辑 |
## 代码风格
### 导入顺序:crate → 外部依赖 → std
```rust
use crate::config::Config;
use crate::error::PgsqlError;
use log::{error, warn};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
```
### 错误处理
- 返回 `Result<T, PgsqlError>` — 禁止 `Result<T, String>`
- 映射 I/O 错误:`.map_err(|e| PgsqlError::Connection(e.to_string()))?`
- 新增的解析/协议路径禁止 `.unwrap()` — 使用可失败转换
- Mutex 加锁:`unwrap_or_else(PoisonError::into_inner)`(现有模式)
### 类型
- 协议字段使用显式数值宽度:`u16`、`u32`、`i32` — 禁止用 `usize` 表示线上数据
- `json::JsonValue` 用于配置输入和查询结果行
- `BTreeMap<String, String>` 用于有序键值映射(不用 HashMap)
- `Vec<u8>` 用于原始协议缓冲区
### 命名
- 结构体:`PascalCase` — `Connect`、`Packet`、`ConnectionGuard`、`FieldFormat`
- 枚举变体:`PascalCase` — `PgsqlError::Protocol(String)`、`FieldFormat::BigInt`
- 函数:`snake_case` — `pack_first()`、`get_connect()`、`from_u16()`
- 常量:`SCREAMING_SNAKE` — `MAX_RETRIES`、`MAX_MESSAGE_SIZE`、`DB_POOL`
### 日志
- 仅使用 `log` crate 宏:`debug!`、`warn!`、`error!`
- 库代码中禁止 `println!`
### 注释
- 代码中已有大量中文注释 — 编辑附近代码时保留原有注释
- 文档注释使用中文:`/// 第一包`、`/// 调试`、`/// 账号`
- 如需翻译,整个代码块统一翻译
### 测试
- 每个源文件底部使用 `#[cfg(test)] mod tests` 内联测试
- Mock TCP 服务器:`TcpListener::bind("127.0.0.1:0")` 获取随机端口
- 辅助函数:`pg_msg(tag: u8, payload: &[u8]) -> Vec<u8>` 构建 PG 协议消息
- `#[cfg(test)]` 常量覆盖:MAX_RETRIES=3、MAX_MESSAGE_SIZE=128、deadline=200ms
- `pools.rs` 使用单个 `pools_all_paths` 测试以避免全局 `DB_POOL` 竞态
- 连接池测试中清理 `DB_POOL`:`DB_POOL.lock().unwrap_or_else(|e| e.into_inner()).clear()`
## 反模式(硬性规则)
- **禁止** 新增 `unsafe { String::from_utf8_unchecked }` — 现有技术债,不要扩大
- **禁止** 新增解析/协议路径中使用 `.unwrap()` — 使用可失败转换
- **禁止** 在核心 crate 中引入 async — 仅同步(async 仅限 examples/)
- **禁止** 持有 mutex 期间进行 socket 操作 — 保持锁范围最小
- **禁止** 未经明确要求修改公开 API 签名
- **禁止** 类型错误抑制(`#[allow(...)]` 隐藏真实问题)
## 已知陷阱
- `packet.rs` 大量使用 `unsafe` UTF-8 转换 — 仅做小范围精确修改
- 全局 `DB_POOL` 静态变量 — 所有 `Pools` 实例共享同一个 `VecDeque<Connect>`
- `connect.rs` 的 `read()` 在 `#[cfg(test)]` 下使用缩小的常量 — 生产值不同
- `tests/index.rs` 需要运行中的 PostgreSQL 且使用默认凭据(postgres/111111)
- `examples/index.rs` 演示的是 `tokio-postgres`,不是本 crate
## 依赖
| `json` | 配置解析、结果行 |
| `hmac` + `sha2` + `base64` + `rand` | SCRAM-SHA-256 认证 |
| `md5` | MD5 认证 |
| `log` | 日志宏 |
## 检查清单
编码前:
1. 确认改动属于哪个模块边界
2. 选择最小测试命令:`cargo test --lib`(无需数据库)
完成前:
1. `cargo fmt --check`
2. `cargo clippy --all-targets --all-features -- -D warnings`
3. `cargo test --lib`
4. 确认 diff 聚焦 — 无无关改动