flusql/src/utils/config.rs

751 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Конфигурационный модуль для flusql
//!
//! Этот модуль отвечает за загрузку и управление конфигурацией
//! сервера flusql из файла config.toml и переменных окружения.
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use thiserror::Error;
/// Основная конфигурация flusql
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Общие настройки сервера
#[serde(default = "default_server_config")]
pub server: ServerConfig,
/// Настройки базы данных
#[serde(default = "default_database_config")]
pub database: DatabaseConfig,
/// Настройки логгера
#[serde(default = "default_logging_config")]
pub logging: LoggingConfig,
/// Настройки Lua интерпретатора
#[serde(default = "default_lua_config")]
pub lua: LuaConfig,
/// Настройки кластера
#[serde(default = "default_cluster_config")]
pub cluster: ClusterConfig,
/// Настройки плагинов
#[serde(default = "default_plugins_config")]
pub plugins: PluginsConfig,
/// Настройки HTTP сервера (если включен)
#[serde(default = "default_http_config")]
pub http: HttpConfig,
/// Настройки репликации
#[serde(default = "default_replication_config")]
pub replication: ReplicationConfig,
/// Настройки сети
#[serde(default = "default_network_config")]
pub network: NetworkConfig,
}
/// Конфигурация сети
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
/// IP-адрес для прослушивания
#[serde(default = "default_network_host")]
pub host: String,
/// Порт для прослушивания
#[serde(default = "default_network_port")]
pub port: u16,
/// Разрешить удаленные подключения
#[serde(default = "default_allow_remote")]
pub allow_remote: bool,
/// Таймаут соединения в секундах
#[serde(default = "default_connection_timeout")]
pub connection_timeout: u64,
/// Максимальное количество соединений
#[serde(default = "default_max_connections")]
pub max_connections: u32,
/// Размер буфера для сетевых операций в байтах
#[serde(default = "default_buffer_size")]
pub buffer_size: usize,
}
/// Значения по умолчанию для NetworkConfig
fn default_network_config() -> NetworkConfig {
NetworkConfig {
host: default_network_host(),
port: default_network_port(),
allow_remote: default_allow_remote(),
connection_timeout: default_connection_timeout(),
max_connections: default_max_connections(),
buffer_size: default_buffer_size(),
}
}
fn default_network_host() -> String { "127.0.0.1".to_string() }
fn default_network_port() -> u16 { 8080 }
fn default_allow_remote() -> bool { false }
fn default_connection_timeout() -> u64 { 30 }
fn default_max_connections() -> u32 { 100 }
fn default_buffer_size() -> usize { 8192 }
/// Конфигурация плагинов
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginsConfig {
/// Включена ли система плагинов
#[serde(default = "default_plugins_enabled")]
pub enabled: bool,
/// Директория для плагинов
#[serde(default = "default_plugins_dir")]
pub plugins_dir: String,
/// Включить изоляцию плагинов (sandbox)
#[serde(default = "default_sandbox_enabled")]
pub sandbox_enabled: bool,
/// Максимальное количество плагинов
#[serde(default = "default_max_plugins")]
pub max_plugins: usize,
/// Автозагрузка плагинов при старте
#[serde(default = "default_auto_load")]
pub auto_load: bool,
/// Включить горячую перезагрузку плагинов
#[serde(default = "default_hot_reload")]
pub hot_reload: bool,
/// Таймаут выполнения плагина в секундах
#[serde(default = "default_plugin_timeout")]
pub plugin_timeout_sec: u64,
/// Максимальный размер памяти плагина в МБ
#[serde(default = "default_max_memory_mb")]
pub max_memory_mb: u64,
/// Разрешенные API для плагинов
#[serde(default = "default_allowed_apis")]
pub allowed_apis: Vec<String>,
/// Запрещенные функции Lua
#[serde(default = "default_blocked_functions")]
pub blocked_functions: Vec<String>,
}
/// Значения по умолчанию для PluginsConfig
fn default_plugins_config() -> PluginsConfig {
PluginsConfig {
enabled: default_plugins_enabled(),
plugins_dir: default_plugins_dir(),
sandbox_enabled: default_sandbox_enabled(),
max_plugins: default_max_plugins(),
auto_load: default_auto_load(),
hot_reload: default_hot_reload(),
plugin_timeout_sec: default_plugin_timeout(),
max_memory_mb: default_max_memory_mb(),
allowed_apis: default_allowed_apis(),
blocked_functions: default_blocked_functions(),
}
}
fn default_plugins_enabled() -> bool { true }
fn default_plugins_dir() -> String { "./plugins".to_string() }
fn default_sandbox_enabled() -> bool { true }
fn default_max_plugins() -> usize { 50 }
fn default_auto_load() -> bool { true }
fn default_hot_reload() -> bool { false }
fn default_plugin_timeout() -> u64 { 30 }
fn default_max_memory_mb() -> u64 { 100 }
fn default_allowed_apis() -> Vec<String> {
vec![
"database".to_string(),
"table".to_string(),
"query".to_string(),
"index".to_string(),
"event".to_string(),
"log".to_string(),
]
}
fn default_blocked_functions() -> Vec<String> {
vec![
"io.popen".to_string(),
"os.execute".to_string(),
"os.exit".to_string(),
"debug.debug".to_string(),
"debug.getregistry".to_string(),
"debug.setmetatable".to_string(),
]
}
/// Конфигурация HTTP сервера
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpConfig {
/// Включен ли HTTP сервер
pub enabled: bool,
/// Хост для HTTP сервера
#[serde(default = "default_http_host")]
pub host: String,
/// Порт HTTP сервера
#[serde(default = "default_http_port")]
pub port: u16,
/// Порт HTTPS сервера
#[serde(default = "default_https_port")]
pub https_port: u16,
/// Включена ли поддержка HTTP/2
#[serde(default)]
pub http2_enabled: bool,
/// Включена ли поддержка TLS
#[serde(default)]
pub tls_enabled: bool,
/// Путь к сертификату TLS
#[serde(default)]
pub tls_cert_path: Option<String>,
/// Путь к приватному ключу TLS
#[serde(default)]
pub tls_key_path: Option<String>,
}
/// Значения по умолчанию для HttpConfig
fn default_http_config() -> HttpConfig {
HttpConfig {
enabled: false,
host: default_http_host(),
port: default_http_port(),
https_port: default_https_port(),
http2_enabled: false,
tls_enabled: false,
tls_cert_path: None,
tls_key_path: None,
}
}
fn default_http_host() -> String { "127.0.0.1".to_string() }
fn default_http_port() -> u16 { 8080 }
fn default_https_port() -> u16 { 8443 }
/// Конфигурация репликации
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplicationConfig {
/// Включена ли репликация
pub enabled: bool,
/// Режим репликации
#[serde(default = "default_replication_mode")]
pub mode: String,
/// Мастер-сервер для репликации
#[serde(default)]
pub master: Option<String>,
/// Список слейв-серверов
#[serde(default)]
pub slaves: Vec<String>,
}
/// Значения по умолчанию для ReplicationConfig
fn default_replication_config() -> ReplicationConfig {
ReplicationConfig {
enabled: false,
mode: default_replication_mode(),
master: None,
slaves: vec![],
}
}
fn default_replication_mode() -> String { "async".to_string() }
/// Конфигурация сервера
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
/// Порт сервера
#[serde(default = "default_server_port")]
pub port: u16,
/// Хост сервера
#[serde(default = "default_server_host")]
pub host: String,
/// Максимальное количество одновременных соединений
#[serde(default = "default_server_max_connections")]
pub max_connections: u32,
/// Таймаут соединения в секундах
#[serde(default = "default_server_timeout")]
pub timeout: u64,
/// Размер пула потоков
#[serde(default = "default_thread_pool_size")]
pub thread_pool_size: usize,
/// Включить отладку
#[serde(default = "default_debug_enabled")]
pub debug: bool,
/// Путь к PID файлу
#[serde(default)]
pub pid_file: Option<String>,
}
/// Значения по умолчанию для ServerConfig
fn default_server_config() -> ServerConfig {
ServerConfig {
port: default_server_port(),
host: default_server_host(),
max_connections: default_server_max_connections(),
timeout: default_server_timeout(),
thread_pool_size: default_thread_pool_size(),
debug: default_debug_enabled(),
pid_file: None,
}
}
fn default_server_port() -> u16 { 5432 }
fn default_server_host() -> String { "127.0.0.1".to_string() }
fn default_server_max_connections() -> u32 { 100 }
fn default_server_timeout() -> u64 { 30 }
fn default_thread_pool_size() -> usize { 4 }
fn default_debug_enabled() -> bool { false }
/// Конфигурация базы данных
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
/// Директория для хранения данных
#[serde(default = "default_data_dir")]
pub data_dir: String,
/// Автоматически создавать базу данных при первом подключении
#[serde(default = "default_auto_create")]
pub auto_create: bool,
/// Режим транзакций
#[serde(default = "default_transaction_mode")]
pub transaction_mode: String,
/// Размер кэша в МБ
#[serde(default = "default_cache_size")]
pub cache_size_mb: u64,
/// Размер страницы в байтах
#[serde(default = "default_page_size")]
pub page_size: u32,
/// Включить MVCC (Multi-Version Concurrency Control)
#[serde(default = "default_mvcc_enabled")]
pub mvcc_enabled: bool,
/// Включить WAL (Write-Ahead Logging)
#[serde(default = "default_wal_enabled")]
pub wal_enabled: bool,
/// Максимальный размер WAL в МБ
#[serde(default = "default_max_wal_size")]
pub max_wal_size_mb: u64,
/// Автоматическая проверка целостности при запуске
#[serde(default = "default_integrity_check")]
pub integrity_check: bool,
/// Частота автоматического сохранения в секундах
#[serde(default = "default_auto_save_interval")]
pub auto_save_interval: u64,
/// Максимальное количество открытых файлов БД
#[serde(default = "default_max_open_files")]
pub max_open_files: u32,
}
/// Значения по умолчанию для DatabaseConfig
fn default_database_config() -> DatabaseConfig {
DatabaseConfig {
data_dir: default_data_dir(),
auto_create: default_auto_create(),
transaction_mode: default_transaction_mode(),
cache_size_mb: default_cache_size(),
page_size: default_page_size(),
mvcc_enabled: default_mvcc_enabled(),
wal_enabled: default_wal_enabled(),
max_wal_size_mb: default_max_wal_size(),
integrity_check: default_integrity_check(),
auto_save_interval: default_auto_save_interval(),
max_open_files: default_max_open_files(),
}
}
fn default_data_dir() -> String { "./data".to_string() }
fn default_auto_create() -> bool { true }
fn default_transaction_mode() -> String { "write_ahead_log".to_string() }
fn default_cache_size() -> u64 { 100 }
fn default_page_size() -> u32 { 8192 } // 8KB страницы по умолчанию
fn default_mvcc_enabled() -> bool { true }
fn default_wal_enabled() -> bool { true }
fn default_max_wal_size() -> u64 { 100 }
fn default_integrity_check() -> bool { true }
fn default_auto_save_interval() -> u64 { 60 } // 60 секунд
fn default_max_open_files() -> u32 { 1000 }
/// Конфигурация логгера
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
/// Уровень логирования
#[serde(default = "default_log_level")]
pub level: String,
/// Путь к файлу логов
#[serde(default = "default_log_path")]
pub log_file: String,
/// Максимальный размер файла логов в МБ
#[serde(default = "default_max_log_size")]
pub max_size_mb: u64,
/// Количество ротируемых файлов
#[serde(default = "default_backup_count")]
pub backup_count: u32,
/// Формат логов
#[serde(default = "default_log_format")]
pub format: String,
/// Включить логирование в stdout
#[serde(default = "default_stdout_enabled")]
pub stdout_enabled: bool,
/// Включить логирование в stderr
#[serde(default = "default_stderr_enabled")]
pub stderr_enabled: bool,
/// Включить логирование SQL запросов
#[serde(default = "default_sql_logging")]
pub sql_logging: bool,
/// Включить медленный лог (запросы дольше N секунд)
#[serde(default)]
pub slow_query_threshold_sec: Option<u64>,
}
/// Значения по умолчанию для LoggingConfig
fn default_logging_config() -> LoggingConfig {
LoggingConfig {
level: default_log_level(),
log_file: default_log_path(),
max_size_mb: default_max_log_size(),
backup_count: default_backup_count(),
format: default_log_format(),
stdout_enabled: default_stdout_enabled(),
stderr_enabled: default_stderr_enabled(),
sql_logging: default_sql_logging(),
slow_query_threshold_sec: None,
}
}
fn default_log_level() -> String { "info".to_string() }
fn default_log_path() -> String { "flusql.log".to_string() }
fn default_max_log_size() -> u64 { 10 }
fn default_backup_count() -> u32 { 5 }
fn default_log_format() -> String { "json".to_string() }
fn default_stdout_enabled() -> bool { true }
fn default_stderr_enabled() -> bool { false }
fn default_sql_logging() -> bool { true }
/// Конфигурация Lua интерпретатора
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuaConfig {
/// Включен ли Lua интерпретатор
#[serde(default = "default_lua_enabled")]
pub enabled: bool,
/// Путь к директории со скриптами
#[serde(default = "default_lua_scripts_dir")]
pub scripts_dir: String,
/// Максимальное время выполнения скрипта в секундах
#[serde(default = "default_lua_timeout")]
pub timeout_seconds: u64,
/// Максимальная память для Lua VM в МБ
#[serde(default = "default_lua_memory_limit")]
pub memory_limit_mb: u64,
/// Разрешить доступ к файловой системе
#[serde(default = "default_lua_filesystem_access")]
pub filesystem_access: bool,
/// Разрешить сетевые операции
#[serde(default = "default_lua_network_access")]
pub network_access: bool,
/// Список разрешенных модулей
#[serde(default = "default_lua_allowed_modules")]
pub allowed_modules: Vec<String>,
}
/// Значения по умолчанию для LuaConfig
fn default_lua_config() -> LuaConfig {
LuaConfig {
enabled: default_lua_enabled(),
scripts_dir: default_lua_scripts_dir(),
timeout_seconds: default_lua_timeout(),
memory_limit_mb: default_lua_memory_limit(),
filesystem_access: default_lua_filesystem_access(),
network_access: default_lua_network_access(),
allowed_modules: default_lua_allowed_modules(),
}
}
fn default_lua_enabled() -> bool { true }
fn default_lua_scripts_dir() -> String { "./lua-scripts".to_string() }
fn default_lua_timeout() -> u64 { 30 }
fn default_lua_memory_limit() -> u64 { 100 }
fn default_lua_filesystem_access() -> bool { false }
fn default_lua_network_access() -> bool { false }
fn default_lua_allowed_modules() -> Vec<String> {
vec![
"string".to_string(),
"table".to_string(),
"math".to_string(),
"os".to_string(),
]
}
/// Конфигурация кластера
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterConfig {
/// Включен ли режим кластера
#[serde(default = "default_cluster_enabled")]
pub enabled: bool,
/// Идентификатор узла
#[serde(default = "default_node_id")]
pub node_id: String,
/// Адрес узла
#[serde(default = "default_node_address")]
pub node_address: String,
/// Режим кластера
#[serde(default = "default_cluster_mode")]
pub mode: String,
/// Список узлов кластера
#[serde(default)]
pub nodes: Vec<String>,
/// Интервал heartbeat в секундах
#[serde(default = "default_heartbeat_interval")]
pub heartbeat_interval: u64,
/// Таймаут heartbeat в секундах
#[serde(default = "default_heartbeat_timeout")]
pub heartbeat_timeout: u64,
/// Включить автоматическое восстановление
#[serde(default = "default_auto_recovery")]
pub auto_recovery: bool,
/// Максимальное количество реплик
#[serde(default = "default_max_replicas")]
pub max_replicas: u32,
}
/// Значения по умолчанию для ClusterConfig
fn default_cluster_config() -> ClusterConfig {
ClusterConfig {
enabled: default_cluster_enabled(),
node_id: default_node_id(),
node_address: default_node_address(),
mode: default_cluster_mode(),
nodes: vec![],
heartbeat_interval: default_heartbeat_interval(),
heartbeat_timeout: default_heartbeat_timeout(),
auto_recovery: default_auto_recovery(),
max_replicas: default_max_replicas(),
}
}
fn default_cluster_enabled() -> bool { false }
fn default_node_id() -> String { "node_1".to_string() }
fn default_node_address() -> String { "127.0.0.1:8080".to_string() }
fn default_cluster_mode() -> String { "single".to_string() }
fn default_heartbeat_interval() -> u64 { 5 }
fn default_heartbeat_timeout() -> u64 { 30 }
fn default_auto_recovery() -> bool { true }
fn default_max_replicas() -> u32 { 3 }
impl Default for Config {
fn default() -> Self {
Self {
server: default_server_config(),
database: default_database_config(),
logging: default_logging_config(),
lua: default_lua_config(),
cluster: default_cluster_config(),
plugins: default_plugins_config(),
http: default_http_config(),
replication: default_replication_config(),
network: default_network_config(),
}
}
}
impl Config {
/// Загрузка конфигурации из файла
pub fn load(path: &str) -> Result<Self, ConfigError> {
let config_content = fs::read_to_string(path)
.map_err(|e| ConfigError::IoError(e))?;
let config: Config = toml::from_str(&config_content)
.map_err(|e| ConfigError::ParseError(e))?;
Ok(config)
}
/// Создание конфигурации по умолчанию
pub fn default_with_path(data_dir: &str) -> Self {
let mut config = Self::default();
config.database.data_dir = data_dir.to_string();
config
}
/// Получение пути к директории данных для базы данных
pub fn get_data_path(&self, db_name: &str) -> String {
format!("{}/{}", self.database.data_dir, db_name)
}
/// Сохранение конфигурации в файл
pub fn save(&self, path: &str) -> Result<(), ConfigError> {
let config_content = toml::to_string_pretty(self)
.map_err(|e| ConfigError::SerializeError(e))?;
// Создаем директорию если она не существует
if let Some(parent) = Path::new(path).parent() {
fs::create_dir_all(parent)
.map_err(|e| ConfigError::IoError(e))?;
}
fs::write(path, config_content)
.map_err(|e| ConfigError::IoError(e))
}
/// Создание файла конфигурации по умолчанию
pub fn create_default_config(path: &str) -> Result<(), ConfigError> {
let default_config = Self::default();
default_config.save(path)
}
/// Получение конфигурации из переменных окружения или файла
pub fn from_env_or_file(default_path: &str) -> Result<Self, ConfigError> {
// Сначала пробуем загрузить из переменной окружения
if let Ok(config_path) = std::env::var("FLUSQL_CONFIG") {
return Self::load(&config_path);
}
// Пробуем загрузить из текущей директории
if Path::new(default_path).exists() {
return Self::load(default_path);
}
// Пробуем загрузить из домашней директории
if let Ok(home_dir) = std::env::var("HOME") {
let home_config = format!("{}/.config/flusql/config.toml", home_dir);
if Path::new(&home_config).exists() {
return Self::load(&home_config);
}
}
// Пробуем загрузить из /etc
let etc_config = "/etc/flusql/config.toml";
if Path::new(etc_config).exists() {
return Self::load(etc_config);
}
// Создаем конфигурацию по умолчанию
Ok(Self::default())
}
/// Проверка валидности конфигурации
pub fn validate(&self) -> Result<(), ConfigError> {
// Проверяем размер страницы (должен быть степенью двойки и в разумных пределах)
if self.database.page_size < 512 || self.database.page_size > 65536 {
return Err(ConfigError::Invalid(format!(
"Page size must be between 512 and 65536 bytes, got {}",
self.database.page_size
)));
}
// Проверяем что page_size является степенью двойки
if self.database.page_size & (self.database.page_size - 1) != 0 {
return Err(ConfigError::Invalid(format!(
"Page size must be a power of two, got {}",
self.database.page_size
)));
}
// Проверяем порты
// Исправление: убраны все проверки на > 65535, так как тип u16 гарантирует этот предел
// Оставляем только проверку на 0 (порт 0 недопустим для сервера)
if self.server.port == 0 {
return Err(ConfigError::Invalid(
"Server port cannot be 0".to_string()
));
}
if self.http.enabled {
// Исправление: убраны все проверки на > 65535, так как тип u16 гарантирует этот предел
// Оставляем только проверку на 0 (порт 0 недопустим для HTTP сервера)
if self.http.port == 0 {
return Err(ConfigError::Invalid(
"HTTP port cannot be 0".to_string()
));
}
if self.http.tls_enabled {
if self.http.tls_cert_path.is_none() || self.http.tls_key_path.is_none() {
return Err(ConfigError::Invalid(
"TLS requires both certificate and key paths".to_string()
));
}
}
}
// Проверяем директорию данных
if self.database.data_dir.trim().is_empty() {
return Err(ConfigError::Invalid("Data directory cannot be empty".to_string()));
}
Ok(())
}
}
/// Ошибки конфигурации
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("IO error: {0}")]
IoError(std::io::Error),
#[error("Parse error: {0}")]
ParseError(toml::de::Error),
#[error("Serialize error: {0}")]
SerializeError(toml::ser::Error),
#[error("Configuration not found")]
NotFound,
#[error("Invalid configuration: {0}")]
Invalid(String),
}