pub type Result<T> = std::result::Result<T, TixError>;
pub fn render_diagnostics<'a>(pairs: impl IntoIterator<Item = (&'a str, &'a str)>) -> String {
let mut seen: Vec<(&str, &str)> = Vec::new();
let mut rendered: Vec<String> = Vec::new();
for (kind, message) in pairs {
if seen.iter().any(|(k, m)| *k == kind && *m == message) {
continue;
}
seen.push((kind, message));
rendered.push(if message.starts_with(kind) {
message.to_string()
} else {
format!("{kind}: {message}")
});
}
rendered.join(" | ")
}
#[derive(Debug, thiserror::Error)]
pub enum TixError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("transition rejected {0}")]
Workflow(crate::workflow::WorkflowDiagnostic),
#[error("config error: {0}")]
Config(String),
#[error("parse error: {0}")]
Parse(String),
#[error("not found: {0}")]
NotFound(String),
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("editor error: {0}")]
Editor(String),
#[error("external tool error: {0}")]
External(String),
#[error("kitty error: {0}")]
Kitty(String),
}
impl TixError {
pub fn with_path(self, path: impl std::fmt::Display) -> Self {
match self {
TixError::Io(e) => TixError::Io(e),
TixError::Config(msg) => TixError::Config(format!("{path}: {msg}")),
TixError::Parse(msg) => TixError::Parse(format!("{path}: {msg}")),
TixError::NotFound(msg) => TixError::NotFound(format!("{path}: {msg}")),
other => other,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn result_alias_resolves_to_tix_error() {
let err: TixError = TixError::InvalidInput("nope".into());
let r: Result<()> = Err(err);
assert!(r.is_err());
}
#[test]
fn config_error_carries_path_when_prefixed() {
let e = TixError::Config("bad keys".into()).with_path("tix.toml");
assert_eq!(e.to_string(), "config error: tix.toml: bad keys");
}
}