* refactor(rust): derive strum VariantArray and string conversions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(rust): use strum conversions directly with explicit spellings Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(rust): use rstest values for key management systems Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
3.9 KiB
Rust workspace rules
For diagnostic tracing changes, follow .agents/skills/rust-tracing/SKILL.md
For string-valued enums and their Serde conversions, follow .agents/skills/rust-string-enums/SKILL.md
For fieldless enums, derive strum::VariantArray and use VARIANTS instead of a hand-listed ALL array; derive Strum string conversions instead of hand-written variant-to-string matches
Use the derived conversions directly (<&'static str>::from(x) / .into(), str::parse) with no as_str/parse wrapper that only delegates, and spell each variant with explicit #[strum(serialize = "...")] instead of serialize_all
Test placement
- Never create a
tests.rs(ortest.rs) file undersrc/, and never#[path = "tests.rs"] mod tests; - A test that reaches private items lives inline, in a
#[cfg(test)] mod tests { ... }at the bottom of the file that owns those items - A test that only uses the crate's public API lives in
crates/<crate>/tests/<subject>.rs, next tosrc/ - Split a mixed test file along that line instead of widening visibility to move it
- A test for another crate's item belongs in that crate, not in a downstream one
- Never set
autotests = falseor hand-list[[test]]targets; every file directly undertests/is discovered by cargo, and a shared helper goes intests/<name>/mod.rsortests/<subject>/support.rsso it is not picked up as a test crate of its own
Test fixtures and cases
Use #[rstest] for new and updated tests and #[fixture] for reusable setup, injected through typed test arguments. Express input variations as named #[case::name(...)] cases instead of loops or duplicated tests so each failure identifies its case. Keep behavior assertions in the test body and fixtures focused on setup. Use the workspace rstest dependency
- Never loop over inputs (
for,.iter().for_each,.all) inside a test body; give each input its own#[case::name(...)], or use#[values(...)]for a cross product - Exception: a test pinning a Rust table against a repo-owned data file (for example
include_str!of a JSON config) may iterate that file's entries
Error definitions
- A crate's errors live in
src/error.rs, defined withthiserror, and re-exported fromlib.rs - Put message templates in the variant's
#[error(...)]declaration. Callers pass only the small typed arguments needed to fill them, neverError::Variant(format!(...))or a preformatted message. Keep the smallest set of neutral variants that callers need to distinguish; different wording or providers do not justify new variants - Default to one top-level
Errorenum per crate, with one variant per failure mode and a#[error(...)]message on each. A failure mode is something a caller handles differently (phase, status code, retry, a message Python parity pins exactly); failures no caller tells apart share one variant and differ only in its message - Keep shared error enums minimal and provider-neutral. Provider names, credential types, configuration fields, and setup guidance belong in caller-supplied data, not dedicated variants or hardcoded shared messages. Reuse a variant for the same failure mode across providers, such as
MissingApiBase { provider: "Azure", guidance: "..." }. An exact parity message does not justify a provider-specific variant when caller-supplied context can preserve it - Wrap a lower-level error as a variant with
#[from]or#[source]instead of flattening it to a string - Exception: split into separate types when different functions fail in disjoint ways, especially when different callers see them. A shared enum would force every caller to match variants its function can never return
- Name a split type after what went wrong (a unit struct is fine for a single failure mode), not after the function that returns it