| | """ |
| | Utilitários de validação para o sistema AgentGraph |
| | """ |
| | import re |
| | import logging |
| | from typing import Dict, Any, Tuple, Optional |
| |
|
| |
|
| | def validate_postgresql_config(config: Dict[str, Any]) -> Tuple[bool, Optional[str]]: |
| | """ |
| | Valida configuração postgresql completa |
| | |
| | Args: |
| | config: Dicionário com configuração postgresql |
| | |
| | Returns: |
| | Tupla (válido, mensagem_erro) |
| | """ |
| | try: |
| | |
| | required_fields = ["host", "port", "database", "username", "password"] |
| | |
| | for field in required_fields: |
| | if field not in config or not config[field]: |
| | return False, f"Campo obrigatório ausente ou vazio: {field}" |
| | |
| | |
| | host = str(config["host"]).strip() |
| | if not host: |
| | return False, "Host não pode estar vazio" |
| | |
| | |
| | if not _is_valid_host(host): |
| | return False, "Formato de host inválido" |
| | |
| | |
| | try: |
| | port = int(config["port"]) |
| | if port < 1 or port > 65535: |
| | return False, "Porta deve estar entre 1 e 65535" |
| | except (ValueError, TypeError): |
| | return False, "Porta deve ser um número válido" |
| | |
| | |
| | database = str(config["database"]).strip() |
| | if not database: |
| | return False, "Nome do banco não pode estar vazio" |
| | |
| | if not _is_valid_database_name(database): |
| | return False, "Nome do banco contém caracteres inválidos" |
| | |
| | |
| | username = str(config["username"]).strip() |
| | if not username: |
| | return False, "Nome de usuário não pode estar vazio" |
| | |
| | if not _is_valid_username(username): |
| | return False, "Nome de usuário contém caracteres inválidos" |
| | |
| | |
| | password = str(config["password"]) |
| | if not password: |
| | return False, "Senha não pode estar vazia" |
| | |
| | return True, None |
| | |
| | except Exception as e: |
| | return False, f"Erro na validação: {e}" |
| |
|
| |
|
| | def _is_valid_host(host: str) -> bool: |
| | """ |
| | Valida formato de host (IP ou hostname) |
| | |
| | Args: |
| | host: Host a validar |
| | |
| | Returns: |
| | True se válido |
| | """ |
| | |
| | ipv4_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' |
| | |
| | |
| | hostname_pattern = r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$' |
| | |
| | |
| | if host.lower() == 'localhost': |
| | return True |
| | |
| | |
| | if re.match(ipv4_pattern, host): |
| | return True |
| | |
| | |
| | if re.match(hostname_pattern, host): |
| | return True |
| | |
| | return False |
| |
|
| |
|
| | def _is_valid_database_name(database: str) -> bool: |
| | """ |
| | Valida nome de banco postgresql |
| | |
| | Args: |
| | database: Nome do banco |
| | |
| | Returns: |
| | True se válido |
| | """ |
| | |
| | |
| | pattern = r'^[a-zA-Z_][a-zA-Z0-9_-]*$' |
| | |
| | |
| | if len(database) > 63: |
| | return False |
| | |
| | return bool(re.match(pattern, database)) |
| |
|
| |
|
| | def _is_valid_username(username: str) -> bool: |
| | """ |
| | Valida nome de usuário postgresql |
| | |
| | Args: |
| | username: Nome de usuário |
| | |
| | Returns: |
| | True se válido |
| | """ |
| | |
| | pattern = r'^[a-zA-Z_][a-zA-Z0-9_-]*$' |
| | |
| | |
| | if len(username) > 63: |
| | return False |
| | |
| | return bool(re.match(pattern, username)) |
| |
|
| |
|
| | def validate_csv_file_path(file_path: str) -> Tuple[bool, Optional[str]]: |
| | """ |
| | Valida caminho de arquivo csv |
| | |
| | Args: |
| | file_path: Caminho do arquivo |
| | |
| | Returns: |
| | Tupla (válido, mensagem_erro) |
| | """ |
| | try: |
| | import os |
| | |
| | if not file_path: |
| | return False, "Caminho do arquivo não pode estar vazio" |
| | |
| | if not os.path.exists(file_path): |
| | return False, f"Arquivo não encontrado: {file_path}" |
| | |
| | if not file_path.lower().endswith('.csv'): |
| | return False, "Arquivo deve ter extensão .csv" |
| | |
| | |
| | if not os.path.isfile(file_path): |
| | return False, "Caminho deve apontar para um arquivo" |
| | |
| | |
| | file_size = os.path.getsize(file_path) |
| | if file_size == 0: |
| | return False, "Arquivo csv está vazio" |
| | |
| | |
| | if file_size > 5 * 1024 * 1024 * 1024: |
| | return False, "Arquivo muito grande (máximo 5GB)" |
| | |
| | return True, None |
| | |
| | except Exception as e: |
| | return False, f"Erro na validação do arquivo: {e}" |
| |
|
| |
|
| | def validate_connection_state(state: Dict[str, Any]) -> Tuple[bool, Optional[str]]: |
| | """ |
| | Valida estado de conexão completo |
| | |
| | Args: |
| | state: Estado da conexão |
| | |
| | Returns: |
| | Tupla (válido, mensagem_erro) |
| | """ |
| | try: |
| | connection_type = state.get("connection_type", "csv") |
| | |
| | if connection_type.lower() not in ["csv", "postgresql"]: |
| | return False, f"Tipo de conexão inválido: {connection_type}" |
| |
|
| | if connection_type.lower() == "postgresql": |
| | postgresql_config = state.get("postgresql_config") |
| | if not postgresql_config: |
| | return False, "Configuração postgresql ausente" |
| |
|
| | return validate_postgresql_config(postgresql_config) |
| |
|
| | elif connection_type.lower() == "csv": |
| | file_path = state.get("file_path") |
| | if file_path: |
| | return validate_csv_file_path(file_path) |
| | else: |
| | |
| | import os |
| | from utils.config import SQL_DB_PATH |
| | |
| | if not os.path.exists(SQL_DB_PATH): |
| | return False, "Nenhum arquivo csv fornecido e nenhum banco existente" |
| | |
| | return True, None |
| | |
| | return True, None |
| | |
| | except Exception as e: |
| | return False, f"Erro na validação do estado: {e}" |
| |
|
| |
|
| | def sanitize_postgresql_config(config: Dict[str, Any]) -> Dict[str, Any]: |
| | """ |
| | Sanitiza configuração postgresql removendo espaços e normalizando |
| | |
| | Args: |
| | config: Configuração original |
| | |
| | Returns: |
| | Configuração sanitizada |
| | """ |
| | try: |
| | sanitized = {} |
| | |
| | |
| | sanitized["host"] = str(config.get("host", "")).strip() |
| | |
| | |
| | try: |
| | sanitized["port"] = int(config.get("port", 5432)) |
| | except (ValueError, TypeError): |
| | sanitized["port"] = 5432 |
| | |
| | |
| | sanitized["database"] = str(config.get("database", "")).strip() |
| | |
| | |
| | sanitized["username"] = str(config.get("username", "")).strip() |
| | |
| | |
| | sanitized["password"] = str(config.get("password", "")) |
| | |
| | return sanitized |
| | |
| | except Exception as e: |
| | logging.error(f"Erro ao sanitizar configuração postgresql: {e}") |
| | return config |
| |
|
| |
|
| | def get_connection_error_message(error: Exception) -> str: |
| | """ |
| | Converte erro de conexão em mensagem amigável |
| | |
| | Args: |
| | error: Exceção capturada |
| | |
| | Returns: |
| | Mensagem de erro amigável |
| | """ |
| | error_str = str(error).lower() |
| | |
| | if "password authentication failed" in error_str: |
| | return "❌ Falha na autenticação: Usuário ou senha incorretos" |
| | |
| | elif "could not connect to server" in error_str: |
| | return "❌ Não foi possível conectar ao servidor: Verifique host e porta" |
| | |
| | elif "database" in error_str and "does not exist" in error_str: |
| | return "❌ Banco de dados não existe: Verifique o nome do banco" |
| | |
| | elif "connection refused" in error_str: |
| | return "❌ Conexão recusada: Servidor postgresql pode estar desligado" |
| | |
| | elif "timeout" in error_str: |
| | return "❌ Timeout na conexão: Servidor demorou muito para responder" |
| | |
| | elif "permission denied" in error_str: |
| | return "❌ Permissão negada: Usuário não tem acesso ao banco" |
| | |
| | elif "too many connections" in error_str: |
| | return "❌ Muitas conexões: Servidor postgresql está sobrecarregado" |
| | |
| | else: |
| | return f"❌ Erro de conexão: {str(error)}" |
| |
|