use std::fmt;
use std::io;
use std::path::PathBuf;
#[derive(Debug)]
pub enum Error {
Session(Outcome),
Io {
source: io::Error,
context: String,
path: Option<PathBuf>,
},
TomlParse {
path: PathBuf,
message: String,
},
JsonParse {
context: String,
message: String,
},
SubprocessSpawn {
program: String,
source: io::Error,
},
Cli {
clap_exit_code: i32,
message: String,
},
CompatPatchOverrideConflict {
crate_name: String,
upstream_entry: String,
expected_resolution: String,
},
OverlayMirrorFailed {
upstream_entry: PathBuf,
staged_target: PathBuf,
stage: String,
source: Option<io::Error>,
},
}
#[derive(Debug, Clone)]
pub enum Outcome {
ConfigInvalid {
message: String,
},
DylibBuildFailed {
invocation: String,
stderr: String,
},
DylibNotFound {
invocation: String,
crate_name: String,
},
ToolchainDrift {
original: String,
current: String,
},
FreshnessDrift {
invariant: String,
detail: String,
},
}
impl Outcome {
pub fn exit_code(&self) -> crate::exit::ExitCode {
match self {
Self::ConfigInvalid { .. } => crate::exit::ExitCode::ConfigInvalid,
Self::DylibBuildFailed { .. } => crate::exit::ExitCode::DylibBuildFailed,
Self::DylibNotFound { .. } => crate::exit::ExitCode::DylibNotFound,
Self::ToolchainDrift { .. } => crate::exit::ExitCode::ToolchainDrift,
Self::FreshnessDrift { .. } => crate::exit::ExitCode::ToolchainDrift,
}
}
}
impl fmt::Display for Outcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ConfigInvalid { message } => {
write!(f, "lihaaf: configuration invalid:\n{message}")
}
Self::DylibBuildFailed { invocation, stderr } => {
write!(
f,
"lihaaf: dylib build failed.\n invocation: {invocation}\n cargo stderr:\n{stderr}"
)
}
Self::DylibNotFound {
invocation,
crate_name,
} => {
write!(
f,
"lihaaf: cargo succeeded but emitted no `compiler-artifact` message naming the `dylib` artifact for `{crate_name}`.\n invocation: {invocation}\n Re-check `dylib_crate` in `[package.metadata.lihaaf]` matches a workspace member."
)
}
Self::ToolchainDrift { original, current } => {
let indent = |s: &str| {
s.lines()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
};
write!(
f,
"lihaaf: rustc toolchain drifted mid-session.\n original (at dylib build):\n{}\n current (at dispatch):\n{}\nRe-run `cargo lihaaf` to rebuild against the current toolchain.",
indent(original),
indent(current),
)
}
Self::FreshnessDrift { invariant, detail } => {
write!(
f,
"lihaaf: freshness invariant `{invariant}` drifted mid-session.\n {detail}\nRe-run `cargo lihaaf` to rebuild against the current dylib + toolchain."
)
}
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Session(o) => write!(f, "{o}"),
Self::Io {
source,
context,
path,
} => match path {
Some(p) => write!(f, "{context}: {} ({source})", p.display()),
None => write!(f, "{context} ({source})"),
},
Self::TomlParse { path, message } => {
write!(f, "failed to parse {}: {message}", path.display())
}
Self::JsonParse { context, message } => {
write!(f, "failed to parse JSON ({context}): {message}")
}
Self::SubprocessSpawn { program, source } => {
write!(f, "failed to spawn `{program}`: {source}")
}
Self::Cli { message, .. } => write!(f, "{message}"),
Self::CompatPatchOverrideConflict {
crate_name,
upstream_entry,
expected_resolution,
} => {
write!(
f,
"compat overlay: upstream [patch.crates-io.{crate_name}] entry {upstream_entry} cannot be safely overwritten.\n {expected_resolution}"
)
}
Self::OverlayMirrorFailed {
upstream_entry,
staged_target,
stage,
source,
} => match source {
Some(e) => write!(
f,
"compat overlay mirror failed in stage `{stage}` mirroring `{}` → `{}` ({e})",
upstream_entry.display(),
staged_target.display()
),
None => write!(
f,
"compat overlay mirror failed in stage `{stage}` mirroring `{}` → `{}`",
upstream_entry.display(),
staged_target.display()
),
},
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source),
Self::SubprocessSpawn { source, .. } => Some(source),
Self::OverlayMirrorFailed {
source: Some(s), ..
} => Some(s),
_ => None,
}
}
}
impl Error {
pub fn io(source: io::Error, context: impl Into<String>, path: Option<PathBuf>) -> Self {
Self::Io {
source,
context: context.into(),
path,
}
}
pub fn overlay_mirror_failed(
upstream_entry: PathBuf,
staged_target: PathBuf,
stage: impl Into<String>,
source: Option<io::Error>,
) -> Self {
Self::OverlayMirrorFailed {
upstream_entry,
staged_target,
stage: stage.into(),
source,
}
}
}
impl Error {
pub fn exit_code(&self) -> crate::exit::ExitCode {
match self {
Self::Session(o) => o.exit_code(),
Self::Cli {
clap_exit_code: 0, ..
} => crate::exit::ExitCode::Ok,
_ => crate::exit::ExitCode::ConfigInvalid,
}
}
}
#[allow(dead_code)] pub type Result<T> = std::result::Result<T, Error>;