use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CliError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("File already exists: {0}")]
FileExists(String),
#[error("Clap error: {0}")]
Clap(String),
#[error("Generation error: {0}")]
Generation(String),
#[error("Migration error: {0}")]
Migration(String),
#[error("Cache error: {0}")]
Cache(String),
#[error("Scheduler error: {0}")]
Scheduler(String),
#[error("{0}")]
Generic(String),
#[error("Invalid plugin name: {0} (must be lowercase letters, digits, underscores or hyphens, not starting with a digit)")]
InvalidPluginName(String),
#[error("Unknown template: '{requested}'. Available templates: {available:?}")]
UnknownTemplate {
requested: String,
available: Vec<String>,
},
#[error("Field parse error: {0}")]
FieldParseError(String),
#[error("Directory already exists: {0} (use --force to overwrite)")]
DirExists(PathBuf),
#[error("Template files missing: {0:?}")]
TemplateMissing(Vec<String>),
#[error("Template syntax error in {file}:{line}:{col}: {msg}")]
TemplateSyntaxError {
file: String,
line: usize,
col: usize,
msg: String,
},
#[error("Variable not found: '{var}' referenced in {file}:{line}")]
VarNotFound {
var: String,
file: String,
line: usize,
},
#[error("Compilation failed: {0:?}")]
CompileFailed(Vec<String>),
#[error("Foreign key not found: '{0}' is not a field in the slave table")]
ForeignKeyNotFound(String),
#[error("Master table and slave table must be different")]
MasterSlaveSame,
#[error("Marketplace error: {0}")]
Marketplace(String),
#[error("HTTP error: {0}")]
Http(String),
#[error("TOML error: {0}")]
Toml(String),
#[error("JSON error: {0}")]
Json(String),
}
impl From<clap::Error> for CliError {
fn from(e: clap::Error) -> Self {
CliError::Clap(e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_io_error_display() {
let err = CliError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"file not found",
));
assert!(err.to_string().contains("IO error"));
assert!(err.to_string().contains("file not found"));
}
#[test]
fn test_file_exists_error_display() {
let err = CliError::FileExists("/path/to/file".to_string());
assert!(err.to_string().contains("File already exists"));
assert!(err.to_string().contains("/path/to/file"));
}
#[test]
fn test_clap_error_conversion() {
let clap_err = clap::Error::new(clap::error::ErrorKind::InvalidValue);
let cli_err: CliError = clap_err.into();
assert!(matches!(cli_err, CliError::Clap(_)));
}
#[test]
fn test_generation_error_display() {
let err = CliError::Generation("template substitution failed".to_string());
assert!(err.to_string().contains("Generation error"));
}
#[test]
fn test_migration_error_display() {
let err = CliError::Migration("database connection failed".to_string());
assert!(err.to_string().contains("Migration error"));
}
#[test]
fn test_cache_error_display() {
let err = CliError::Cache("redis connection failed".to_string());
assert!(err.to_string().contains("Cache error"));
}
#[test]
fn test_scheduler_error_display() {
let err = CliError::Scheduler("cron parse failed".to_string());
assert!(err.to_string().contains("Scheduler error"));
}
#[test]
fn test_invalid_plugin_name_display() {
let err = CliError::InvalidPluginName("my plugin".to_string());
assert!(err.to_string().contains("Invalid plugin name"));
assert!(err.to_string().contains("my plugin"));
}
#[test]
fn test_unknown_template_display() {
let err = CliError::UnknownTemplate {
requested: "nonexistent".to_string(),
available: vec!["crud".to_string(), "master-slave".to_string()],
};
assert!(err.to_string().contains("Unknown template"));
assert!(err.to_string().contains("nonexistent"));
assert!(err.to_string().contains("crud"));
}
#[test]
fn test_field_parse_error_display() {
let err = CliError::FieldParseError("unexpected ',' at position 5".to_string());
assert!(err.to_string().contains("Field parse error"));
}
#[test]
fn test_dir_exists_display() {
let err = CliError::DirExists(PathBuf::from("/path/to/plugin"));
assert!(err.to_string().contains("Directory already exists"));
assert!(err.to_string().contains("--force"));
}
#[test]
fn test_template_missing_display() {
let err = CliError::TemplateMissing(vec!["model.rs.tera".to_string()]);
assert!(err.to_string().contains("Template files missing"));
assert!(err.to_string().contains("model.rs.tera"));
}
#[test]
fn test_template_syntax_error_display() {
let err = CliError::TemplateSyntaxError {
file: "model.rs.tera".to_string(),
line: 10,
col: 5,
msg: "unexpected token".to_string(),
};
let s = err.to_string();
assert!(s.contains("model.rs.tera"));
assert!(s.contains("10"));
assert!(s.contains("5"));
assert!(s.contains("unexpected token"));
}
#[test]
fn test_var_not_found_display() {
let err = CliError::VarNotFound {
var: "plugin_name".to_string(),
file: "model.rs.tera".to_string(),
line: 3,
};
let s = err.to_string();
assert!(s.contains("plugin_name"));
assert!(s.contains("model.rs.tera"));
}
#[test]
fn test_compile_failed_display() {
let err =
CliError::CompileFailed(vec!["error[E0277]: trait bound not satisfied".to_string()]);
assert!(err.to_string().contains("Compilation failed"));
}
#[test]
fn test_foreign_key_not_found_display() {
let err = CliError::ForeignKeyNotFound("user_id".to_string());
assert!(err.to_string().contains("Foreign key not found"));
assert!(err.to_string().contains("user_id"));
}
#[test]
fn test_master_slave_same_display() {
let err = CliError::MasterSlaveSame;
assert!(err
.to_string()
.contains("Master table and slave table must be different"));
}
}