Skills serde-code-review
Reviews serde serialization code for derive patterns, enum representations, custom implementations, and common serialization bugs. Use when reviewing Rust code that uses serde, serde_json, toml, or any serde-based serialization format. Covers attribute macros, field renaming, and format-specific pitfalls.
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/anderskev/serde-code-review" ~/.claude/skills/clawdbot-skills-serde-code-review && rm -rf "$T"
manifest:
skills/anderskev/serde-code-review/SKILL.mdsource content
Serde Code Review
Review Workflow
- Check Cargo.toml — Note serde features (
,derive
), format crates (rc
,serde_json
,toml
, etc.), and Rust edition (2024 has breaking changes affecting serde code)bincode - Check derive usage — Verify
andSerialize
are derived appropriatelyDeserialize - 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
keyword, RPIT lifetime capture changes,gennever_type_fallback - Verify round-trip correctness — Serialized data must deserialize back to the same value
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
-
on types that cross serialization boundaries#[derive(Serialize, Deserialize)] -
alongside serde derives (debugging serialization issues)#[derive(Debug)] - Feature-gated derives when serde is optional:
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - Prefer
over#[expect(unused)]
for serde-only fields (self-cleaning lint suppression, stable since 1.81)#[allow(unused)]
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
-
used consistently across the API#[serde(rename_all = "...")]
Field Configuration
-
for optional fields (clean JSON output)#[serde(skip_serializing_if = "Option::is_none")] -
for fields that should have fallback values during deserialization#[serde(default)] -
when Rust field names differ from wire format#[serde(rename = "...")] -
used judiciously (can cause key collisions)#[serde(flatten)] - No
on types that need forward compatibility#[serde(deny_unknown_fields)] - No fields or variants named
— reserved keyword in edition 2024 (usegen
or rename)r#gen
Database Integration (sqlx)
-
enums use consistent representation with serde#[derive(sqlx::Type)] - Enum variant casing matches between serde (
) and sqlx (rename_all
)rename_all
Edition 2024 Compatibility
- No fields or enum variants named
(reserved keyword — usegen
withr#gen
or choose a different name)#[serde(rename = "gen")] - Custom
/Serialize
impls returningDeserialize
account for RPIT lifetime capture changes (all in-scope lifetimes captured by default; useimpl Trait
for precise control)+ use<'a> - Deserialization error paths handle
—never_type_fallback
falls back to!
instead of!
, which affects match exhaustiveness on()
patternsResult<T, !>
Correctness
- Round-trip tests exist for complex types (serialize → deserialize → assert_eq)
-
derived for types with round-trip testsPartialEq - No lossy conversions (e.g.,
→f64
in JSON numbers)i64 -
used for money/precision-sensitive values, notDecimalf64
Severity Calibration
Critical
- Enum representation mismatch between serializer and deserializer (data loss)
- Missing
causing API-breaking field name changes#[serde(rename)]
causing silent key collisions#[serde(flatten)]- Lossy numeric conversions (
precision loss for monetary values)f64
Major
- Inconsistent
across related types (confusing API)rename_all - Missing
causing null/empty noise in outputskip_serializing_if
on types consumed by evolving APIs (breaks forward compatibility)deny_unknown_fields- Missing round-trip tests for complex enum representations
- Field or variant named
withoutgen
escape (edition 2024 compile failure)r#gen
Minor
- Unnecessary
on required fields#[serde(default)] - Using string representation for enums when numeric would be more efficient
- Verbose custom implementations where derive + attributes suffice
- Using
instead of#[allow(unused)]
for serde-only fields (prefer self-cleaning lint suppression)#[expect(unused)]
Informational
- Suggestions to switch enum representation for cleaner wire format
- Suggestions to add
alongside serde for forward compatibility#[non_exhaustive]
Valid Patterns (Do NOT Flag)
- Externally tagged enums — serde's default, valid for many use cases
enums — Valid when discriminated by structure, not by tag#[serde(untagged)]
for dynamic data — Appropriate for truly schema-less fieldsserde_json::Value
on computed fields — Correct for derived/cached values#[serde(skip)]
for custom formats — Standard for dates, UUIDs, etc.#[serde(with = "...")]
withr#gen
— Correct edition 2024 workaround for#[serde(rename = "gen")]
fields in wire formatsgen
on custom serializer return types — Precise RPIT lifetime capture (edition 2024)+ use<'a>
Before Submitting Findings
Load and follow
beagle-rust:review-verification-protocol before reporting any issue.