use std::borrow::Cow;
use std::fmt;
use anyhow::{Error, Result};
pub const ERR_READ_FILE: &str = "failed to read file";
pub const ERR_WRITE_FILE: &str = "failed to write file";
pub const ERR_READ_DIR: &str = "failed to read directory";
pub const ERR_CREATE_DIR: &str = "failed to create directory";
pub const ERR_REMOVE_FILE: &str = "failed to remove file";
pub const ERR_REMOVE_DIR: &str = "failed to remove directory";
pub const ERR_READ_DIR_ENTRY: &str = "failed to read directory entry";
pub const ERR_GET_FILE_TYPE: &str = "failed to read file type";
pub const ERR_GET_METADATA: &str = "failed to read file metadata";
pub const ERR_CANONICALIZE_PATH: &str = "failed to canonicalize path";
pub const ERR_READ_SYMLINK: &str = "failed to read symlink";
pub const ERR_CREATE_SKILLS_DIR: &str = "failed to create skills directory";
pub const ERR_CREATE_SKILL_DIR: &str = "failed to create skill directory";
pub const ERR_READ_SKILL_CODE: &str = "failed to read skill code";
pub const ERR_WRITE_SKILL_CODE: &str = "failed to write skill code";
pub const ERR_READ_SKILL_METADATA: &str = "failed to read skill metadata";
pub const ERR_WRITE_SKILL_METADATA: &str = "failed to write skill metadata";
pub const ERR_PARSE_SKILL_METADATA: &str = "failed to parse skill metadata";
pub const ERR_WRITE_SKILL_DOCS: &str = "failed to write skill documentation";
pub const ERR_DELETE_SKILL: &str = "failed to delete skill";
pub const ERR_READ_SKILLS_DIR: &str = "failed to read skills directory";
pub const ERR_TOOL_DENIED: &str = "tool denied or unavailable by policy";
pub const ERR_CREATE_AUDIT_DIR: &str = "Failed to create audit directory";
pub const ERR_WRITE_AUDIT_LOG: &str = "failed to write audit log";
pub const ERR_CREATE_CHECKPOINT_DIR: &str = "failed to create checkpoint directory";
pub const ERR_WRITE_CHECKPOINT: &str = "failed to write checkpoint";
pub const ERR_READ_CHECKPOINT: &str = "failed to read checkpoint";
pub const ERR_CREATE_POLICY_DIR: &str = "Failed to create directory for tool policy config";
pub const ERR_CREATE_WORKSPACE_POLICY_DIR: &str = "Failed to create workspace policy directory";
pub const ERR_SERIALIZE_METADATA: &str = "failed to serialize skill metadata";
pub const ERR_SERIALIZE_STATE: &str = "failed to serialize state";
pub const ERR_DESERIALIZE: &str = "failed to deserialize data";
pub const ERR_CREATE_IPC_DIR: &str = "failed to create IPC directory";
pub const ERR_READ_REQUEST_FILE: &str = "failed to read request file";
pub const ERR_READ_REQUEST_JSON: &str = "failed to read request JSON";
pub const ERR_PARSE_REQUEST_JSON: &str = "failed to parse request JSON";
pub const ERR_PARSE_ARGS: &str = "failed to parse tokenized args";
pub const ERR_PARSE_RESULT: &str = "failed to parse de-tokenized result";
#[macro_export]
macro_rules! file_err {
($path:expr, read) => {
format!("failed to read {}", $path)
};
($path:expr, write) => {
format!("failed to write {}", $path)
};
($path:expr, delete) => {
format!("failed to delete {}", $path)
};
($path:expr, create) => {
format!("failed to create {}", $path)
};
}
#[macro_export]
macro_rules! ctx_err {
($op:expr, $ctx:expr) => {
format!("{}: {}", $op, $ctx)
};
}
pub trait ErrorFormatter: Send + Sync {
fn format_error(&self, error: &Error) -> Cow<'_, str>;
}
pub trait ErrorReporter: Send + Sync {
fn capture(&self, error: &Error) -> Result<()>;
fn capture_message(&self, message: impl Into<Cow<'static, str>>) -> Result<()> {
let message: Cow<'static, str> = message.into();
self.capture(&Error::msg(message))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopErrorReporter;
impl ErrorReporter for NoopErrorReporter {
fn capture(&self, _error: &Error) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct DisplayErrorFormatter;
impl ErrorFormatter for DisplayErrorFormatter {
fn format_error(&self, error: &Error) -> Cow<'_, str> {
Cow::Owned(format!("{error}"))
}
}
#[derive(Debug, Clone)]
pub struct MultiErrors<E = Error> {
errors: Vec<E>,
}
impl<E> MultiErrors<E> {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn push(&mut self, error: E) {
self.errors.push(error);
}
pub fn extend(&mut self, iter: impl IntoIterator<Item = E>) {
self.errors.extend(iter);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.errors.len()
}
#[must_use]
pub fn into_inner(self) -> Vec<E> {
self.errors
}
#[must_use]
pub fn as_slice(&self) -> &[E] {
&self.errors
}
pub fn iter(&self) -> std::slice::Iter<'_, E> {
self.errors.iter()
}
pub fn ok(self) -> std::result::Result<(), Self> {
if self.errors.is_empty() {
Ok(())
} else {
Err(self)
}
}
pub fn clear(&mut self) {
self.errors.clear();
}
pub fn collect_result<T, F>(&mut self, result: std::result::Result<T, F>) -> Option<T>
where
F: Into<E>,
{
match result {
Ok(val) => Some(val),
Err(e) => {
self.errors.push(e.into());
None
}
}
}
#[must_use]
pub fn to_anyhow(&self) -> Error
where
E: fmt::Display,
{
Error::msg(self.to_string())
}
}
impl<E> Default for MultiErrors<E> {
fn default() -> Self {
Self::new()
}
}
impl<E> From<Vec<E>> for MultiErrors<E> {
fn from(errors: Vec<E>) -> Self {
Self { errors }
}
}
impl<E> IntoIterator for MultiErrors<E> {
type Item = E;
type IntoIter = std::vec::IntoIter<E>;
fn into_iter(self) -> Self::IntoIter {
self.errors.into_iter()
}
}
impl<E: serde::Serialize> serde::Serialize for MultiErrors<E> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.errors.serialize(serializer)
}
}
impl<'de, E: serde::Deserialize<'de>> serde::Deserialize<'de> for MultiErrors<E> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Vec::<E>::deserialize(deserializer).map(|errors| Self { errors })
}
}
impl<E: fmt::Display> fmt::Display for MultiErrors<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.errors.len() {
0 => write!(f, "no errors"),
1 => write!(f, "{}", self.errors[0]),
_ => {
for (i, error) in self.errors.iter().enumerate() {
if i > 0 {
writeln!(f)?;
}
write!(f, " {}. {error}", i + 1)?;
}
Ok(())
}
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for MultiErrors<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.errors
.first()
.map(|e| e as &(dyn std::error::Error + 'static))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formatter_uses_display() {
let formatter = DisplayErrorFormatter;
let error = Error::msg("test error");
assert_eq!(formatter.format_error(&error), "test error");
}
#[test]
fn noop_reporter_drops_errors() {
let reporter = NoopErrorReporter;
let error = Error::msg("test");
assert!(reporter.capture(&error).is_ok());
assert!(reporter.capture_message("message").is_ok());
}
#[test]
fn multi_errors_new_is_empty() {
let errors: MultiErrors<String> = MultiErrors::new();
assert!(errors.is_empty());
assert_eq!(errors.len(), 0);
}
#[test]
fn multi_errors_push_and_len() {
let mut errors = MultiErrors::new();
errors.push("error 1".to_string());
errors.push("error 2".to_string());
assert!(!errors.is_empty());
assert_eq!(errors.len(), 2);
}
#[test]
fn multi_errors_collect_result_ok() {
let mut errors: MultiErrors<String> = MultiErrors::new();
let value: i32 = errors.collect_result(Ok::<_, String>(42)).unwrap_or(0);
assert_eq!(value, 42);
assert!(errors.is_empty());
}
#[test]
fn multi_errors_collect_result_err() {
let mut errors: MultiErrors<String> = MultiErrors::new();
let value: i32 = errors
.collect_result(Err::<i32, String>("bad".to_string()))
.unwrap_or(0);
assert_eq!(value, 0);
assert_eq!(errors.len(), 1);
}
#[test]
fn multi_errors_ok_succeeds_when_empty() {
let errors: MultiErrors<String> = MultiErrors::new();
assert!(errors.ok().is_ok());
}
#[test]
fn multi_errors_ok_fails_when_not_empty() {
let mut errors = MultiErrors::new();
errors.push("error".to_string());
assert!(errors.ok().is_err());
}
#[test]
fn multi_errors_display_empty() {
let errors: MultiErrors<String> = MultiErrors::new();
assert_eq!(errors.to_string(), "no errors");
}
#[test]
fn multi_errors_display_single() {
let mut errors = MultiErrors::new();
errors.push("something failed".to_string());
assert_eq!(errors.to_string(), "something failed");
}
#[test]
fn multi_errors_display_multiple() {
let mut errors = MultiErrors::new();
errors.push("first issue".to_string());
errors.push("second issue".to_string());
let display = errors.to_string();
assert!(display.contains("1. first issue"));
assert!(display.contains("2. second issue"));
}
#[test]
fn multi_errors_extend() {
let mut errors = MultiErrors::new();
errors.extend(vec!["a".to_string(), "b".to_string()]);
assert_eq!(errors.len(), 2);
}
#[test]
fn multi_errors_into_inner() {
let mut errors = MultiErrors::new();
errors.push("test".to_string());
let inner: Vec<String> = errors.into_inner();
assert_eq!(inner.len(), 1);
}
#[test]
fn multi_errors_from_vec() {
let errors: MultiErrors<String> = MultiErrors::from(vec!["a".to_string()]);
assert_eq!(errors.len(), 1);
}
#[test]
fn multi_errors_into_iterator() {
let mut errors = MultiErrors::new();
errors.push("a".to_string());
errors.push("b".to_string());
let collected: Vec<String> = errors.into_iter().collect();
assert_eq!(collected, vec!["a", "b"]);
}
#[test]
fn multi_errors_slice_access() {
let mut errors = MultiErrors::new();
errors.push("err".to_string());
assert_eq!(errors.as_slice(), &["err".to_string()]);
}
#[test]
fn multi_errors_to_anyhow() {
let mut errors = MultiErrors::new();
errors.push("something broke".to_string());
let err = errors.to_anyhow();
assert!(err.to_string().contains("something broke"));
}
}