use thiserror::Error;
use crate::agent::AgentId;
#[derive(Debug, Error)]
pub enum ProserpinaError {
#[error("agent `{agent_id}` failed: {detail}")]
AgentFailure {
agent_id: String,
detail: String,
},
#[error("no agent registered for id `{0}`")]
MissingAgent(AgentId),
#[error("no API keys found for any provider (tried: {})", .0.join(", "))]
NoAuthedProviders(Vec<String>),
#[error("summarizer failed: {detail}")]
SummaryFailed { detail: String },
#[error("malformed credentials config `{path}`: {detail}")]
MalformedCredentials {
path: String,
detail: String,
},
#[error("custom provider `{name}` is missing required field(s): {}", .missing.join(", "))]
IncompleteCustomProvider {
name: String,
missing: Vec<&'static str>,
},
#[error("unknown panel `{name}`; available: {}", .available.join(", "))]
UnknownPanel {
name: String,
available: Vec<String>,
},
#[cfg(feature = "keyring")]
#[error("keychain access failed for `{name}`: {detail}")]
KeyringAccess {
name: String,
detail: String,
},
}
impl ProserpinaError {
pub fn agent_failure(agent_id: impl Into<String>, detail: impl Into<String>) -> Self {
Self::AgentFailure {
agent_id: agent_id.into(),
detail: detail.into(),
}
}
pub fn missing_agent(id: AgentId) -> Self {
Self::MissingAgent(id)
}
pub fn no_authed_providers(tried: Vec<String>) -> Self {
Self::NoAuthedProviders(tried)
}
pub fn summary_failed(detail: impl Into<String>) -> Self {
Self::SummaryFailed {
detail: detail.into(),
}
}
pub fn malformed_credentials(path: impl Into<String>, detail: impl std::fmt::Display) -> Self {
Self::MalformedCredentials {
path: path.into(),
detail: detail.to_string(),
}
}
pub fn incomplete_custom_provider(name: impl Into<String>, missing: Vec<&'static str>) -> Self {
Self::IncompleteCustomProvider {
name: name.into(),
missing,
}
}
pub fn error_kind(&self) -> &'static str {
match self {
ProserpinaError::AgentFailure { .. } => "agent_failure",
ProserpinaError::MissingAgent(_) => "missing_agent",
ProserpinaError::NoAuthedProviders(_) => "no_authed_providers",
ProserpinaError::SummaryFailed { .. } => "summary_failed",
ProserpinaError::MalformedCredentials { .. } => "malformed_credentials",
ProserpinaError::IncompleteCustomProvider { .. } => "incomplete_custom_provider",
ProserpinaError::UnknownPanel { .. } => "unknown_panel",
#[cfg(feature = "keyring")]
ProserpinaError::KeyringAccess { .. } => "keyring_access",
}
}
pub fn exit_code(&self) -> u8 {
match self {
ProserpinaError::AgentFailure { .. } => 11,
ProserpinaError::MissingAgent(_) => 15,
ProserpinaError::NoAuthedProviders(_) => 10,
ProserpinaError::SummaryFailed { .. } => 12,
ProserpinaError::MalformedCredentials { .. } => 13,
ProserpinaError::IncompleteCustomProvider { .. } => 14,
ProserpinaError::UnknownPanel { .. } => 16,
#[cfg(feature = "keyring")]
ProserpinaError::KeyringAccess { .. } => 17,
}
}
#[cfg(feature = "json")]
pub fn to_error_json(&self) -> String {
let details = self.details_json();
let payload = serde_json::json!({
"error": {
"kind": self.error_kind(),
"message": self.to_string(),
"details": details,
}
});
serde_json::to_string(&payload)
.unwrap_or_else(|_| "{\"error\":{\"kind\":\"serialization_failed\"}}".to_owned())
}
#[cfg(feature = "json")]
fn details_json(&self) -> serde_json::Value {
match self {
ProserpinaError::AgentFailure { agent_id, detail } => serde_json::json!({
"agent_id": agent_id,
"detail": detail,
}),
ProserpinaError::MissingAgent(id) => serde_json::json!({ "agent_id": id.to_string() }),
ProserpinaError::NoAuthedProviders(tried) => serde_json::json!({
"tried": tried,
}),
ProserpinaError::SummaryFailed { detail } => serde_json::json!({
"detail": detail,
}),
ProserpinaError::MalformedCredentials { path, detail } => serde_json::json!({
"path": path,
"detail": detail,
}),
ProserpinaError::IncompleteCustomProvider { name, missing } => serde_json::json!({
"provider": name,
"missing": missing,
}),
ProserpinaError::UnknownPanel { name, available } => serde_json::json!({
"name": name,
"available": available,
}),
#[cfg(feature = "keyring")]
ProserpinaError::KeyringAccess { name, detail } => serde_json::json!({
"name": name,
"detail": detail,
}),
}
}
pub fn unknown_panel(name: impl Into<String>, available: Vec<String>) -> Self {
Self::UnknownPanel {
name: name.into(),
available,
}
}
#[cfg(feature = "keyring")]
pub fn keyring_access(name: impl Into<String>, detail: impl std::fmt::Display) -> Self {
Self::KeyringAccess {
name: name.into(),
detail: detail.to_string(),
}
}
}
#[allow(dead_code)]
pub fn exit_codes_map() -> std::collections::BTreeMap<u8, &'static str> {
[
(0, "success"),
(2, "usage error"),
(10, "no authed providers"),
(11, "agent (provider) failure"),
(12, "summarizer failure"),
(13, "malformed credentials"),
(14, "incomplete custom provider"),
(15, "missing agent"),
(16, "unknown panel"),
(17, "keychain access failed"),
(70, "other / internal"),
]
.into_iter()
.collect()
}