Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Rust Serde

ASecurity

Serde as the boundary where untrusted input becomes a domain type — rename_all, default, skip_serializing_if, flatten, the four enum representations, deny_unknown_fields, and try_from validation. Use when deriving Serialize or Deserialize, when choosing an enum wire representation, when a JSON or YAML shape does not match the Rust type, or when the user asks how to validate deserialized data.

2 stars
0 votes
0 copies
1 views
Added 9/19/2026
developmentrustgobashapi

Works with

cliapi

Security Analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned 9/19/2026

$npx -y skills add rewrite-rs/skills --skill rust-serde --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rust Serde?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Rust Serde
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rewrite-rs-rust-serde/badge)](https://www.skillsdirectory.com/skills/rewrite-rs-rust-serde)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: rust-serde
description: Serde as the boundary where untrusted input becomes a domain type — rename_all, default, skip_serializing_if, flatten, the four enum representations, deny_unknown_fields, and try_from validation. Use when deriving Serialize or Deserialize, when choosing an enum wire representation, when a JSON or YAML shape does not match the Rust type, or when the user asks how to validate deserialized data.
---

# Rust Serde

A `#[derive(Deserialize)]` is a claim that anything it accepts is already valid
for the domain — the parse is the only place that claim is cheap to enforce.

## Deserialization is a parse

The type you deserialize into is the type the rest of the program trusts, so it
is the last place validation is cheap. A `Config` that deserializes with a
`String` port and checks it in `run()` has moved the failure past every layer
that could have reported it usefully. What the validated type should be is
`/type-driven-design`; this skill owns the boundary that type crosses.

## `#[serde(try_from = "...")]` is the mechanism

Deserialize into a raw shape, convert with `TryFrom` into the validated type,
and the conversion failure becomes a deserialization error — the read fails,
not the first use of the value. The error must implement `std::error::Error`.

```rust
#[derive(serde::Deserialize)]
struct RawConfig {
    workers: u32,
}

#[derive(serde::Deserialize)]
#[serde(try_from = "RawConfig")]
struct WorkerCount(u32);

#[derive(Debug)]
struct ZeroWorkers;

impl std::fmt::Display for ZeroWorkers {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "workers must be at least one")
    }
}

impl std::error::Error for ZeroWorkers {}

impl TryFrom<RawConfig> for WorkerCount {
    type Error = ZeroWorkers;

    fn try_from(raw: RawConfig) -> Result<Self, Self::Error> {
        if raw.workers == 0 { Err(ZeroWorkers) } else { Ok(WorkerCount(raw.workers)) }
    }
}
```

## Naming across the boundary

`#[serde(rename_all = "camelCase")]` on the type, not `rename` on every field:
the Rust name follows Rust convention, the attribute carries the wire convention,
so neither side distorts the other.

## Optionality has three meanings, and they are different

- `#[serde(default)]` — the field may be absent and has a sensible zero;
  `T::default()` fills it, and an explicit null is still an error.
- `Option<T>` — absent and null both land in `None`, erasing the distinction;
  when the code must say which it means, the field is not a bare `Option`.
- `#[serde(skip_serializing_if = "Option::is_none")]` — do not emit the key at
  all on the way out.

Getting these wrong produces a round-trip that is not one — the bug that shows
up in a system you do not run, failing on a payload you produced.

## The four enum representations

| Representation | On the wire | Constraint |
|---|---|---|
| External — the default | `{"Named": "x"}`: a one-key wrapper around the variant | the wrapper key is noise when the payload already says what it is |
| Internal — `tag = "type"` | `{"type": "Named", "name": "x"}`: the tag inside the variant | cannot hold a newtype variant over a non-struct |
| Adjacent — `tag` plus `content` | `{"type": "Named", "value": "x"}`: tag and payload as siblings | the tag key is reserved in the envelope — a wire shape that already spends it at the top level cannot use this representation |
| Untagged | the variant shape as-is, no marker | tries every variant in order and reports a useless error when all fail — last resort, never for input you must diagnose |

External is the default; internal and adjacent are what a self-describing
message needs; untagged is for a shape no representation fits.

## `deny_unknown_fields` is a decision, not a default

Denying catches typos in a config file the user writes — a silently ignored
key is a support ticket. Accepting lets a producer add a field without
breaking every consumer. Choose per type: a config file usually denies, a
message from a service you do not control usually accepts.

## `flatten` and what it costs

It composes a shared struct into several types without repeating the fields,
and it costs twice: it disables `deny_unknown_fields` on the containing type,
and it forces buffering during deserialization. Worth it for composition, not
for saving four lines.

## Errors from the boundary

A deserialization failure is user-facing input error, not a bug: it carries the
reason — and, in JSON, a line and column once the failure sits in a nested
field — and it goes into the crate error type as its own kind, not a
stringified message. Shaping that kind is `/rust-errors`.

## Deferrals

What the validated type should be is `/type-driven-design`; whether it is
public API is `/rust-api-design`; custom derive mechanics are `/rust-macros`;
numeric range validation is the numerics companion of `/type-driven-design`.

## Verification

Run at the lint level the target repo configures; the fallback applies only
when the repo configures no lints at all:

```bash
cargo test                   # round-trip tests: serialize, deserialize, compare
cargo clippy --all-targets   # at the level the repo configures
```

Plus the check the compiler cannot do: for every type crossing a wire, a test
that a real payload from the other side deserializes — a round-trip that only
round-trips your own output proves the type agrees with itself.

Attribution

rewrite-rsrewrite-rs
View sourceSee grades on GitHubMore from rewrite-rs →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10311 votes
View all in development →