Build the Rust crate on its MSRV, without its default feature, and as docs.rs would

Merged
JE
2mo ago
+224-26

First of three CI-hardening pull requests for the Rust crate (card: hey-sdk Rust: CI hardening). This one is the build side; cargo deny and the API-compatibility gate follow on top of it.

What changes

test-rust installs the toolchain from the new rust-toolchain.toml and runs make rs-check, which is now fmt, clippy, tests and docs with --all-features, then clippy and the tests again with --no-default-features. The same target a developer runs locally, in the same order.

msrv-rust reads rust-version out of rust/Cargo.toml and builds the library on exactly that toolchain, both feature sets. Only the library: the generator and the conformance runner are tooling and float on the pinned stable. A bump to rust-version is what moves this job; the job fails if it can't find the field.

docs-rust builds the docs on nightly with RUSTDOCFLAGS="--cfg docsrs -D warnings", which is what docs.rs will do with the crate. Not proposed as a required check; nightly is nightly.

rust-toolchain.toml pins an exact stable (1.98.1, what .mise.toml resolves 1.98 to) with clippy and rustfmt. The stable jobs install from it with a bare rustup toolchain install; the MSRV and nightly jobs set RUSTUP_TOOLCHAIN, which outranks the file. It sits at the repo root rather than under rust/ because conformance/runner/rust is a second workspace outside that directory.

--no-default-features

The crate's one feature is the shipped reqwest transport, and without it the test suite had no way to reach wiremock. tests/support now builds every client with an explicit http_client: the shipped ReqwestClient when the feature is on, and otherwise a minimal DevTransport over the reqwest dev-dependency (no redirects, streamed body). So under --no-default-features the whole integration suite runs through a transport of the caller's own, which is the thing the feature exists to allow. Tests that are specifically about the shipped client — ReqwestClient::from_builder, the OAuth wire tests that use OAuthClient::default(), the messages/entries unit tests that build a default client — are gated on the feature. Doctests are skipped in that configuration; they show the default client on purpose.

Verified locally: make rs-check green on this branch; cargo build -p hey-sdk --locked green on 1.88.0 with the committed lockfile.

Not in this PR

  • cargo deny, Dependabot cargo, Trivy over Cargo.lock — next PR.
  • cargo semver-checks and cargo package in the gate — third PR.
  • cargo build --examples — lands with the examples (docs card).
  • CodeQL for Rust is already on: the repo's default setup analyses actions, go and rust.
  • --all-features and the default set are the same build today (one feature); when a second feature lands the matrix gains a leg.

Merge order

This PR is part of a five-PR stack, each rebased on the one below and all five on current main, so they merge cleanly in this order and only this order: #155 (build/MSRV/feature matrix) → #157 (cargo deny, Dependabot, Trivy) → #159 (semver-checks, packaging) → #160 (docs, examples, rustfmt) → #162 (crates.io publishing). Every one of them touches .github/workflows/test.yml and/or the Makefiles; the stacking is the conflict resolution. Merge each one after the one below has landed; GitHub retargets the next PR to main when the merged branch is deleted. #162 can merge before the crates.io bootstrap: it publishes nothing until the crate exists on crates.io (its publish job fails closed with instructions on a tag until then), and nothing in it changes after the bootstrap.