use anyhow::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idioma {
English,
Portugues,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mensagem {
VpsRegistroVazio,
VpsListaTitulo,
VpsAdicionada {
nome: String,
},
VpsRemovida {
nome: String,
},
VpsDuplicada {
nome: String,
},
VpsNaoEncontrada {
nome: String,
},
VpsAtivaSelecionada {
nome: String,
},
ConfigCaminhoLabel,
ConfigCaminho {
caminho: String,
},
ConfigSemChaves,
ErroCarregarConfig,
ErroSalvarConfig,
ErroConexaoSsh,
ErroComandoFalhou,
ErroArgumentoInvalido {
detalhe: String,
},
ErroGenerico {
detalhe: String,
},
TunnelAtivo {
porta_local: u16,
host_remoto: String,
porta_remota: u16,
vps_nome: String,
},
TunnelPressioneCtrlC,
HealthCheckOk {
nome: String,
},
HealthCheckSemVps,
HealthCheckFalhou {
nome: String,
detalhe: String,
},
HealthCheckLatencia {
nome: String,
latencia_ms: u64,
},
OperacaoCancelada,
ConfirmarRemocaoVps {
nome: String,
},
RemocaoCancelada,
RemoveExigeYesEmNaoInterativo,
}
impl Mensagem {
pub fn texto(&self, idioma: Idioma) -> String {
match idioma {
Idioma::English => en(self),
Idioma::Portugues => pt(self),
}
}
}
pub fn inicializar_idioma(forcar: Option<&str>) -> Result<()> {
let idioma = crate::locale::resolver_idioma(forcar);
crate::locale::definir_idioma(idioma);
Ok(())
}
#[must_use]
pub fn idioma_atual() -> Idioma {
crate::locale::idioma_atual()
}
#[must_use]
pub fn t(msg: Mensagem) -> String {
msg.texto(idioma_atual())
}
fn en(msg: &Mensagem) -> String {
match msg {
Mensagem::VpsRegistroVazio => "No VPS registered.".to_string(),
Mensagem::VpsListaTitulo => "Registered VPS:".to_string(),
Mensagem::VpsAdicionada { nome } => format!("VPS '{nome}' added successfully."),
Mensagem::VpsRemovida { nome } => format!("VPS '{nome}' removed successfully."),
Mensagem::VpsDuplicada { nome } => format!("VPS '{nome}' is already registered."),
Mensagem::VpsNaoEncontrada { nome } => format!("VPS '{nome}' not found."),
Mensagem::VpsAtivaSelecionada { nome } => format!("Active VPS: '{nome}'."),
Mensagem::ConfigCaminhoLabel => "Configuration file:".to_string(),
Mensagem::ConfigCaminho { caminho } => caminho.clone(),
Mensagem::ConfigSemChaves => "No API keys configured.".to_string(),
Mensagem::ErroCarregarConfig => "Failed to load configuration.".to_string(),
Mensagem::ErroSalvarConfig => "Failed to save configuration.".to_string(),
Mensagem::ErroConexaoSsh => "SSH connection error.".to_string(),
Mensagem::ErroComandoFalhou => "Command execution failed.".to_string(),
Mensagem::ErroArgumentoInvalido { detalhe } => format!("Invalid argument: {detalhe}"),
Mensagem::ErroGenerico { detalhe } => detalhe.clone(),
Mensagem::TunnelAtivo {
porta_local,
host_remoto,
porta_remota,
vps_nome,
} => format!(
"SSH tunnel active: localhost:{porta_local} -> {host_remoto}:{porta_remota} via {vps_nome}"
),
Mensagem::TunnelPressioneCtrlC => "Press Ctrl+C to terminate.".to_string(),
Mensagem::HealthCheckOk { nome } => format!("Health check passed for '{nome}'."),
Mensagem::HealthCheckSemVps => {
"No active VPS. Use 'ssh-cli connect <NAME>' first.".to_string()
}
Mensagem::HealthCheckFalhou { nome, detalhe } => {
format!("Health check FAILED for '{nome}': {detalhe}")
}
Mensagem::HealthCheckLatencia { nome, latencia_ms } => {
format!("Health check OK for '{nome}' ({latencia_ms}ms)")
}
Mensagem::OperacaoCancelada => "Operation cancelled by user.".to_string(),
Mensagem::ConfirmarRemocaoVps { nome } => format!("Remove VPS '{nome}'? (y/N): "),
Mensagem::RemocaoCancelada => "Removal cancelled.".to_string(),
Mensagem::RemoveExigeYesEmNaoInterativo => {
"Non-interactive mode: use --yes (-y) to confirm removal.".to_string()
}
}
}
fn pt(msg: &Mensagem) -> String {
match msg {
Mensagem::VpsRegistroVazio => "Nenhum VPS cadastrado.".to_string(),
Mensagem::VpsListaTitulo => "VPS cadastrados:".to_string(),
Mensagem::VpsAdicionada { nome } => format!("VPS '{nome}' adicionada com sucesso."),
Mensagem::VpsRemovida { nome } => format!("VPS '{nome}' removida com sucesso."),
Mensagem::VpsDuplicada { nome } => format!("VPS '{nome}' já está cadastrada."),
Mensagem::VpsNaoEncontrada { nome } => format!("VPS '{nome}' não encontrada."),
Mensagem::VpsAtivaSelecionada { nome } => format!("VPS ativa: '{nome}'."),
Mensagem::ConfigCaminhoLabel => "Arquivo de configuração:".to_string(),
Mensagem::ConfigCaminho { caminho } => caminho.clone(),
Mensagem::ConfigSemChaves => "Nenhuma chave de API configurada.".to_string(),
Mensagem::ErroCarregarConfig => "Falha ao carregar configuração.".to_string(),
Mensagem::ErroSalvarConfig => "Falha ao salvar configuração.".to_string(),
Mensagem::ErroConexaoSsh => "Erro de conexão SSH.".to_string(),
Mensagem::ErroComandoFalhou => "Falha na execução do comando.".to_string(),
Mensagem::ErroArgumentoInvalido { detalhe } => format!("Argumento inválido: {detalhe}"),
Mensagem::ErroGenerico { detalhe } => detalhe.clone(),
Mensagem::TunnelAtivo {
porta_local,
host_remoto,
porta_remota,
vps_nome,
} => format!(
"Tunnel SSH: localhost:{porta_local} -> {host_remoto}:{porta_remota} via {vps_nome}"
),
Mensagem::TunnelPressioneCtrlC => "Pressione Ctrl+C para encerrar.".to_string(),
Mensagem::HealthCheckOk { nome } => format!("Health check bem-sucedido para '{nome}'."),
Mensagem::HealthCheckSemVps => {
"Nenhuma VPS ativa. Use 'ssh-cli connect <NOME>' primeiro.".to_string()
}
Mensagem::HealthCheckFalhou { nome, detalhe } => {
format!("Health check FALHOU para '{nome}': {detalhe}")
}
Mensagem::HealthCheckLatencia { nome, latencia_ms } => {
format!("Health check OK para '{nome}' ({latencia_ms}ms)")
}
Mensagem::OperacaoCancelada => "Operação cancelada pelo usuário.".to_string(),
Mensagem::ConfirmarRemocaoVps { nome } => format!("Remover VPS '{nome}'? (s/N): "),
Mensagem::RemocaoCancelada => "Remoção cancelada.".to_string(),
Mensagem::RemoveExigeYesEmNaoInterativo => {
"Modo não-interativo: use --yes (-y) para confirmar remoção.".to_string()
}
}
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn idioma_enum_e_copy() {
let a = Idioma::English;
let b = a;
assert_eq!(a, b);
}
#[test]
fn mensagem_nao_e_copy_mas_e_clone() {
let m = Mensagem::VpsAdicionada {
nome: "vps-01".to_string(),
};
let m2 = m.clone();
assert_eq!(m, m2);
}
#[test]
fn vps_registro_vazio_en() {
assert_eq!(
Mensagem::VpsRegistroVazio.texto(Idioma::English),
"No VPS registered."
);
}
#[test]
fn vps_registro_vazio_pt() {
assert_eq!(
Mensagem::VpsRegistroVazio.texto(Idioma::Portugues),
"Nenhum VPS cadastrado."
);
}
#[test]
fn vps_adicionada_inclui_nome_en() {
let msg = Mensagem::VpsAdicionada {
nome: "prod-01".to_string(),
};
assert_eq!(
msg.texto(Idioma::English),
"VPS 'prod-01' added successfully."
);
}
#[test]
fn vps_adicionada_inclui_nome_pt() {
let msg = Mensagem::VpsAdicionada {
nome: "prod-01".to_string(),
};
assert_eq!(
msg.texto(Idioma::Portugues),
"VPS 'prod-01' adicionada com sucesso."
);
}
#[test]
fn vps_removida_inclui_nome() {
let msg = Mensagem::VpsRemovida {
nome: "dev-01".to_string(),
};
assert!(msg.texto(Idioma::English).contains("dev-01"));
assert!(msg.texto(Idioma::Portugues).contains("dev-01"));
}
#[test]
fn vps_duplicada_inclui_nome() {
let msg = Mensagem::VpsDuplicada {
nome: "staging".to_string(),
};
assert!(msg.texto(Idioma::English).contains("staging"));
assert!(msg.texto(Idioma::Portugues).contains("staging"));
}
#[test]
fn vps_nao_encontrada_inclui_nome() {
let msg = Mensagem::VpsNaoEncontrada {
nome: "inexistente".to_string(),
};
assert!(msg.texto(Idioma::English).contains("inexistente"));
assert!(msg.texto(Idioma::Portugues).contains("inexistente"));
}
#[test]
fn tunnel_ativo_inclui_todos_os_campos() {
let msg = Mensagem::TunnelAtivo {
porta_local: 8080,
host_remoto: "1.2.3.4".to_string(),
porta_remota: 22,
vps_nome: "meu-servidor".to_string(),
};
let en = msg.texto(Idioma::English);
assert!(en.contains("8080"));
assert!(en.contains("1.2.3.4"));
assert!(en.contains("22"));
assert!(en.contains("meu-servidor"));
}
#[test]
fn erro_argumento_invalido_inclui_detalhe() {
let msg = Mensagem::ErroArgumentoInvalido {
detalhe: "porta fora do intervalo".to_string(),
};
assert!(msg
.texto(Idioma::English)
.contains("porta fora do intervalo"));
assert!(msg
.texto(Idioma::Portugues)
.contains("porta fora do intervalo"));
}
#[test]
fn health_check_ok_inclui_nome() {
let msg = Mensagem::HealthCheckOk {
nome: "prod-01".to_string(),
};
assert!(msg.texto(Idioma::English).contains("prod-01"));
assert!(msg.texto(Idioma::Portugues).contains("prod-01"));
}
#[test]
fn todas_variantes_unitarias_en_nao_vazias() {
let unitarias = [
Mensagem::VpsRegistroVazio,
Mensagem::VpsListaTitulo,
Mensagem::ConfigCaminhoLabel,
Mensagem::ConfigSemChaves,
Mensagem::ErroCarregarConfig,
Mensagem::ErroSalvarConfig,
Mensagem::ErroConexaoSsh,
Mensagem::ErroComandoFalhou,
Mensagem::TunnelPressioneCtrlC,
Mensagem::HealthCheckSemVps,
Mensagem::OperacaoCancelada,
];
for v in &unitarias {
let texto = v.texto(Idioma::English);
assert!(!texto.is_empty(), "EN vazia para {:?}", v);
}
}
#[test]
fn todas_variantes_unitarias_pt_nao_vazias() {
let unitarias = [
Mensagem::VpsRegistroVazio,
Mensagem::VpsListaTitulo,
Mensagem::ConfigCaminhoLabel,
Mensagem::ConfigSemChaves,
Mensagem::ErroCarregarConfig,
Mensagem::ErroSalvarConfig,
Mensagem::ErroConexaoSsh,
Mensagem::ErroComandoFalhou,
Mensagem::TunnelPressioneCtrlC,
Mensagem::HealthCheckSemVps,
Mensagem::OperacaoCancelada,
];
for v in &unitarias {
let texto = v.texto(Idioma::Portugues);
assert!(!texto.is_empty(), "PT vazia para {:?}", v);
}
}
#[test]
fn traducoes_pt_diferentes_de_en_para_unitarias() {
let pares = [
(Mensagem::VpsRegistroVazio, Mensagem::VpsRegistroVazio),
(Mensagem::ErroConexaoSsh, Mensagem::ErroConexaoSsh),
(Mensagem::HealthCheckSemVps, Mensagem::HealthCheckSemVps),
(Mensagem::OperacaoCancelada, Mensagem::OperacaoCancelada),
];
for (a, b) in &pares {
let en = a.texto(Idioma::English);
let pt = b.texto(Idioma::Portugues);
assert_ne!(en, pt, "EN == PT para {:?}", a);
}
}
#[test]
fn health_check_falhou_inclui_nome_e_detalhe() {
let msg = Mensagem::HealthCheckFalhou {
nome: "prod-01".to_string(),
detalhe: "timeout".to_string(),
};
assert!(msg.texto(Idioma::English).contains("prod-01"));
assert!(msg.texto(Idioma::English).contains("timeout"));
assert!(msg.texto(Idioma::Portugues).contains("prod-01"));
assert!(msg.texto(Idioma::Portugues).contains("timeout"));
}
#[test]
fn health_check_latencia_inclui_nome_e_ms() {
let msg = Mensagem::HealthCheckLatencia {
nome: "relay-01".to_string(),
latencia_ms: 42,
};
assert!(msg.texto(Idioma::English).contains("relay-01"));
assert!(msg.texto(Idioma::English).contains("42"));
assert!(msg.texto(Idioma::Portugues).contains("relay-01"));
assert!(msg.texto(Idioma::Portugues).contains("42"));
}
#[test]
fn inicializar_idioma_sem_forcar_nao_panic() {
let resultado = inicializar_idioma(None);
assert!(resultado.is_ok());
}
#[test]
fn inicializar_idioma_com_pt_br_funciona() {
let resultado = inicializar_idioma(Some("pt-BR"));
assert!(resultado.is_ok());
}
#[test]
fn idioma_atual_retorna_valor_valido() {
let idioma = idioma_atual();
assert!(idioma == Idioma::English || idioma == Idioma::Portugues);
}
}