use serde_json::Value;
#[ohno::error]
#[from(
LoadMetadataError,
InvalidThresholdValueError,
ThresholdOutOfRangeError,
InvalidNoCoverableLinesValueError,
ConflictingCoverageMetadataError,
WorkspaceTargetPolicyError,
InvalidTargetTableError,
InvalidTargetPolicyShapeError,
MissingTargetPolicyBehaviorError,
InvalidTargetSelectorError,
UnsupportedTargetSelectorError,
AmbiguousTargetPolicyError,
WorkspaceScopedNoCoverableLinesError,
ResolveTargetError,
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("coverage-gate target policies are package-scoped and cannot be set in workspace metadata")]
pub(crate) struct WorkspaceTargetPolicyError;
#[ohno::error]
#[display("{source}: coverage-gate `target` must be a table keyed by target triple or cfg expression")]
pub(crate) struct InvalidTargetTableError {
pub(crate) source: String,
}
#[ohno::error]
#[display("{source}: coverage-gate target policy must be a table")]
pub(crate) struct InvalidTargetPolicyShapeError {
pub(crate) source: String,
}
#[ohno::error]
#[display("{source}: target policy must set `min-lines-percent` or `expect-no-coverable-lines = true`")]
pub(crate) struct MissingTargetPolicyBehaviorError {
pub(crate) source: String,
}
#[ohno::error]
#[display("{source}: invalid coverage-gate target selector `{selector}`")]
#[from(cargo_platform::ParseError)]
pub(crate) struct InvalidTargetSelectorError {
pub(crate) source: String,
pub(crate) selector: String,
}
#[ohno::error]
#[display("{source}: coverage-gate target selector `{selector}` uses unsupported build-context configuration options: {options}")]
pub(crate) struct UnsupportedTargetSelectorError {
pub(crate) source: String,
pub(crate) selector: String,
pub(crate) options: String,
}
#[ohno::error]
#[display(
"{source}: multiple coverage-gate target policies match `{target}`: {selectors}; \
use disjoint cfg expressions or an exact Rust target-triple override"
)]
pub(crate) struct AmbiguousTargetPolicyError {
pub(crate) source: String,
pub(crate) target: String,
pub(crate) selectors: String,
}
#[ohno::error]
#[display("failed to resolve Rust target")]
#[from(ExecuteRustcError, RustcCommandFailedError, MissingRustcHostTargetError, InvalidRustcCfgError)]
pub(crate) struct ResolveTargetError;
#[ohno::error]
#[display("could not execute `{command}`")]
#[from(std::io::Error)]
pub(crate) struct ExecuteRustcError {
pub(crate) command: String,
}
#[ohno::error]
#[display("`{command}` exited with {status}: {stderr}")]
pub(crate) struct RustcCommandFailedError {
pub(crate) command: String,
pub(crate) status: String,
pub(crate) stderr: String,
}
#[ohno::error]
#[display("`{command}` did not report a host target")]
pub(crate) struct MissingRustcHostTargetError {
pub(crate) command: String,
}
#[ohno::error]
#[display("rustc reported invalid cfg `{value}` for Rust target `{target}`")]
#[from(cargo_platform::ParseError)]
pub(crate) struct InvalidRustcCfgError {
pub(crate) value: String,
pub(crate) target: String,
}
#[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 execute_rustc_error_preserves_io_source() {
let error = ExecuteRustcError::caused_by("rustc -vV".to_owned(), std::io::Error::other("launch failed"));
let source = std::error::Error::source(&error).expect("execute error must retain its IO source");
assert_eq!(source.to_string(), "launch failed");
}
#[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"));
}
#[test]
fn ambiguous_target_policy_names_target_and_selectors() {
let err = AmbiguousTargetPolicyError::new(
"alpha".to_owned(),
"x86_64-pc-windows-msvc".to_owned(),
"cfg(windows), cfg(target_os = \"windows\")".to_owned(),
);
let rendered = err.to_string();
assert!(rendered.contains("alpha"));
assert!(rendered.contains("x86_64-pc-windows-msvc"));
assert!(rendered.contains("cfg(windows)"));
}
}