13 breaking releases
Uses new Rust 2024
| 0.14.0 | Mar 6, 2026 |
|---|---|
| 0.12.0 | Feb 28, 2026 |
#535 in Command line utilities
1MB
16K
SLoC
cargo-aprz
A cargo tool to appraise the quality of Rust dependencies.
- Background
- Installation
- Quick Start
- Data Sources
- Crates and Dependencies
- Reports
- Configuration and Expressions
- Troubleshooting
- Collected Metrics
Background
Building modern applications usually involves integrating a large number of third-party dependencies. While these dependencies can provide valuable functionality and accelerate development, they also introduce risks related to quality, security vulnerabilities, future compatibility, and more.
Before taking a dependency in your project, it's useful to vet whether that dependency meets some baseline quality standards. For example, maybe you believe in having excellent unit test coverage for your projects, but if you pull in some dependency which has no tests, it can undermine the overall quality of your application.
cargo-aprz lets you appraise the quality of dependencies. For any given crate, it collects a large number
of metrics, such as the number of open issues, the frequency of releases, the existence of security advisories,
the number of examples, the code coverage percentage, and many more. You can view nice reports showing you
all of these metrics in an easy to consume form.
You can also use cargo-aprz to automatically evaluate whether a crate meets your quality standards. You
do this by writing a set of expressions that operate on the collected metrics. For example, you can have an
expression that says "if code coverage is less than 20%, treat this crate as not being acceptable as a dependency".
You can run cargo-aprz by specifying a set of crates to evaluate, or you can run it on the full transitive set of
dependencies of an existing Rust project.
Installation
cargo install --locked cargo-aprz
Quick Start
-
Generate a default configuration file:
cargo aprz initThis creates
aprz.tomlwhich lets you control various options. This is where you define expressions that let you evaluate the relative quality of a crate by inspecting its metrics. -
Get the metrics associated with the latest version of a crate:
cargo aprz crates tokioThe first time you run this command, it will take a while as it needs to download a large database from crates.io along with the
RustSecadvisory database. This data is cached such that subsequent runs will be much faster. -
Get the metrics associated with the dependencies of a Rust project:
cargo aprz deps -
Get the metrics for specific versions of crates:
cargo aprz crates tokio@1.40.0 serde@1.0.0 -
Get the metrics for a crate and produce an HTML report instead of outputting to the console:
cargo aprz crates tokio@1.40.0 --html report.html
Data Sources
cargo-aprz collects data from these sources:
-
crates.io: Provides metadata and download statistics for each crate.
-
GitHub or Codeberg: Provide information about the popularity of a crate, the number of issues and pull requests, the frequency of commits, and more. This is also where
cargo-aprzgets source code in order to analyze the code quality of a crate. -
RustSecAdvisory Database: Provides information about known vulnerabilities in Rust crates. -
docs.rs: Provides information about the quality of documentation for a crate, such as the presence of examples, the number of items with documentation comments, and more.
-
codecov.io: Provides code coverage information.
Crates and Dependencies
cargo-aprz can be used to appraise the quality of specific crates, or the quality of the dependencies of an existing Rust project.
When you run cargo-aprz crates, you specify a set of crates to appraise with or without a version number. For example:
cargo aprz crates tokio serde@1.0.1
When you run cargo-aprz deps, it will appraise the quality of the dependencies of the Rust project in the current directory.
cargo aprz deps --dependency-types standard
Dependency Types
The --dependency-types option accepts a comma-separated list of dependency types to include in the appraisal. Possible values are:
standard: Only include the standard dependencies of the project.dev: Only include the development dependencies of the project.build: Only include the build dependencies of the project.
Package & Feature Selection
When using the deps command, you can use the usual cargo options to control precisely which package and feature to consider. The available options include:
--manifest-path <PATH>: Path to theCargo.tomlfile of the project to analyze. By default, it looks forCargo.tomlin the current directory.--features: A comma-separated list of features to activate.--no-default-features: Do not activate thedefaultfeature.--all-features: Activate all available features.--package: Appraise the dependencies of a specific package in a workspace.--workspace: Appraise the dependencies of all packages in a workspace.
Tokens
cargo-aprz accesses he GitHub or Codeberg API to collect data about a crate. Although these APIs can be used without any form of authentication, this
results in very low rate limits. If cargo-aprz detects it is being throttled by the API, it will enter a retry loop where it will wait until it is safe
to try the operation again.
When using the deps command on a large project, it's likely you'll hit these rate limits, which can make the process take hours to complete fully.
In such a case, you can provide a GitHub or Codebarg token on the command-line or through environment variables, which gives you substantially higher
rate limits.
cargo aprz deps --github-token <GITHUB_TOKEN> --codeberg-token <CODEBERG_TOKEN>
You can also set the GITHUB_TOKEN and CODEBERG_TOKEN environment variables, which cargo-aprz will automatically pick up.
Reports
When you run cargo-aprz, it collects the many metrics listed below and then proceeds to generate a report
that shows all the collected metrics. The report can be in a variety of formats including HTML and JSON.
By default, the report is simply printed to the console.
cargo aprz crates tokio # Terminal output (default)
cargo aprz crates tokio --console # Terminal output (explicit)
cargo aprz crates tokio --html report.html # HTML report
cargo aprz crates tokio --json report.json # JSON data
cargo aprz crates tokio --csv report.csv # CSV file
cargo aprz crates tokio --excel report.xlsx # Excel spreadsheet
Configuration and Expressions
You can configure cargo-aprz by creating an aprz.toml file in the current directory. This file lets you define the set of expressions that the tool uses in order
to assess whether a crate is acceptable or not acceptable to use as a dependency. The --config option lets you specify an arbitrary path to the configuration file
instead of the default.
cargo-aprz uses the CEL expression language. This is a flexible, general-purpose expression
language that allows you to write potentially complex boolean expressions that operate on the value of collected metrics. Expressions are divided into two buckets:
-
high_risk: All expressions must evaluate to
true. If any evaluates tofalse, the crate is flagged as high risk. -
eval: Each expression has a point value (default 1). All expressions are evaluated and a score is computed as
granted_points / total_points * 100. The score is compared against configurable thresholds (medium_risk_thresholdandlow_risk_threshold) to determine whether the crate is low, medium, or high risk.
These buckets are evaluated in order. If no expressions are defined, then all crates are considered low risk.
Within these expressions, you can refer to any of the collected metrics. For example, you could write an expression that says "the crate must have 100 or fewer open issues to avoid being flagged as high risk":
[[high_risk]]
name = "Open Issues"
description = "Crate must not have too many open issues."
expression = "activity.open_issues <= 100"
Any of the metric listed in Collected Metrics below can be used in these expressions, which gives you a lot of flexibility in defining what you consider to be an acceptable or unacceptable crate.
You can also use duration() in expressions for time-based comparisons. You can assign higher point values to more
important expressions using the points field:
[[eval]]
name = "Established Crate"
description = "Accepts if the crate version was created more than 6 months ago."
expression = "stability.version_created_at < (stability.version_updated_at - duration('4320h'))" # 4320 hours = 180 days
points = 5
By default, crates scoring below 30 are high risk, between 30 and 70 are medium risk, and 70 or above are low risk. You can customize these thresholds:
medium_risk_threshold = 30.0
low_risk_threshold = 70.0
Expression Checks in CI
If you want to use cargo-aprz in a CI pipeline to detect if any unsavory dependencies are being added to your project, you
can use the --error-if-high-risk option to make cargo-aprz return a non-zero exit code if any of the crates being appraised are
flagged as high risk based on the configured expressions. Similarly, --error-if-medium-risk returns a non-zero exit code
if any crate is flagged as medium or high risk.
You can exempt specific crates from triggering these error exit codes by adding them to the allow list in your configuration file. Allowed crates are still evaluated and reported normally, but they won't cause a non-zero exit code. Each entry specifies a crate name and a semver version requirement:
[[allow_list]]
name = "some-crate"
version = "=1.2.3"
[[allow_list]]
name = "another-crate"
version = "^2.0"
Version requirements use standard semver syntax such as "*" (any version), "=1.2.3" (exact),
"^1.2" (compatible), "~1.2" (patch-level), or ">=1.0, <2.0" (range).
Troubleshooting
The crates and deps commands both let you specify a logging level using the --log-level option. Turning on logging can be useful
to troubleshooting connectivity problems. When logging is enabled, then normal console output is suspended.
Collected Metrics
The sections below show the full set of metrics collected.
Metadata Metrics
| Metric | Description |
|---|---|
crate.name |
Name of the crate |
crate.version |
Semantic version of the crate |
crate.description |
Description of the crate's purpose and use |
crate.license |
SPDX license identifier constraining use of the crate |
crate.categories |
Crate categories |
crate.keywords |
Crate keywords |
crate.features |
Available crate features |
crate.repository |
URL to the crate's source code repository |
crate.homepage |
URL to the crate's homepage |
crate.minimum_rust |
Minimum Rust version (MSRV) required to compile this crate |
crate.rust_edition |
Rust edition this crate targets |
crate.owners |
List of owner usernames |
Usage Metrics
| Metric | Description |
|---|---|
usage.total_downloads |
Crate downloads across all versions |
usage.total_downloads_last_90_days |
Crate downloads across all versions in the last 90 days |
usage.version_downloads |
Crate downloads of this specific version |
usage.version_downloads_last_90_days |
Crate downloads of this specific version in the last 90 days |
usage.dependent_crates |
Number of unique crates that depend on this crate |
Stability Metrics
| Metric | Description |
|---|---|
stability.crate_created_at |
When the crate was first published to crates.io |
stability.crate_updated_at |
When the crate's metadata was last updated on crates.io |
stability.version_created_at |
When this version was first published to crates.io |
stability.version_updated_at |
When this version's metadata was last updated on crates.io |
stability.yanked |
Whether this version has been yanked from crates.io |
stability.versions_last_90_days |
Number of versions published in the last 90 days |
stability.versions_last_180_days |
Number of versions published in the last 180 days |
stability.versions_last_365_days |
Number of versions published in the last 365 days |
Community Metrics
| Metric | Description |
|---|---|
community.repo_stars |
Number of stars on the repository |
community.repo_forks |
Number of forks of the repository |
community.repo_subscribers |
Number of users watching/subscribing to the repository |
community.repo_contributors |
Number of contributors to the repository |
Activity Metrics
| Metric | Description |
|---|---|
activity.commits_last_90_days |
Number of commits to the repository in the last 90 days |
activity.commits_last_180_days |
Number of commits to the repository in the last 180 days |
activity.commits_last_365_days |
Number of commits to the repository in the last 365 days |
activity.commit_count |
Total number of commits in the repository |
activity.first_commit_at |
Timestamp of the first commit in the repository |
activity.last_commit_at |
Timestamp of the most recent commit in the repository |
activity.open_issues |
Number of currently open issues |
activity.open_issue_age_avg |
Average age in days of open issues |
activity.open_issue_age_p50 |
Median age in days of open issues |
activity.open_issue_age_p75 |
75th percentile age in days of open issues |
activity.open_issue_age_p90 |
90th percentile age in days of open issues |
activity.open_issue_age_p95 |
95th percentile age in days of open issues |
activity.issues_opened_last_90_days |
Number of issues opened in the last 90 days |
activity.issues_opened_last_180_days |
Number of issues opened in the last 180 days |
activity.issues_opened_last_365_days |
Number of issues opened in the last 365 days |
activity.issues_opened_total |
Total number of issues opened (all time) |
activity.issues_closed_last_90_days |
Number of issues closed in the last 90 days |
activity.issues_closed_last_180_days |
Number of issues closed in the last 180 days |
activity.issues_closed_last_365_days |
Number of issues closed in the last 365 days |
activity.issues_closed_total |
Total number of issues closed (all time) |
activity.closed_issue_age_avg |
Average age in days of closed issues |
activity.closed_issue_age_p50 |
Median age in days of closed issues |
activity.closed_issue_age_p75 |
75th percentile age in days of closed issues |
activity.closed_issue_age_p90 |
90th percentile age in days of closed issues |
activity.closed_issue_age_p95 |
95th percentile age in days of closed issues |
activity.closed_issue_age_last_90_days_avg |
Average age in days of issues closed in the last 90 days |
activity.closed_issue_age_last_90_days_p50 |
Median age in days of issues closed in the last 90 days |
activity.closed_issue_age_last_90_days_p75 |
75th percentile age in days of issues closed in the last 90 days |
activity.closed_issue_age_last_90_days_p90 |
90th percentile age in days of issues closed in the last 90 days |
activity.closed_issue_age_last_90_days_p95 |
95th percentile age in days of issues closed in the last 90 days |
activity.closed_issue_age_last_180_days_avg |
Average age in days of issues closed in the last 180 days |
activity.closed_issue_age_last_180_days_p50 |
Median age in days of issues closed in the last 180 days |
activity.closed_issue_age_last_180_days_p75 |
75th percentile age in days of issues closed in the last 180 days |
activity.closed_issue_age_last_180_days_p90 |
90th percentile age in days of issues closed in the last 180 days |
activity.closed_issue_age_last_180_days_p95 |
95th percentile age in days of issues closed in the last 180 days |
activity.closed_issue_age_last_365_days_avg |
Average age in days of issues closed in the last 365 days |
activity.closed_issue_age_last_365_days_p50 |
Median age in days of issues closed in the last 365 days |
activity.closed_issue_age_last_365_days_p75 |
75th percentile age in days of issues closed in the last 365 days |
activity.closed_issue_age_last_365_days_p90 |
90th percentile age in days of issues closed in the last 365 days |
activity.closed_issue_age_last_365_days_p95 |
95th percentile age in days of issues closed in the last 365 days |
activity.open_prs |
Number of currently open pull requests |
activity.open_pr_age_avg |
Average age in days of open pull requests |
activity.open_pr_age_p50 |
Median age in days of open pull requests |
activity.open_pr_age_p75 |
75th percentile age in days of open pull requests |
activity.open_pr_age_p90 |
90th percentile age in days of open pull requests |
activity.open_pr_age_p95 |
95th percentile age in days of open pull requests |
activity.prs_opened_last_90_days |
Number of pull requests opened in the last 90 days |
activity.prs_opened_last_180_days |
Number of pull requests opened in the last 180 days |
activity.prs_opened_last_365_days |
Number of pull requests opened in the last 365 days |
activity.prs_opened_total |
Total number of pull requests opened (all time) |
activity.prs_merged_last_90_days |
Number of pull requests merged in the last 90 days |
activity.prs_merged_last_180_days |
Number of pull requests merged in the last 180 days |
activity.prs_merged_last_365_days |
Number of pull requests merged in the last 365 days |
activity.prs_merged_total |
Total number of pull requests merged (all time) |
activity.prs_closed_last_90_days |
Number of pull requests closed in the last 90 days |
activity.prs_closed_last_180_days |
Number of pull requests closed in the last 180 days |
activity.prs_closed_last_365_days |
Number of pull requests closed in the last 365 days |
activity.prs_closed_total |
Total number of pull requests closed (all time) |
activity.merged_pr_age_avg |
Average age in days of merged pull requests |
activity.merged_pr_age_p50 |
Median age in days of merged pull requests |
activity.merged_pr_age_p75 |
75th percentile age in days of merged pull requests |
activity.merged_pr_age_p90 |
90th percentile age in days of merged pull requests |
activity.merged_pr_age_p95 |
95th percentile age in days of merged pull requests |
activity.merged_pr_age_last_90_days_avg |
Average age in days of pull requests merged in the last 90 days |
activity.merged_pr_age_last_90_days_p50 |
Median age in days of pull requests merged in the last 90 days |
activity.merged_pr_age_last_90_days_p75 |
75th percentile age in days of pull requests merged in the last 90 days |
activity.merged_pr_age_last_90_days_p90 |
90th percentile age in days of pull requests merged in the last 90 days |
activity.merged_pr_age_last_90_days_p95 |
95th percentile age in days of pull requests merged in the last 90 days |
activity.merged_pr_age_last_180_days_avg |
Average age in days of pull requests merged in the last 180 days |
activity.merged_pr_age_last_180_days_p50 |
Median age in days of pull requests merged in the last 180 days |
activity.merged_pr_age_last_180_days_p75 |
75th percentile age in days of pull requests merged in the last 180 days |
activity.merged_pr_age_last_180_days_p90 |
90th percentile age in days of pull requests merged in the last 180 days |
activity.merged_pr_age_last_180_days_p95 |
95th percentile age in days of pull requests merged in the last 180 days |
activity.merged_pr_age_last_365_days_avg |
Average age in days of pull requests merged in the last 365 days |
activity.merged_pr_age_last_365_days_p50 |
Median age in days of pull requests merged in the last 365 days |
activity.merged_pr_age_last_365_days_p75 |
75th percentile age in days of pull requests merged in the last 365 days |
activity.merged_pr_age_last_365_days_p90 |
90th percentile age in days of pull requests merged in the last 365 days |
activity.merged_pr_age_last_365_days_p95 |
95th percentile age in days of pull requests merged in the last 365 days |
Documentation Metrics
| Metric | Description |
|---|---|
docs.documentation |
URL to the crate's documentation |
docs.public_api_elements |
Number of public API elements (functions, structs, etc.) |
docs.undocumented_public_api_elements |
Number of public API elements without documentation |
docs.public_api_coverage_percentage |
Percentage of public API elements with documentation |
docs.crate_level_docs_present |
Whether crate-level documentation exists |
docs.broken_links |
Number of broken links in documentation |
docs.examples_in_docs |
Number of code examples in documentation |
docs.standalone_examples |
Number of standalone example programs in the codebase |
Advisory Metrics
| Metric | Description |
|---|---|
advisories.total_low_severity_vulnerabilities |
Number of low severity vulnerabilities across all versions |
advisories.total_medium_severity_vulnerabilities |
Number of medium severity vulnerabilities across all versions |
advisories.total_high_severity_vulnerabilities |
Number of high severity vulnerabilities across all versions |
advisories.total_critical_severity_vulnerabilities |
Number of critical severity vulnerabilities across all versions |
advisories.total_notice_warnings |
Number of notice warnings across all versions |
advisories.total_unmaintained_warnings |
Number of unmaintained warnings across all versions |
advisories.total_unsound_warnings |
Number of unsound warnings across all versions |
advisories.version_low_severity_vulnerabilities |
Number of low severity vulnerabilities in this version |
advisories.version_medium_severity_vulnerabilities |
Number of medium severity vulnerabilities in this version |
advisories.version_high_severity_vulnerabilities |
Number of high severity vulnerabilities in this version |
advisories.version_critical_severity_vulnerabilities |
Number of critical severity vulnerabilities in this version |
advisories.version_notice_warnings |
Number of notice warnings for this version |
advisories.version_unmaintained_warnings |
Number of unmaintained warnings for this version |
advisories.version_unsound_warnings |
Number of unsound warnings for this version |
Code Metrics
| Metric | Description |
|---|---|
code.source_files |
Number of source files |
code.source_files_with_errors |
Number of source files that had analysis errors |
code.code_lines |
Number of lines of production code (excluding tests) |
code.test_lines |
Number of lines of test code |
code.comment_lines |
Number of comment lines in the codebase |
code.transitive_dependencies |
Number of transitive dependencies |
Trustworthiness Metrics
| Metric | Description |
|---|---|
trust.unsafe_blocks |
Number of unsafe blocks in the codebase |
trust.ci_workflows |
Whether CI/CD workflows were detected in the repository |
trust.miri_usage |
Whether Miri is used in CI |
trust.clippy_usage |
Whether Clippy is used in CI |
trust.code_coverage_percentage |
Percentage of code covered by tests |
Dependencies
~59–100MB
~1.5M SLoC