use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConproxyError {
#[error("Not in a conproxy project (no .conproxy/ directory found).")]
NotInitialized,
#[error("Invalid config: {0}")]
InvalidConfig(String),
#[error("Config validation error: {0}")]
ConfigValidation(String),
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("TOML parse error: {0}")]
TomlParse(#[from] toml::de::Error),
#[error("TOML serialize error: {0}")]
TomlSerialize(#[from] toml::ser::Error),
#[error("Proxy not running")]
ProxyNotRunning,
#[error("Proxy connection failed: {0}")]
ProxyConnection(String),
#[error("Upstream error: {0}")]
UpstreamError(String),
#[error("Cache error: {0}")]
CacheError(String),
#[error(
"ONNX model not installed in ~/.conproxy/models/ — place model.onnx + tokenizer.json manually"
)]
ModelNotInstalled,
#[cfg(feature = "embed-api")]
#[error("Embedding error: {0}")]
Embedding(String),
}
pub type Result<T> = std::result::Result<T, ConproxyError>;
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects
)]
mod tests {
use super::*;
#[test]
fn test_display_not_initialized() {
let s = format!("{}", ConproxyError::NotInitialized);
assert!(
s.contains(".conproxy/") || s.contains("conproxy project"),
"should mention .conproxy/, got: {s}"
);
}
#[test]
fn test_display_invalid_config() {
let s = format!("{}", ConproxyError::InvalidConfig("bad value".to_string()));
assert!(s.contains("Invalid config"), "got: {s}");
assert!(
s.contains("bad value"),
"should include inner message, got: {s}"
);
}
#[test]
fn test_display_config_validation() {
let s = format!(
"{}",
ConproxyError::ConfigValidation("semver mismatch".to_string())
);
assert!(s.contains("Config validation"), "got: {s}");
assert!(s.contains("semver mismatch"), "got: {s}");
}
#[test]
fn test_display_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let err: ConproxyError = io_err.into();
let s = format!("{err}");
assert!(s.contains("IO error"), "got: {s}");
assert!(
s.contains("file missing"),
"should propagate inner message, got: {s}"
);
}
#[test]
fn test_display_toml_parse() {
let parse_result: std::result::Result<toml::Value, _> = toml::from_str("invalid = =");
assert!(parse_result.is_err());
let err: ConproxyError = parse_result.unwrap_err().into();
let s = format!("{err}");
assert!(s.contains("TOML parse error"), "got: {s}");
}
#[test]
fn test_display_toml_serialize() {
let mut bad = std::collections::HashMap::new();
bad.insert(std::ffi::OsString::from("k"), "v"); let ser_result = toml::to_string(&bad);
if let Err(e) = ser_result {
let err: ConproxyError = e.into();
let s = format!("{err}");
assert!(
s.contains("TOML serialize") || s.contains("serialize"),
"got: {s}"
);
}
}
#[test]
fn test_display_proxy_not_running() {
let s = format!("{}", ConproxyError::ProxyNotRunning);
assert!(s.contains("Proxy not running"), "got: {s}");
}
#[test]
fn test_display_proxy_connection() {
let s = format!(
"{}",
ConproxyError::ProxyConnection("socket closed".to_string())
);
assert!(s.contains("Proxy connection"), "got: {s}");
assert!(s.contains("socket closed"), "got: {s}");
}
#[test]
fn test_display_upstream_error() {
let s = format!(
"{}",
ConproxyError::UpstreamError("502 Bad Gateway".to_string())
);
assert!(s.contains("Upstream error"), "got: {s}");
assert!(s.contains("502"), "got: {s}");
}
#[test]
fn test_display_cache_error() {
let s = format!(
"{}",
ConproxyError::CacheError("eviction failed".to_string())
);
assert!(s.contains("Cache error"), "got: {s}");
assert!(s.contains("eviction failed"), "got: {s}");
}
#[test]
fn test_display_model_not_installed() {
let s = format!("{}", ConproxyError::ModelNotInstalled);
assert!(s.contains("ONNX model not installed"), "got: {s}");
assert!(
s.contains("model.onnx") && s.contains("tokenizer.json"),
"should mention required files, got: {s}"
);
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let err: ConproxyError = io_err.into();
matches!(err, ConproxyError::Io(_));
}
#[test]
fn test_from_toml_parse_error() {
let bad: std::result::Result<toml::Value, _> = toml::from_str("= = =");
let err: ConproxyError = bad.unwrap_err().into();
matches!(err, ConproxyError::TomlParse(_));
}
#[test]
fn test_debug_format_works() {
let err = ConproxyError::InvalidConfig("test".to_string());
let dbg = format!("{err:?}");
assert!(
dbg.contains("InvalidConfig"),
"Debug should include variant name, got: {dbg}"
);
}
}