Guides

Contributing

Repository layout, running CI locally, keeping target/ small, snapshots and fuzzing.

Layout

PathContents
crates/vldthe core crate: src/, tests/all/ (one test binary), benches/ (Criterion)
crates/vld-*integration crates (each with src/, tests/, usually examples/)
fuzz/cargo-fuzz targets (not in the workspace)
examples/playgrounda small app that uses many crates together
docs/this site (Nuxt + Nuxt Content)
justfileCI, checks and the release hook (just lists them)

All crates share one version and are released together; see PUBLISHING.md.

Running CI locally

CI runs just recipes, so the same commands work locally. Install the tools once:

cargo install just cargo-hack cargo-deny cargo-shear cargo-semver-checks
just            # list recipes
just ci         # what CI runs on every push
just lint test  # only these
RecipeWhat it does
fmtcargo fmt --check
lintcargo shear (unused / misplaced dependencies), then clippy -D warnings: workspace, each vld feature alone and all together, feature pairs of integration crates
testtests and doctests with the same feature sets as lint
docscargo doc with -D warnings
denycargo deny (advisories, licenses, duplicates)
packagecargo package dry run
fuzzbuilds the fuzz targets
playgroundruns the playground example
wasmwasm32 builds of the UI integrations
powersetevery pair of vld features compiles (slow)
msrvevery crate on its rust-version (see MSRV)
minimallowest allowed dependency versions (nightly)
semvercargo semver-checks: API changes vs the latest crates.io release fit the version bump
cleanremoves target/ci and the MSRV / minimal build dirs

Feature combinations come from cargo-hack. Crates with one feature per supported major of a dependency (redis-0 / redis-1, sqlx-0_8 / sqlx-0_9, …) are listed in feature_crates at the top of the justfile; cargo-hack runs every major alone and with each other feature, never two majors together. wasm, powerset, msrv, minimal and the Windows / macOS tests run only before a release, by hand (Actions → CI → Run workflow).

Lints shared by every crate live in [workspace.lints] in the root Cargo.toml (each crate has [lints] workspace = true); external dependencies used by several crates are declared once in [workspace.dependencies].

Builds use the committed Cargo.lock (--locked) and the ci profile (no debuginfo, output in target/ci). diesel's postgres / mysql features need libpq / libmysqlclient (brew install libpq mysql-client, apt install libpq-dev libmysqlclient-dev).

Never use --all-features on crates with dual-major features (ntex-3 / ntex-4, utoipa-5 / utoipa-6, sqlx-0_8 / sqlx-0_9, …): the majors are mutually exclusive and fail with compile_error!.

Keeping target/ small

With ~30 integration crates a debug target/ grows fast. The workspace already helps:

  • [profile.dev] uses debug = "line-tables-only" for workspace crates and no debuginfo for dependencies;
  • split-debuginfo = "off" stops macOS from leaving stale object files in target/*/deps on every rebuild;
  • just recipes build into target/ci; just clean removes it without touching the dev build.

If it still grows, remove old artifacts with cargo sweep:

cargo install cargo-sweep
cargo sweep --time 7     # delete artifacts not used for 7 days

Snapshot tests

vld-utoipa, vld-aide and vld-ts use insta snapshots. After an intended output change:

cargo insta test -p vld-ts
cargo insta review

vld-ts cross-check against Node

vld-ts can compare its generated Zod / Valibot schemas against real Node runs. Point VLD_TS_NODE_DIR at a directory with zod@3 and valibot@1 installed:

mkdir -p .ts-check && echo '{"private":true}' > .ts-check/package.json && npm --prefix .ts-check install zod@3 valibot@1
VLD_TS_NODE_DIR=$PWD/.ts-check cargo test -p vld-ts

Without the variable the cross-check is skipped.

Fuzzing

cargo install cargo-fuzz
cd fuzz
cargo +nightly fuzz list
cargo +nightly fuzz run iso_formats -- -max_total_time=60

Targets: iso_formats, string_formats, url_decode, error_truncation. Crashes are saved to fuzz/artifacts/<target>/.

Documentation

This site lives in docs/ and is built with Nuxt Content:

cd docs
pnpm install
pnpm dev                                   # http://localhost:3000
NUXT_APP_BASE_URL=/vld/ pnpm generate      # static build in .output/public

CHANGELOG.md and MSRV.md are copied into the site by docs/scripts/sync.mjs; edit the originals. The site is deployed to GitHub Pages by .github/workflows/docs.yml.

Rust API docs: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps must stay clean.

Changelog

Add entries under ## [Unreleased] at the top of CHANGELOG.md (create the heading if it is not there), and a line in "Migration" for anything breaking.