use serde_json::Value;
#[ohno::error]
#[from(
LoadMetadataError,
InvalidThresholdValueError,
ThresholdOutOfRangeError,
InvalidNoCoverableLinesValueError,
ConflictingCoverageMetadataError,
WorkspaceScopedNoCoverableLinesError,
ParseLcovError,
ReadLcovError,
UnknownPackageSelectorError
)]
pub struct CoverageGateError;
#[ohno::error]
#[display("failed to load workspace metadata")]
#[from(cargo_metadata::Error)]
pub(crate) struct LoadMetadataError;
#[ohno::error]
#[display("{source}: `coverage-gate.min-lines-percent` must be a number, got {min}")]
pub(crate) struct InvalidThresholdValueError {
pub source: String,
pub min: Value,
}
#[ohno::error]
#[display(
"invalid coverage-gate min-lines-percent value `{value}` for {source}: \
expected a value in {lower:.1}..={upper:.1}"
)]
pub(crate) struct ThresholdOutOfRangeError {
pub source: String,
pub value: f64,
pub lower: f64,
pub upper: f64,
}
#[ohno::error]
#[display("{source}: `coverage-gate.expect-no-coverable-lines` must be a boolean, got {value}")]
pub(crate) struct InvalidNoCoverableLinesValueError {
pub source: String,
pub value: Value,
}
#[ohno::error]
#[display(
"{source}: `coverage-gate` cannot set both `min-lines-percent` and \
`expect-no-coverable-lines`; pick one"
)]
pub(crate) struct ConflictingCoverageMetadataError {
pub source: String,
}
#[ohno::error]
#[display(
"`coverage-gate.expect-no-coverable-lines` is a package-level assertion and \
cannot be set in `[workspace.metadata.coverage-gate]`"
)]
pub(crate) struct WorkspaceScopedNoCoverableLinesError;
#[ohno::error]
#[display("lcov tracefile is not well-formed")]
#[from(lcov::report::ParseError)]
pub(crate) struct ParseLcovError;
#[ohno::error]
#[display("failed to read lcov tracefile `{path}`")]
pub(crate) struct ReadLcovError {
pub path: String,
}
#[ohno::error]
#[display("`--package` selector `{selector}` did not match any workspace member")]
pub(crate) struct UnknownPackageSelectorError {
pub selector: String,
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn umbrella_propagates_load_metadata_chain() {
let inner = LoadMetadataError::caused_by(std::io::Error::other("no manifest"));
let outer: CoverageGateError = inner.into();
let rendered = outer.to_string();
assert!(rendered.contains("failed to load workspace metadata"));
assert!(rendered.contains("no manifest"));
}
#[test]
fn umbrella_propagates_parse_lcov() {
let inner = ParseLcovError::new();
let outer: CoverageGateError = inner.into();
let rendered = outer.to_string();
assert!(rendered.contains("lcov tracefile"));
}
#[test]
fn unknown_package_selector_carries_pattern() {
let err = UnknownPackageSelectorError::new("nope-*".to_owned());
let rendered = err.to_string();
assert!(rendered.contains("nope-*"));
assert!(rendered.contains("did not match"));
}
#[test]
fn threshold_out_of_range_renders_value_and_bounds() {
let err = ThresholdOutOfRangeError::new("alpha".to_owned(), 150.0, 0.0, 100.0);
let rendered = err.to_string();
assert!(rendered.contains("150"));
assert!(rendered.contains("alpha"));
assert!(rendered.contains("0.0..=100.0"));
}
#[test]
fn invalid_no_coverable_lines_value_renders_source_and_value() {
let err = InvalidNoCoverableLinesValueError::new("alpha".to_owned(), Value::from("yes"));
let rendered = err.to_string();
assert!(rendered.contains("alpha"));
assert!(rendered.contains("expect-no-coverable-lines"));
assert!(rendered.contains("boolean"));
assert!(rendered.contains("yes"));
}
#[test]
fn conflicting_coverage_metadata_renders_source() {
let err = ConflictingCoverageMetadataError::new("alpha".to_owned());
let rendered = err.to_string();
assert!(rendered.contains("alpha"));
assert!(rendered.contains("min-lines-percent"));
assert!(rendered.contains("expect-no-coverable-lines"));
}
#[test]
fn workspace_scoped_no_coverable_lines_mentions_workspace() {
let err = WorkspaceScopedNoCoverableLinesError::new();
let rendered = err.to_string();
assert!(rendered.contains("expect-no-coverable-lines"));
assert!(rendered.contains("workspace.metadata.coverage-gate"));
}
}