# Acgetchell Dotfiles Rust Parse Dont Validate

> rust-parse-dont-validate

- Skill: `tomevault-io/acgetchell-dotfiles-rust-parse-dont-validate` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/acgetchell-dotfiles-rust-parse-dont-validate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/acgetchell-dotfiles-rust-parse-dont-validate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/acgetchell-dotfiles-rust-parse-dont-validate

---


# rust-parse-dont-validate

Audit Rust code for invalid-state prevention and parse-don't-validate design.

The goal is to reject bad input at the boundary, store only valid values, and let
later computation operate on proof-bearing / validated domain types without
repeated defensive checks.

## Scope

Depending on the requested scope, inspect Rust code that:

- introduces or changes structs, enums, builders, constructors, setters, parsers,
  deserialization, configuration, checkpoint loading, or public fields
- stores values with invariants such as counts, dimensions, indexes, probabilities,
  finite floats, normalized weights, ranges, paths, IDs, modes, topology, or
  algorithm parameters
- validates values after construction, immediately before use, or repeatedly in
  compute functions
- returns `Result` from computation only because earlier validation evidence was
  discarded
- accepts raw values where a refined type, newtype, enum, or `NonZero*` type could
  encode validity

Ignore passive report/DTO structs with no meaningful invariants unless public
fields can be fed back into invariant-bearing APIs.

DTO means data transfer object: a passive shape used to carry raw data across a
boundary, such as a wire format, config-file representation, API response, test
fixture, or report. DTOs are useful at boundaries, but they are not a substitute
for proof-bearing / validated domain types. Prefer terms like
`proof-bearing type`, `validated domain type`, `refined type`, or
`validated wrapper` for the parsed value that carries validation evidence inward.

## Rust Idiom Guardrails

This skill should not push Java-style boilerplate into Rust code.

Prefer idiomatic Rust boundaries:

- prefer parsing into a more precise type over validating and returning `()`;
  if a check proves something, ask what type should carry that proof
- parse raw inputs at public boundaries into a proof-bearing / validated domain
  type, then pass that value inward; do not trust a public wrapper type merely
  because its normal constructor validates, because crate-internal unchecked
  constructors, tests, deserialization, or future changes may still create
  invalid stored state
- avoid the opposite failure mode too: once a proof-bearing / validated domain
  value exists, internal helpers should accept that type and trust its invariant
  rather than repeatedly reparsing or rescanning the same object
- public fields are fine for passive data with no invariants
- private fields plus accessors are appropriate when fields carry invariants
- use `TryFrom`, `FromStr`, fallible constructors, or builder `build` methods for
  raw-to-refined conversion
- use `From` only when conversion cannot fail
- name public raw-to-refined constructors fallibly (`try_new`, `try_from_*`,
  `parse`, `FromStr`, or `TryFrom`); reserve infallible `new` / `from_*` for
  already-refined inputs, crate-private literals, or internal validated paths
- design public APIs so any operation with caller-observable failure returns
  `Result` or `Option`; public functions that return plain values should be
  genuinely total for all representable inputs, not panic-based validation paths
- use `Option` for absence, not for explaining invalid input
- use `Result` when callers need to know why raw input was rejected
- keep getters and borrowed accessors infallible whenever stored data is already
  valid
- treat `*_unchecked` APIs as internal, unsafe-style escape hatches unless they
  are clearly documented and justified
- when standard traits such as `Extend` or `FromIterator` force infallible APIs,
  ensure fallible alternatives such as `try_extend` or `try_from_iter` exist when
  invalid input matters

### Scope Modes

Default changed-code mode:
- Use when the user asks for this skill without an explicit scope.
- Audit newly added or modified invariant-bearing code.
- Use nearby unchanged code only to understand local conventions.

Pull-request mode:
- Use when the user says "PR", "pull request", "this branch", "diff against
  main", or similar.
- Identify the PR/diff base from local repository context or the user's explicit
  target.
- Audit changed invariant-bearing APIs first, then any unchanged constructors,
  setters, deserialization paths, or compute functions needed to decide whether
  the changed code can store invalid state.
- Report findings against the changed code when possible. Mention unchanged
  supporting code only when it creates or preserves the risk.
- Keep the review PR-sized: prioritize merge-blocking invalid-state risks,
  public API hazards, and missing tests for new validation behavior.

Whole-repo baseline mode:
- Use when the user explicitly says "whole repo", "entire repo", "baseline
  audit", or similar.
- Audit public constructors, builders, setters, deserialization boundaries,
  configuration/checkpoint types, and common validation helpers.
- Prioritize API boundaries and stored invalid states over cosmetic type
  refinements.
- Group related findings by type/module so the user can split follow-up work.
- Do not require fixing every historical issue in one patch; identify the highest
  leverage invariant boundaries first.

## Review Goals

### 1. Identify Invariants

For each changed type or API, name the invariants it relies on.

Examples:

- count must be positive
- probability must be finite and within `[0, 1]`
- log value may be `-inf` but not `NaN` or `+inf`
- index must be within a collection
- enum-like string must be one of a fixed set
- dimensions must agree
- checkpoint counters must be internally consistent
- `Option<T>` must represent true absence rather than deferred initialization or
  a state-specific payload that belongs in an enum variant

If no invariant exists, do not force smart constructors or private fields.

### 2. Validate Before Storage

Flag any path that stores raw invalidable values before validation.

Check:

- `new`, `try_new`, `from_*`, `parse`, `builder.build`, and setter methods
- `Default` implementations
- struct update syntax made possible by public fields
- direct derived `Deserialize` on invariant-bearing public or private fields
- internal constructors used by tests, examples, or deserialization
- mutation that partially updates an object before validation can fail

Prefer:

- fallible constructors/parsers for raw input
- public raw-to-refined constructors named to signal fallibility; infallible
  raw-value helpers should normally be private, `pub(crate)`, test-only, or
  explicitly `*_unchecked`
- private fields for invariant-bearing data
- validation before mutation, with atomic accept/reject behavior
- custom `Deserialize`, `TryFrom`, or builder `build` methods when raw serialized
  data can be invalid

### 2.5. Use DTOs Only at Boundaries

DTOs are appropriate for raw transport and passive reporting. They should not be
the type that core algorithms trust when fields have semantic constraints.
Do not call the parsed valid value a DTO; use `proof-bearing type`,
`validated domain type`, `refined type`, or `validated wrapper`.

Prefer a two-layer design:

- raw DTOs with simple fields for deserialization, serialization, fixtures, or
  passive reports
- fallible conversion from raw DTOs into proof-bearing / validated domain types
- validated domain types with private invariant-bearing fields and infallible
  accessors
- computation over proof-bearing / validated domain types, not over raw DTOs

Flag:

- raw deserialized/config DTOs passed directly into computation without a parse
  or `TryFrom` step
- report/passive DTOs later reused as inputs to invariant-bearing APIs
- DTOs with public fields whose values must be mutually consistent for methods
  to behave correctly
- "DTO" naming used to justify storing invalid algorithm state

Accept:

- public fields on passive reports/results that callers only inspect
- raw config/wire structs that are immediately parsed into proof-bearing /
  validated domain types
- test fixtures that deliberately model invalid input before rejection

### 3. Make Invalid States Unrepresentable

Prefer encoding invariants in types when doing so reduces repeated validation or
clarifies API contracts.

Consider:

- `NonZeroUsize`, `NonZeroU64`, or other standard refined types
- small domain enums instead of finite string categories
- newtypes such as `PositiveWeight`, `FiniteLogProb`, `Probability`,
  `SampleCount`, `Dimension`, `ValidIndex`, or domain-specific IDs
- smart constructors with private fields
- accepting refined types in infallible APIs once validation has already happened

Do not introduce a new type if it only adds ceremony and the invariant is local,
obvious, and already impossible to violate.

#### Algebraic Domain Modeling

Use Rust's algebraic data types to encode domain states, alternatives, and
proofs directly in the type system when invalid combinations are otherwise
representable.

Prefer enums, tuple structs, newtypes, and variant-specific payloads when:

- a set of fields is only valid for some modes or outcomes
- `bool` plus `Option<T>` combinations can represent impossible states
- a raw primitive has domain meaning beyond its machine type
- a computation branches repeatedly to rediscover a value's domain category
- a validated value should carry proof into later infallible computation

Flag patterns such as:

- `accepted: bool` plus `action_after: Option<f64>` when accepted steps must
  always have an action-after value and rejected steps must never have one
- enum-like strings or integers used in core domain state
- structs where several fields are mutually dependent but publicly mutable
- `Option<T>` used for values that are required in one domain state and forbidden
  in another
- parallel fields whose valid combinations are documented in comments instead of
  encoded in variants
- repeated matches or checks that could be eliminated by variant-specific
  payloads

Prefer shapes like:

- `enum StepTelemetry { Accepted { ... }, RejectedProposal { ... }, NoProposal { ... } }`
- `struct FiniteAction(f64)` instead of storing raw `f64` actions
- `struct MeasurementCount(NonZeroU32)` or domain-specific count newtypes when
  the invariant is stronger than nonzero
- private fields plus smart constructors for variant payloads with numeric
  constraints

For simple positive integer invariants, apply the dedicated `NonZero*` guidance
below before inventing a domain newtype.

Do not over-model:

- keep passive DTOs raw at serialization and configuration boundaries
- avoid replacing simple public wire or report shapes with rich ADTs when
  consumers need stable, flat serialization; parse into ADTs internally instead
- avoid typestate or deeply generic encodings when a small enum or newtype is
  enough
- prefer clear runtime validation at true external boundaries, then convert into
  refined domain types

#### NonZero* Primitive Checks

Explicitly check whether positive integer invariants should be carried by
standard `NonZero*` types.

Prefer `NonZeroU32`, `NonZeroUsize`, `NonZeroU64`, and their signed/integer
siblings when:

- a stored domain value, validated configuration, metadata field, count, period,
  retry limit, capacity, or sampling parameter has the invariant `value > 0`
- a fallible constructor/parser has already proven nonzero-ness and downstream
  code currently stores the raw integer
- an API accepts a value that must be nonzero and has no more specific invariant
  than nonzero
- the value participates in `Option<NonZero*>`, where Rust's niche optimization
  keeps absence and nonzero value compact while making `Some(0)` impossible

Flag patterns such as:

- `u32`, `usize`, or `u64` fields accompanied by checks like `== 0`, `> 0`,
  `>= 1`, or `"at least one"` after construction
- getters returning raw positive counts from validated domain objects when a
  `NonZero*` getter would preserve the proof
- converting `NonZero*` back to raw integers too early, then rechecking for zero
  in later computation
- public setters or constructors that accept raw positive counts and mutate state
  before preserving the nonzero proof
- `Option<u32>` or sentinel `0` values used to mean "absent" when
  `Option<NonZeroU32>` would encode the distinction directly

Do not recommend `NonZero*` by reflex. Keep raw integers at input boundaries such
as CLI/config DTOs, wire formats, passive reports, and tests that deliberately
model invalid input before rejection. Use a domain-specific newtype instead when
the invariant is stronger than nonzero, such as `>= 3`, a bounded range,
topology-dependent constraints, or a count that must match another field. In
those cases, `NonZero*` may be an internal building block, but it is not the full
proof.

For example, prefer a smart constructor that returns a refined value:

```rust
struct PositiveWeight(f64);

impl PositiveWeight {
    fn new(value: f64) -> Result<Self, WeightError> {
        if value.is_finite() && value > 0.0 {
            Ok(Self(value))
        } else {
            Err(WeightError::InvalidPositiveWeight { value })
        }
    }

    const fn get(self) -> f64 {
        self.0
    }
}
```

over a standalone `validate_positive_weight(value) -> Result<(), _>` whose
caller can ignore or forget the evidence it produced.

### 4. Keep Getters Infallible

Getters and borrowed accessors should normally be infallible views of already
valid stored data.

Flag:

- getters returning `Result` because stored data may be invalid
- getters that validate cached/raw fields before returning them
- accessors that panic on recoverable invalid stored state

Prefer:

- fallible construction or mutation
- infallible getters returning values, references, slices, or iterators
- fallible computation only when the computation itself can fail for a reason
  distinct from input validation

### 5. Use `Result` at Boundaries

Fallible constructors, parsers, builders, and raw-value setters should return
`Result<_, Error>` with an appropriate typed error.

As a public API rule, a function should either be genuinely infallible for all
representable inputs or make its fallibility visible with `Result` / `Option`.
Panics, `assert!`, `unwrap`, and `expect` should not be used as recoverable
validation paths in public APIs. If a panic is intentionally reserved for an
impossible internal invariant, keep that path private/crate-private where
possible and document any remaining public panic behavior explicitly.

Check that:

- each rejection reason maps to a useful error variant
- errors include relevant observed values and expected constraints
- generic `InvalidInput`, string errors, `anyhow`, or `Box<dyn Error>` do not hide
  caller-visible validation failures in library APIs
- setters that accept raw values validate before changing `self`
- constructors that accept already-refined types are infallible when no other
  failure is possible

Coordinate with `rust-error-variants` when error enum design needs deeper review.

### 6. Move Validation Out of Computation

Flag compute functions that revalidate object invariants every time they run.

Prefer:

- a single validation/parsing step at each public raw-input boundary
- public methods that accept invariant-bearing public wrapper types but can be
  reached by crate-internal unchecked construction should re-parse into a
  private proof type before computation, then pass that proof type inward
- private unchecked helpers only reachable with validated values
- infallible computation over refined types
- documented internal debug assertions only for impossible invariant violations

Keep runtime checks when:

- the computation depends on dynamic external state that can change independently
- the invariant is too expensive to preserve eagerly and the API documents the
  trade-off
- the function accepts raw input directly and is itself the boundary
- the function is a public boundary and receives a public type whose invariant is
  normally maintained but not impossible to bypass internally; in that case,
  parse once into a private proof type, then avoid revalidating after the proof is
  carried inward

### 7. Tests

Validation behavior needs direct tests.

Prefer tests that:

- cover every rejection variant
- assert structured error fields, not only `is_err()`
- include boundary values and adversarial floats (`NaN`, infinities, signed zero
  when relevant, subnormal/tiny values, huge finite values)
- use adversarial property tests for broad numeric/count/input spaces when invalid
  values can be generated systematically
- verify failed setters/builders leave the original value unchanged
- verify downstream compute functions are infallible on accepted values
- when a crate-private unchecked constructor exists, add focused regression tests
  that build invalid storage through that unchecked path and assert public
  methods reject it before computation; this proves the public boundary parses
  even when normal public constructors would have rejected the value earlier
- add companion tests or code review checks that internal proof-type helpers do
  not re-parse the same object after the boundary has already produced the proof

If a repository has a property-test placement convention, follow it.

### 8. Semgrep Guardrails

When a repo already uses Semgrep or similar project rules, consider adding
narrow guardrails for recurring parse-don't-validate regressions.

Good rule targets include:

- public `*_unchecked` APIs in production code
- infallible raw-value ingestion for known invariant-bearing types
- refined fields regressing from `NonZero*`, newtypes, or enums back to raw
  primitives
- public `validate_* -> Result<(), _>` APIs where a constructor/parser/newtype
  should carry the proof
- direct `Deserialize` derives on invariant-bearing domain types without a raw
  DTO plus fallible conversion step

Keep these rules repo-specific and low-noise. Semgrep should catch known bad
shapes and protect established invariants; it should not try to infer every Rust
invariant. If the repo has Semgrep fixtures, add positive and negative examples
for new rules.

## Common Findings

Flag these patterns:

- public invariant-bearing fields
- `new(...) -> Self` that accepts raw invalidable values
- public infallible `new` / `from_*` constructors for raw invalidable values when
  callers should receive `Result<_, Error>` instead
- public compute methods that accept public wrapper types and trust their stored
  invariants without first converting to a private proof type, when unchecked
  internal construction can bypass the public constructor
- internal helpers that accept raw/public wrapper types and revalidate them
  repeatedly instead of accepting the proof-bearing / validated domain type
  produced at the boundary
- `validate_*(&self)` required before normal use
- `is_valid` boolean APIs whose result is not encoded in the returned type
- delayed validation in `run`, `compute`, `sample`, `step`, or `finish`
- constructors that accept invalid values for later rejection
- setters that mutate first and validate later
- `Default` producing placeholder invalid values
- derived deserialization bypassing smart constructors
- getters returning `Result` because invalid data was stored
- parallel fields whose valid combinations are enforced by comments or later
  validation instead of variant-specific payloads

Accept these patterns when justified:

- public fields on passive reports with no invariants
- raw DTO structs used only at input/output boundaries and parsed into
  proof-bearing / validated domain types before computation
- infallible constructors from refined types
- crate-private or test-only infallible constructors for literals/fixtures whose
  invariant is visible at the call site, especially when the public raw
  constructor is fallible
- `unsafe` or unchecked internal constructors that are private, documented, and
  only called after validation
- lazy validation when preserving the invariant eagerly is measurably too
  expensive and the API explicitly owns that trade-off

## Output Format

### Scope
- State whether the audit used default changed-code mode, pull-request mode, or
  whole-repo baseline mode.

### Summary
- PASS
- NEEDS IMPROVEMENT
- FAIL

### Findings
- Ordered by severity.
- Include file and line references.
- Explain the invalid state that can be stored or the validation evidence that is
  discarded.

### Suggested Fixes
- Say where validation should move.
- Name any refined type/newtype/error variant that should be introduced.
- Identify tests or property tests needed to lock the invariant down.

### Optional Improvements
- Note non-blocking refinements that would improve clarity without changing
  correctness.

---
> Source: [acgetchell/dotfiles](https://github.com/acgetchell/dotfiles) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-20 -->

