Serde Code Review
Review Workflow
- 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
SerializeandDeserializeare 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
genkeyword, RPIT lifetime capture changes,never_type_fallback - Verify round-trip correctness — Serialized data must deserialize back to the same value
Gates (before reporting findings)
Run in order. Do not write a finding until the step that applies has passed.
-
Serde context on disk — Pass when: You have read the relevant
Cargo.toml(crate or workspace root) and can state Rustedition,serde/serde_derivefeatures if non-default (derive,rc), and which format crates apply (serde_json,toml,bincode, etc.) for the code under review. Then apply edition-specific checklist items (e.g.gen, RPIT/never_type_fallback) only when that file supports them. -
Per-finding evidence — Pass when: Each issue cites
[FILE:LINE]from the current tree for thestruct/enum,Serialize/Deserializeimpl, or attribute block in question (not from memory, docs-only, or another branch). -
Category check vs protocol — Pass when: For the finding type (derive attrs, enum tagging,
flatten, custom impl, sqlx + serde alignment), you ran the matching checks frombeagle-rust:review-verification-protocol(e.g. full type definition + serde attrs before “wrong representation”; confirmed edition inCargo.tomlbefore edition-2024-only findings). Then add the finding. -
Output shape — Pass when: The report lines match Output Format below (severity + description).
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
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:
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - 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 (user#genor 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 — user#genwith#[serde(rename = "gen")]or choose a different name) - Custom
Serialize/Deserializeimpls returningimpl Traitaccount 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 onResult<T, !>patterns
Correctness
- Round-trip tests exist for complex types (serialize → deserialize → assert_eq)
-
PartialEqderived for types with round-trip tests - No lossy conversions (e.g.,
f64→i64in JSON numbers) -
Decimalused for money/precision-sensitive values, notf64
Severity Calibration
Critical
- Enum representation mismatch between serializer and deserializer (data loss)
- Missing
#[serde(rename)]causing API-breaking field name changes #[serde(flatten)]causing silent key collisions- Lossy numeric conversions (
f64precision loss for monetary values)
Major
- Inconsistent
rename_allacross related types (confusing API) - Missing
skip_serializing_ifcausing null/empty noise in output deny_unknown_fieldson types consumed by evolving APIs (breaks forward compatibility)- Missing round-trip tests for complex enum representations
- Field or variant named
genwithoutr#genescape (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::Valuefor 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#genwith#[serde(rename = "gen")]— Correct edition 2024 workaround forgenfields in wire formats+ use<'a>on custom serializer return types — Precise RPIT lifetime capture (edition 2024)
Before Submitting Findings
Complete Gates (before reporting findings) above; gate 3 incorporates beagle-rust:review-verification-protocol for serde-related issue types.