Serde Code Review
Review Workflow
- 1. Check Cargo.toml — Note serde features (
derive, rc), format crates (serde_json, toml, bincode, etc.), and Rust edition (2024 has breaking changes affecting serde code) - Check derive usage — Verify
Serialize and Deserialize are derived appropriately - Check enum representations — Enum tagging affects wire format compatibility and readability
- Check field attributes — Renaming, defaults, skipping affect API contracts
- Check edition 2024 compatibility — Reserved
gen keyword, RPIT lifetime capture changes, INLINECODE8 - Verify round-trip correctness — Serialized data must deserialize back to the same value
Output Format
Report findings as:
CODEBLOCK0
Quick Reference
| Issue Type | Reference |
|---|
| Derive patterns, attribute macros, field configuration | references/derive-patterns.md |
| Custom Serialize/Deserialize, format-specific issues |
references/custom-serialization.md |
Review Checklist
Derive Usage
- - [ ]
#[derive(Serialize, Deserialize)] on types that cross serialization boundaries - [ ]
#[derive(Debug)] alongside serde derives (debugging serialization issues) - [ ] Feature-gated derives when serde is optional: INLINECODE11
- [ ] Prefer
#[expect(unused)] over #[allow(unused)] for serde-only fields (self-cleaning lint suppression, stable since 1.81)
Enum Representation
- - [ ] Enum tagging is explicit (not relying on serde's default externally-tagged format when another is intended)
- [ ] Tag names are stable and won't collide with field names
- [ ]
#[serde(rename_all = "...")] used consistently across the API
Field Configuration
- - [ ]
#[serde(skip_serializing_if = "Option::is_none")] for optional fields (clean JSON output) - [ ]
#[serde(default)] for fields that should have fallback values during deserialization - [ ]
#[serde(rename = "...")] when Rust field names differ from wire format - [ ]
#[serde(flatten)] used judiciously (can cause key collisions) - [ ] No
#[serde(deny_unknown_fields)] on types that need forward compatibility - [ ] No fields or variants named
gen — reserved keyword in edition 2024 (use r#gen or rename)
Database Integration (sqlx)
- - [ ]
#[derive(sqlx::Type)] enums use consistent representation with serde - [ ] Enum variant casing matches between serde (
rename_all) and sqlx (rename_all)
Edition 2024 Compatibility
- - [ ] No fields or enum variants named
gen (reserved keyword — use r#gen with #[serde(rename = "gen")] or choose a different name) - [ ] Custom
Serialize/Deserialize impls returning impl Trait account for RPIT lifetime capture changes (all in-scope lifetimes captured by default; use + use<'a> for precise control) - [ ] Deserialization error paths handle
never_type_fallback — ! falls back to ! instead of (), which affects match exhaustiveness on Result<T, !> patterns
Correctness
- - [ ] Round-trip tests exist for complex types (serialize → deserialize → assert_eq)
- [ ]
PartialEq derived for types with round-trip tests - [ ] No lossy conversions (e.g.,
f64 → i64 in JSON numbers) - [ ]
Decimal used for money/precision-sensitive values, not INLINECODE41
Severity Calibration
Critical
- - Enum representation mismatch between serializer and deserializer (data loss)
- Missing
#[serde(rename)] causing API-breaking field name changes - INLINECODE43 causing silent key collisions
- Lossy numeric conversions (
f64 precision loss for monetary values)
Major
- - Inconsistent
rename_all across related types (confusing API) - Missing
skip_serializing_if causing null/empty noise in output - INLINECODE47 on types consumed by evolving APIs (breaks forward compatibility)
- Missing round-trip tests for complex enum representations
- Field or variant named
gen without r#gen escape (edition 2024 compile failure)
Minor
- - Unnecessary
#[serde(default)] on required fields - Using string representation for enums when numeric would be more efficient
- Verbose custom implementations where derive + attributes suffice
- Using
#[allow(unused)] instead of #[expect(unused)] for serde-only fields (prefer self-cleaning lint suppression)
Informational
- - Suggestions to switch enum representation for cleaner wire format
- Suggestions to add
#[non_exhaustive] alongside serde for forward compatibility
Valid Patterns (Do NOT Flag)
- - Externally tagged enums — serde's default, valid for many use cases
#[serde(untagged)] enums — Valid when discriminated by structure, not by tagserde_json::Value for dynamic data — Appropriate for truly schema-less fields#[serde(skip)] on computed fields — Correct for derived/cached values#[serde(with = "...")] for custom formats — Standard for dates, UUIDs, etc.r#gen with #[serde(rename = "gen")] — Correct edition 2024 workaround for gen fields in wire formats+ use<'a> on custom serializer return types — Precise RPIT lifetime capture (edition 2024)
Before Submitting Findings
Load and follow beagle-rust:review-verification-protocol before reporting any issue.
Serde 代码审查
审查工作流
- 1. 检查 Cargo.toml — 注意 serde 特性(derive、rc)、格式化 crate(serdejson、toml、bincode 等)以及 Rust 版本(2024 版本存在影响 serde 代码的破坏性变更)
- 检查 derive 使用 — 验证是否适当派生了 Serialize 和 Deserialize
- 检查枚举表示 — 枚举标记影响线格式兼容性和可读性
- 检查字段属性 — 重命名、默认值、跳过影响 API 契约
- 检查 2024 版本兼容性 — 保留的 gen 关键字、RPIT 生命周期捕获变更、nevertype_fallback
- 验证往返正确性 — 序列化的数据必须能反序列化回相同的值
输出格式
按以下格式报告发现:
text
[FILE:LINE] 问题标题
严重级别:严重 | 主要 | 次要 | 信息性
问题描述及其重要性说明。
快速参考
references/custom-serialization.md |
审查清单
派生使用
- - [ ] 在跨越序列化边界的类型上使用 #[derive(Serialize, Deserialize)]
- [ ] 在 serde 派生旁使用 #[derive(Debug)](用于调试序列化问题)
- [ ] 当 serde 为可选时,使用特性门控派生:#[cfg_attr(feature = serde, derive(Serialize, Deserialize))]
- [ ] 对于仅 serde 使用的字段,优先使用 #[expect(unused)] 而非 #[allow(unused)](自清理 lint 抑制,自 1.81 版本稳定)
枚举表示
- - [ ] 枚举标记是显式的(不依赖 serde 默认的外部标记格式,如果意图使用其他格式)
- [ ] 标记名称稳定且不会与字段名称冲突
- [ ] #[serde(rename_all = ...)] 在整个 API 中一致使用
字段配置
- - [ ] 对于可选字段使用 #[serde(skipserializingif = Option::isnone)](生成干净的 JSON 输出)
- [ ] 对于应在反序列化时具有回退值的字段使用 #[serde(default)]
- [ ] 当 Rust 字段名称与线格式不同时使用 #[serde(rename = ...)]
- [ ] 谨慎使用 #[serde(flatten)](可能导致键冲突)
- [ ] 在需要前向兼容性的类型上不要使用 #[serde(denyunknown_fields)]
- [ ] 没有名为 gen 的字段或变体——在 2024 版本中是保留关键字(使用 r#gen 或重命名)
数据库集成(sqlx)
- - [ ] #[derive(sqlx::Type)] 枚举使用与 serde 一致的表示
- [ ] 枚举变体的大小写在 serde(renameall)和 sqlx(renameall)之间匹配
2024 版本兼容性
- - [ ] 没有名为 gen 的字段或枚举变体(保留关键字——使用 r#gen 配合 #[serde(rename = gen)] 或选择其他名称)
- [ ] 返回 impl Trait 的自定义 Serialize/Deserialize 实现考虑 RPIT 生命周期捕获变更(默认捕获所有作用域内的生命周期;使用 + use 进行精确控制)
- [ ] 反序列化错误路径处理 nevertypefallback——! 回退到 ! 而非 (),这影响 Result 模式上的匹配穷尽性
正确性
- - [ ] 复杂类型存在往返测试(序列化 → 反序列化 → assert_eq)
- [ ] 具有往返测试的类型派生了 PartialEq
- [ ] 没有有损转换(例如 JSON 数字中的 f64 → i64)
- [ ] 货币/精度敏感值使用 Decimal,而非 f64
严重级别校准
严重
- - 序列化器和反序列化器之间的枚举表示不匹配(数据丢失)
- 缺少 #[serde(rename)] 导致破坏 API 的字段名称变更
- #[serde(flatten)] 导致静默键冲突
- 有损数值转换(货币值的 f64 精度丢失)
主要
- - 相关类型间的 renameall 不一致(令人困惑的 API)
- 缺少 skipserializingif 导致输出中出现 null/空值噪音
- 在由演进 API 消费的类型上使用 denyunknown_fields(破坏前向兼容性)
- 复杂枚举表示缺少往返测试
- 名为 gen 的字段或变体未使用 r#gen 转义(2024 版本编译失败)
次要
- - 在必填字段上不必要的 #[serde(default)]
- 当数值表示更高效时使用字符串表示枚举
- 当派生加属性足够时使用冗长的自定义实现
- 对于仅 serde 使用的字段,使用 #[allow(unused)] 而非 #[expect(unused)](优先使用自清理 lint 抑制)
信息性
- - 建议切换枚举表示以获得更清晰的线格式
- 建议在 serde 旁添加 #[non_exhaustive] 以实现前向兼容性
有效模式(不要标记)
- - 外部标记枚举 — serde 的默认方式,适用于许多用例
- #[serde(untagged)] 枚举 — 当通过结构而非标记区分时有效
- 用于动态数据的 serde_json::Value — 适用于真正无模式的字段
- 计算字段上的 #[serde(skip)] — 适用于派生/缓存值
- 自定义格式的 #[serde(with = ...)] — 日期、UUID 等的标准做法
- r#gen 配合 #[serde(rename = gen)] — 线格式中 gen 字段的正确 2024 版本解决方案
- 自定义序列化器返回类型上的 + use — 精确的 RPIT 生命周期捕获(2024 版本)
提交发现前
在报告任何问题前,加载并遵循 beagle-rust:review-verification-protocol。