use thiserror::Error;
#[derive(Debug, Error)]
pub enum ErroSshCli {
#[error("erro de I/O: {0}")]
Io(#[from] std::io::Error),
#[error("erro de JSON: {0}")]
Json(#[from] serde_json::Error),
#[error("erro de TOML (leitura): {0}")]
TomlDe(#[from] toml::de::Error),
#[error("erro de TOML (escrita): {0}")]
TomlSer(#[from] toml::ser::Error),
#[error("erro de conexão SSH: {0}")]
ConexaoSsh(String),
#[error("erro de autenticação SSH: {0}")]
AutenticacaoSsh(String),
#[error("conexão SSH falhou: {0}")]
ConexaoFalhou(String),
#[error("autenticação SSH falhou")]
AutenticacaoFalhou,
#[error("canal SSH falhou: {0}")]
CanalFalhou(String),
#[error("timeout SSH após {0}ms")]
TimeoutSsh(u64),
#[error("comando falhou com exit code {exit_code}: {stderr}")]
ComandoFalhou {
exit_code: i32,
stderr: String,
},
#[error("VPS '{0}' não encontrada no registro")]
VpsNaoEncontrada(String),
#[error("VPS '{0}' já existe no registro")]
VpsDuplicada(String),
#[error("arquivo não encontrado: {0}")]
ArquivoNaoEncontrado(String),
#[error("argumento inválido: {0}")]
ArgumentoInvalido(String),
#[error("timeout excedido após {0}ms")]
Timeout(u64),
#[error("diretório de configuração indisponível")]
DiretorioXdg,
#[error("versão de schema incompatível: esperada {esperada}, encontrada {encontrada}")]
SchemaIncompativel {
esperada: u32,
encontrada: u32,
},
#[error("erro: {0}")]
Generico(String),
}
pub mod exit_codes {
pub const EX_OK: i32 = 0;
pub const EX_GENERAL: i32 = 1;
pub const EX_USAGE: i32 = 64;
pub const EX_DATAERR: i32 = 65;
pub const EX_NOINPUT: i32 = 66;
pub const EX_CANTCREAT: i32 = 73;
pub const EX_IOERR: i32 = 74;
pub const EX_NOPERM: i32 = 77;
pub const EX_SIGINT: i32 = 130;
pub const EX_SIGTERM: i32 = 143;
}
impl ErroSshCli {
#[must_use]
pub fn exit_code(&self) -> i32 {
match self {
Self::Io(_) => exit_codes::EX_IOERR,
Self::Json(_) => exit_codes::EX_DATAERR,
Self::TomlDe(_) => exit_codes::EX_DATAERR,
Self::TomlSer(_) => exit_codes::EX_CANTCREAT,
Self::ConexaoSsh(_) => exit_codes::EX_IOERR,
Self::AutenticacaoSsh(_) => exit_codes::EX_IOERR,
Self::ConexaoFalhou(_) => exit_codes::EX_IOERR,
Self::AutenticacaoFalhou => exit_codes::EX_NOPERM,
Self::CanalFalhou(_) => exit_codes::EX_IOERR,
Self::TimeoutSsh(_) => exit_codes::EX_IOERR,
Self::ComandoFalhou { exit_code, .. } => *exit_code,
Self::VpsNaoEncontrada(_) => exit_codes::EX_NOINPUT,
Self::VpsDuplicada(_) => exit_codes::EX_USAGE,
Self::ArquivoNaoEncontrado(_) => exit_codes::EX_NOINPUT,
Self::ArgumentoInvalido(_) => exit_codes::EX_USAGE,
Self::Timeout(_) => exit_codes::EX_IOERR,
Self::DiretorioXdg => exit_codes::EX_CANTCREAT,
Self::SchemaIncompativel { .. } => exit_codes::EX_DATAERR,
Self::Generico(_) => exit_codes::EX_GENERAL,
}
}
}
pub type ResultadoSshCli<T> = std::result::Result<T, ErroSshCli>;
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn vps_nao_encontrada_mensagem_contem_nome() {
let erro = ErroSshCli::VpsNaoEncontrada("producao".into());
assert!(erro.to_string().contains("producao"));
}
#[test]
fn vps_duplicada_mensagem_contem_nome() {
let erro = ErroSshCli::VpsDuplicada("vps-1".into());
let msg = erro.to_string();
assert!(msg.contains("vps-1"));
assert!(msg.contains("já existe"));
}
#[test]
fn erro_io_exibe_mensagem() {
let erro = ErroSshCli::from(std::io::Error::new(
std::io::ErrorKind::NotFound,
"arquivo nao encontrado",
));
let msg = erro.to_string();
assert!(msg.contains("I/O") || msg.contains("arquivo nao encontrado"));
}
#[test]
fn erro_toml_de_exibe_mensagem() {
let toml_err = "invalid TOML".parse::<toml::Value>().unwrap_err();
let erro = ErroSshCli::TomlDe(toml_err);
let msg = erro.to_string();
assert!(msg.contains("TOML") || msg.contains("leitura"));
}
#[test]
fn erro_tipo_servidor_vps_nao_encontrada() {
let erro = ErroSshCli::VpsNaoEncontrada("servidor-x".into());
let msg = erro.to_string();
assert!(msg.contains("servidor-x"));
assert!(msg.contains("não encontrada") || msg.contains("not found"));
}
#[test]
fn exit_code_io_retorna_ioerr() {
let e = ErroSshCli::Io(std::io::Error::other("teste"));
assert_eq!(e.exit_code(), exit_codes::EX_IOERR);
}
#[test]
fn exit_code_autenticacao_falhou_retorna_noperm() {
assert_eq!(
ErroSshCli::AutenticacaoFalhou.exit_code(),
exit_codes::EX_NOPERM
);
}
#[test]
fn exit_code_vps_nao_encontrada_retorna_noinput() {
let e = ErroSshCli::VpsNaoEncontrada("teste".to_string());
assert_eq!(e.exit_code(), exit_codes::EX_NOINPUT);
}
#[test]
fn exit_code_comando_falhou_propaga_exit_code_remoto() {
let e = ErroSshCli::ComandoFalhou {
exit_code: 127,
stderr: "not found".to_string(),
};
assert_eq!(e.exit_code(), 127);
}
#[test]
fn exit_code_argumento_invalido_retorna_usage() {
let e = ErroSshCli::ArgumentoInvalido("bad".to_string());
assert_eq!(e.exit_code(), exit_codes::EX_USAGE);
}
#[test]
fn exit_code_diretorio_xdg_retorna_cantcreat() {
assert_eq!(
ErroSshCli::DiretorioXdg.exit_code(),
exit_codes::EX_CANTCREAT
);
}
}