Python-shaped input becomes typed Rust only in crates/config, which reads config.yaml with litellm-python-compat, and crates/python-bridge, which reads live Python objects through PyO3. A crate that stands in for a Python library, like redis-resolve for redis-py, keeps that library's semantics because they are its compatibility contract. The skill names the code that still coerces past the boundary, so it moves there when touched instead of growing
3.6 KiB
Rust workspace rules
For keeping secrets out of Debug output, follow .agents/skills/rust-redaction/SKILL.md
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 error types, error variants and conversions between crate errors, follow .agents/skills/rust-error/SKILL.md
For where config.yaml values, environment layering and kwargs from Python become typed Rust, follow .agents/skills/rust-python-boundary/SKILL.md
The process-wide rustls CryptoProvider (ring) is installed once by the host. rust-proxy does it at the top of main through litellm_gateway::install_crypto_provider, and python-bridge must do the same in its module init before it builds any rustls config. Library crates never install it. The AWS SDK links aws-lc-rs next to ring, so without an installed default the first rustls config a library builds, such as redis-rs TLS, panics
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; - Tests live inline by default, in a
#[cfg(test)] mod tests { ... }at the bottom of the file that owns the code under test, whether or not they touch private items crates/<crate>/tests/is only for tests that drive several modules together end to end, such as a full request through a route. A test of one type or function never goes there- A helper shared by several inline test modules goes in a
#[cfg(test)] mod test_support;at the crate root, and only once a second module needs it - A test for another crate's item belongs in that crate, not in a downstream one
- Tests that call a real provider go in
crates/<crate>/tests/live/<provider>.rsbehind atests/live/main.rs. Each reads the provider's key from the environment through anOptionfixture and returns early when it is unset, never#[ignore], socargo teststays offline until the key is exported - 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