use crate::diagnostics::Diagnostic;
pub type Result<T, E = TixError> = std::result::Result<T, E>;
pub fn render_diagnostics<'a>(
id: &'static str,
pairs: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> String {
let mut seen: Vec<(&str, &str)> = Vec::new();
let mut rendered: Vec<Diagnostic> = Vec::new();
for (kind, message) in pairs {
if seen.iter().any(|(k, m)| *k == kind && *m == message) {
continue;
}
seen.push((kind, message));
let text = if message.starts_with(kind) {
message.to_string()
} else {
format!("{kind}: {message}")
};
rendered.push(Diagnostic::error(id, text));
}
crate::diagnostics::render_all(&rendered)
}
#[must_use]
pub fn render_diagnostics_plain<'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(" | ")
}
#[must_use]
pub fn render_error(err: &TixError) -> String {
if let TixError::Refused(block) = err {
return block.clone();
}
Diagnostic::error(err.diagnostic_id(), err.to_string()).render()
}
#[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("{0}")]
Refused(String),
#[error("editor error: {0}")]
Editor(String),
#[error("external tool error: {0}")]
External(String),
#[error("kitty error: {0}")]
Kitty(String),
}
impl TixError {
#[must_use]
pub fn diagnostic_id(&self) -> &'static str {
match self {
TixError::Io(_) => "E-tix-io",
TixError::Workflow(_) => "E-tix-workflow",
TixError::Config(_) => "E-tix-config",
TixError::Parse(_) => "E-tix-parse",
TixError::NotFound(_) => "E-tix-not-found",
TixError::InvalidInput(_) => "E-tix-invalid-input",
TixError::Refused(_) => "E-tix-refused",
TixError::Editor(_) => "E-tix-editor",
TixError::External(_) => "E-tix-external",
TixError::Kitty(_) => "E-tix-kitty",
}
}
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");
}
}