File size: 8,928 Bytes
e982206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
"""
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:
        # Campos obrigatórios
        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}"
        
        # Validação específica do host
        host = str(config["host"]).strip()
        if not host:
            return False, "Host não pode estar vazio"
        
        # Validação básica de formato de host
        if not _is_valid_host(host):
            return False, "Formato de host inválido"
        
        # Validação da porta
        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"
        
        # Validação do nome do banco
        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"
        
        # Validação do usuário
        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"
        
        # Validação da senha (básica)
        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
    """
    # Regex para IPv4
    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]?)$'
    
    # Regex para hostname/FQDN
    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])?$'
    
    # Permite localhost
    if host.lower() == 'localhost':
        return True
    
    # Valida IPv4
    if re.match(ipv4_pattern, host):
        return True
    
    # Valida hostname
    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
    """
    # postgresql: deve começar com letra ou underscore, 
    # pode conter letras, números, underscores e hífens
    pattern = r'^[a-zA-Z_][a-zA-Z0-9_-]*$'
    
    # Comprimento máximo típico
    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
    """
    # Similar ao nome do banco
    pattern = r'^[a-zA-Z_][a-zA-Z0-9_-]*$'
    
    # Comprimento máximo típico
    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"
        
        # Verifica se é um arquivo (não diretório)
        if not os.path.isfile(file_path):
            return False, "Caminho deve apontar para um arquivo"
        
        # Verifica tamanho do arquivo
        file_size = os.path.getsize(file_path)
        if file_size == 0:
            return False, "Arquivo csv está vazio"
        
        # Limite de 5GB
        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:
                # Verifica se há banco existente
                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 = {}
        
        # Host
        sanitized["host"] = str(config.get("host", "")).strip()
        
        # Porta
        try:
            sanitized["port"] = int(config.get("port", 5432))
        except (ValueError, TypeError):
            sanitized["port"] = 5432
        
        # Database
        sanitized["database"] = str(config.get("database", "")).strip()
        
        # Username
        sanitized["username"] = str(config.get("username", "")).strip()
        
        # Password (não remove espaços - pode ser intencional)
        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)}"