use std::fmt;
use crate::types::{Ecosystem, PackageManager};
#[derive(Debug)]
pub(crate) enum ResolveError {
NoSignalsFound {
ecosystem: Ecosystem,
soft: bool,
},
DevEnginesFailHard {
pm: PackageManager,
reason: DevEnginesFailReason,
},
MismatchPolicyError {
declared: PackageManager,
field: &'static str,
lockfile: PackageManager,
},
InvalidOverride {
value: String,
reason: &'static str,
},
ConflictingFailurePolicy {
source: &'static str,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum DevEnginesFailReason {
BinaryMissing,
VersionMismatch {
declared: String,
actual: String,
},
}
impl fmt::Display for ResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoSignalsFound { ecosystem, soft } => {
let suffix = if *soft { "" } else { " (--fallback=error)" };
write!(
f,
"no {} package manager detected{suffix}. Checked: lockfiles, manifest \
(packageManager + devEngines), PATH. Pin one with `--pm <name>`, set \
`RUNNER_PM=<name>`, add it to runner.toml, or install a supported PM.",
ecosystem.label(),
)
}
Self::DevEnginesFailHard { pm, reason } => match reason {
DevEnginesFailReason::BinaryMissing => write!(
f,
"devEngines.packageManager declares {} but it was not found on PATH \
(onFail=error)",
pm.label(),
),
DevEnginesFailReason::VersionMismatch { declared, actual } => write!(
f,
"devEngines.packageManager requires {} {declared} but the installed version \
is {actual} (onFail=error)",
pm.label(),
),
},
Self::MismatchPolicyError {
declared,
field,
lockfile,
} => write!(
f,
"{field} declares {} but the lockfile reflects {} (--on-mismatch=error)",
declared.label(),
lockfile.label(),
),
Self::InvalidOverride { value, reason } => {
write!(f, "invalid override value {value:?}: {reason}")
}
Self::ConflictingFailurePolicy { source } => write!(
f,
"`keep_going` and `kill_on_fail` are mutually exclusive but both were set ({source}). \
Unset one of `--keep-going` / `RUNNER_KEEP_GOING` / `[chain].keep_going` or \
`--kill-on-fail` / `RUNNER_KILL_ON_FAIL` / `[chain].kill_on_fail` to pick a policy.",
),
}
}
}
impl std::error::Error for ResolveError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conflicting_failure_policy_display_includes_source() {
let err = ResolveError::ConflictingFailurePolicy { source: "env vars" };
let msg = format!("{err}");
assert!(msg.contains("keep_going"), "msg: {msg}");
assert!(msg.contains("kill_on_fail"), "msg: {msg}");
assert!(msg.contains("env vars"), "msg: {msg}");
}
}