Compare commits

..

9 Commits

Author SHA1 Message Date
dependabot[bot] f1b7cfb253
build(deps): bump tokio from 1.44.2 to 1.45.0 (#3643)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.2 to 1.45.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.2...tokio-1.45.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 20:43:47 +00:00
dependabot[bot] e983276a78
build(deps): bump taiki-e/install-action from 2.50.7 to 2.50.10 (#3641)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.50.7 to 2.50.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.50.7...v2.50.10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 20:40:46 +00:00
dependabot[bot] bbe0134523
build(deps): bump brotli from 7.0.0 to 8.0.0 (#3627)
* build(deps): bump brotli from 7.0.0 to 8.0.0

Bumps [brotli](https://github.com/dropbox/rust-brotli) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dropbox/rust-brotli/releases)
- [Commits](https://github.com/dropbox/rust-brotli/commits)

---
updated-dependencies:
- dependency-name: brotli
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-05-09 20:05:56 +00:00
dependabot[bot] 2dd165dc0b
build(deps): bump divan from 0.1.18 to 0.1.21 (#3621)
Bumps [divan](https://github.com/nvzqz/divan) from 0.1.18 to 0.1.21.
- [Changelog](https://github.com/nvzqz/divan/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nvzqz/divan/compare/v0.1.18...v0.1.21)

---
updated-dependencies:
- dependency-name: divan
  dependency-version: 0.1.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 20:05:19 +00:00
dependabot[bot] 9829f87cd1
build(deps): bump taiki-e/install-action from 2.49.50 to 2.50.7 (#3636)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.50 to 2.50.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.49.50...v2.50.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 19:32:24 +00:00
dependabot[bot] 6892896a62
build(deps): bump actions-rust-lang/setup-rust-toolchain from 1.11.0 to 1.12.0 (#3632)
build(deps): bump actions-rust-lang/setup-rust-toolchain

Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.11.0...v1.12.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 19:32:17 +00:00
dependabot[bot] bdb7b4da2d
build(deps): bump tokio-util from 0.7.14 to 0.7.15 (#3631)
Bumps [tokio-util](https://github.com/tokio-rs/tokio) from 0.7.14 to 0.7.15.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.14...tokio-util-0.7.15)

---
updated-dependencies:
- dependency-name: tokio-util
  dependency-version: 0.7.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 19:32:05 +00:00
Rob Ede 7eea3d3657
chore: address clippy lints 2025-05-09 20:21:02 +01:00
Rob Ede ad73cdc823
build: rework justfile toolchain 2025-05-09 20:15:43 +01:00
23 changed files with 83 additions and 81 deletions

View File

@ -1,3 +1,6 @@
version: "0.2"
words:
- actix
- httparse
- msrv
- rustup

View File

@ -1,10 +1,11 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
versioning-strategy: lockfile-only

View File

@ -44,12 +44,12 @@ jobs:
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -80,10 +80,10 @@ jobs:
uses: rui314/setup-mold@v1
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: Install just, cargo-hack
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just,cargo-hack

View File

@ -59,12 +59,12 @@ jobs:
uses: rui314/setup-mold@v1
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -92,7 +92,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: nightly
@ -108,12 +108,12 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just

View File

@ -18,13 +18,13 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: nightly
components: llvm-tools
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just,cargo-llvm-cov,cargo-nextest

View File

@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: nightly
components: rustfmt
@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
components: clippy
@ -55,7 +55,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: nightly
components: rust-docs
@ -72,12 +72,12 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
with:
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
- name: Install just
uses: taiki-e/install-action@v2.49.50
uses: taiki-e/install-action@v2.50.10
with:
tool: just

24
Cargo.lock generated
View File

@ -762,9 +762,9 @@ dependencies = [
[[package]]
name = "brotli"
version = "7.0.0"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@ -773,9 +773,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
version = "4.0.2"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@ -1211,9 +1211,9 @@ dependencies = [
[[package]]
name = "divan"
version = "0.1.18"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "009c56317fe2bd3b5eebe3aa888828c62ed1b085d26c1ef2079a60369795765e"
checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933"
dependencies = [
"cfg-if",
"clap",
@ -1225,9 +1225,9 @@ dependencies = [
[[package]]
name = "divan-macros"
version = "0.1.18"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4de9827ae754db91aedec0277381f5a2d8e2f801564c8d774acfe1ab1b045e"
checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323"
dependencies = [
"proc-macro2",
"quote",
@ -3102,9 +3102,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [
"backtrace",
"bytes",
@ -3210,9 +3210,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.14"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [
"bytes",
"futures-core",

View File

@ -2,6 +2,8 @@
## Unreleased
- Update `brotli` dependency to `8`.
## 3.10.0
### Added

View File

@ -139,7 +139,7 @@ sha1 = { version = "0.10", optional = true }
actix-tls = { version = "3.4", default-features = false, optional = true }
# compress-*
brotli = { version = "7", optional = true }
brotli = { version = "8", optional = true }
flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.13", optional = true }

View File

@ -31,7 +31,7 @@ async fn main() -> io::Result<()> {
actix_rt::time::sleep(Duration::from_secs(1)).await;
yield Err(io::Error::new(io::ErrorKind::Other, "abc"));
yield Err(io::Error::other("abc"));
})))
})
.tcp()

View File

@ -190,7 +190,7 @@ mod tests {
#[actix_rt::test]
async fn to_body_limit_error() {
let err_stream = stream::once(async { Err(io::Error::new(io::ErrorKind::Other, "")) });
let err_stream = stream::once(async { Err(io::Error::other("")) });
let body = SizedStream::new(8, err_stream);
// not too big, but propagates error from body stream
assert!(to_bytes_limited(body, 10).await.unwrap().is_err());

View File

@ -100,10 +100,7 @@ where
loop {
if let Some(ref mut fut) = this.fut {
let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| {
PayloadError::Io(io::Error::new(
io::ErrorKind::Other,
"Blocking task was cancelled unexpectedly",
))
PayloadError::Io(io::Error::other("Blocking task was cancelled unexpectedly"))
})??;
*this.decoder = Some(decoder);

View File

@ -183,8 +183,7 @@ where
if let Some(ref mut fut) = this.fut {
let mut encoder = ready!(Pin::new(fut).poll(cx))
.map_err(|_| {
EncoderError::Io(io::Error::new(
io::ErrorKind::Other,
EncoderError::Io(io::Error::other(
"Blocking task was cancelled unexpectedly",
))
})?

View File

@ -415,7 +415,7 @@ mod tests {
#[test]
fn test_as_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let orig = io::Error::other("other");
let err: Error = ParseError::Io(orig).into();
assert_eq!(
format!("{}", err),
@ -425,14 +425,14 @@ mod tests {
#[test]
fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let orig = io::Error::other("other");
let err = Error::new_io().with_cause(orig);
assert_eq!("connection error: other", err.to_string());
}
#[test]
fn test_error_http_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let orig = io::Error::other("other");
let err = Error::new_io().with_cause(orig);
let resp: Response<BoxBody> = err.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
@ -440,7 +440,7 @@ mod tests {
#[test]
fn test_payload_error() {
let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
let err: PayloadError = io::Error::other("ParseError").into();
assert!(err.to_string().contains("ParseError"));
let err = PayloadError::Incomplete(None);
@ -475,7 +475,7 @@ mod tests {
#[test]
fn test_from() {
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..));
from_and_cause!(io::Error::other("other") => ParseError::Io(..));
from!(httparse::Error::HeaderName => ParseError::Header);
from!(httparse::Error::HeaderName => ParseError::Header);
from!(httparse::Error::HeaderValue => ParseError::Header);

View File

@ -310,10 +310,10 @@ impl MessageType for RequestHeadType {
Version::HTTP_11 => "HTTP/1.1",
Version::HTTP_2 => "HTTP/2.0",
Version::HTTP_3 => "HTTP/3.0",
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
_ => return Err(io::Error::other("Unsupported version")),
}
)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(io::Error::other)
}
}
@ -433,7 +433,7 @@ impl TransferEncoding {
buf.extend_from_slice(b"0\r\n\r\n");
} else {
writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len())
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
.map_err(io::Error::other)?;
buf.reserve(msg.len() + 2);
buf.extend_from_slice(msg);

View File

@ -776,10 +776,7 @@ where
}
Poll::Pending => break,
Poll::Ready(Some(Err(err))) => {
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new(
io::ErrorKind::Other,
format!("{err}"),
)))));
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::other(err)))));
}
}
}
@ -795,11 +792,10 @@ where
}
Some(frm) => {
let msg = match frm {
Frame::Text(data) => {
Message::Text(ByteString::try_from(data).map_err(|err| {
ProtocolError::Io(io::Error::new(io::ErrorKind::Other, err))
})?)
}
Frame::Text(data) => Message::Text(
ByteString::try_from(data)
.map_err(|err| ProtocolError::Io(io::Error::other(err)))?,
),
Frame::Binary(data) => Message::Binary(data),
Frame::Ping(s) => Message::Ping(s),
Frame::Pong(s) => Message::Pong(s),

View File

@ -2,6 +2,8 @@
## Unreleased
- Update `brotli` dependency to `8`.
## 4.10.2
- No significant changes since `4.10.1`.

View File

@ -173,7 +173,7 @@ actix-files = "0.6"
actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
awc = { version = "3", features = ["openssl"] }
brotli = "7"
brotli = "8"
const-str = "0.5"
core_affinity = "0.8"
criterion = { version = "0.5", features = ["html_reports"] }

View File

@ -1073,10 +1073,7 @@ fn bind_addrs(addrs: impl net::ToSocketAddrs, backlog: u32) -> io::Result<Vec<ne
} else if let Some(err) = err.take() {
Err(err)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
Err(io::Error::other("Could not bind to address"))
}
}

View File

@ -2,6 +2,8 @@
## Unreleased
- Update `brotli` dependency to `8`.
## 3.6.0
- Prevent panics on connection pool drop when Tokio runtime is shutdown early.

View File

@ -141,7 +141,7 @@ actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23"] }
actix-utils = "3"
actix-web = { version = "4", features = ["openssl"] }
brotli = "7"
brotli = "8"
const-str = "0.5"
env_logger = "0.11"
flate2 = "1.0.13"

View File

@ -179,9 +179,8 @@ where
.acquire_owned()
.await
.map_err(|_| {
ConnectError::Io(io::Error::new(
io::ErrorKind::Other,
"failed to acquire semaphore on client connection pool",
ConnectError::Io(io::Error::other(
"Failed to acquire semaphore on client connection pool",
))
})?;

View File

@ -1,6 +1,8 @@
_list:
@just --list
toolchain := ""
# Format workspace.
fmt:
just --unstable --fmt
@ -10,11 +12,11 @@ fmt:
# Downgrade dependencies necessary to run MSRV checks/tests.
[private]
downgrade-for-msrv:
cargo update -p=clap --precise=4.4.18 # next ver: 1.74.0
cargo update -p=divan --precise=0.1.15 # next ver: 1.80.0
cargo update -p=litemap --precise=0.7.4 # next ver: 1.81.0
cargo update -p=zerofrom --precise=0.1.5 # next ver: 1.81.0
cargo update -p=half --precise=2.4.1 # next ver: 1.81.0
cargo {{ toolchain }} update -p=divan --precise=0.1.15 # next ver: 1.80.0
cargo {{ toolchain }} update -p=half --precise=2.4.1 # next ver: 1.81.0
cargo {{ toolchain }} update -p=litemap --precise=0.7.4 # next ver: 1.81.0
cargo {{ toolchain }} update -p=zerofrom --precise=0.1.5 # next ver: 1.81.0
cargo {{ toolchain }} update -p=idna_adapter --precise=1.2.0 # next ver: 1.82.0
msrv := ```
cargo metadata --format-version=1 \
@ -40,41 +42,43 @@ check-default:
cargo hack --workspace check
# Run Clippy over workspace.
check toolchain="": && (clippy toolchain)
check: && clippy
# Run Clippy over workspace.
clippy toolchain="":
clippy:
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
# Test workspace using MSRV.
test-msrv: downgrade-for-msrv (test msrv_rustup)
test-msrv:
@just toolchain={{ msrv_rustup }} downgrade-for-msrv
@just toolchain={{ msrv_rustup }} test
# Test workspace code.
test toolchain="":
test:
cargo {{ toolchain }} test --lib --tests -p=actix-web-codegen --all-features
cargo {{ toolchain }} test --lib --tests -p=actix-multipart-derive --all-features
cargo {{ toolchain }} nextest run --no-tests=warn -p=actix-router --no-default-features
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-web-codegen --exclude=actix-multipart-derive {{ all_crate_features }} --filter-expr="not test(test_reading_deflate_encoding_large_random_rustls)"
# Test workspace docs.
test-docs toolchain="": && doc
test-docs: && doc
cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture
# Test workspace.
test-all toolchain="": (test toolchain) (test-docs toolchain)
test-all: test test-docs
# Test workspace and collect coverage info.
[private]
test-coverage toolchain="":
test-coverage:
cargo {{ toolchain }} llvm-cov nextest --no-tests=warn --no-report {{ all_crate_features }}
cargo {{ toolchain }} llvm-cov --doc --no-report {{ all_crate_features }}
# Test workspace and generate Codecov report.
test-coverage-codecov toolchain="": (test-coverage toolchain)
test-coverage-codecov: test-coverage
cargo {{ toolchain }} llvm-cov report --doctests --codecov --output-path=codecov.json
# Test workspace and generate LCOV report.
test-coverage-lcov toolchain="": (test-coverage toolchain)
test-coverage-lcov: test-coverage
cargo {{ toolchain }} llvm-cov report --doctests --lcov --output-path=lcov.info
# Document crates in workspace.