futriix/src/common/mod.rs
2025-11-28 01:03:13 +03:00

567 lines
17 KiB
Rust
Executable File
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.

// src/common/mod.rs
//! Общие модули для Futriix
//!
//! Содержит общие структуры данных, ошибки, протоколы и конфигурацию,
//! используемые во всех компонентах системы с wait-free архитектурой.
use thiserror::Error;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
/// Основной тип ошибки для Futriix
#[derive(Error, Debug)]
pub enum FutriixError {
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Database error: {0}")]
DatabaseError(String),
#[error("Lua error: {0}")]
LuaError(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Replication error: {0}")]
ReplicationError(String),
#[error("HTTP error: {0}")]
HttpError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("CSV error: {0}")]
CsvError(String),
#[error("Unknown error: {0}")]
Unknown(String),
}
// Реализация преобразования из rlua::Error в FutriixError
impl From<rlua::Error> for FutriixError {
fn from(error: rlua::Error) -> Self {
FutriixError::LuaError(error.to_string())
}
}
/// Тип результата для Futriix
pub type Result<T> = std::result::Result<T, FutriixError>;
// Модуль конфигурации
pub mod config {
use super::*;
/// Конфигурация сервера
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
#[serde(default = "ServerConfig::default")]
pub server: ServerConfig,
#[serde(default = "ReplicationConfig::default")]
pub replication: ReplicationConfig,
#[serde(default = "ClusterConfig::default")] // Новая секция кластера
pub cluster: ClusterConfig,
#[serde(default = "LuaConfig::default")]
pub lua: LuaConfig,
#[serde(default = "AclConfig::default")]
pub acl: AclConfig,
#[serde(default = "TlsConfig::default")]
pub tls: TlsConfig,
#[serde(default = "CsvConfig::default")]
pub csv: CsvConfig,
}
/// Конфигурация серверных параметров
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ServerConfig {
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_port")]
pub port: u16,
#[serde(default)]
pub http_port: Option<u16>,
#[serde(default)]
pub https_port: Option<u16>,
#[serde(default)]
pub http2_enabled: Option<bool>,
#[serde(default = "default_http_enabled")] // Новая директива
pub http: bool,
#[serde(default = "default_https_enabled")] // Новая директива
pub https: bool,
}
/// Конфигурация репликации
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ReplicationConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_master_nodes")]
pub master_nodes: Vec<String>,
#[serde(default = "default_sync_interval")]
pub sync_interval: u64,
}
/// Конфигурация кластера
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ClusterConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_cluster_name")]
pub name: String,
}
/// Конфигурация Lua
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LuaConfig {
#[serde(default = "default_scripts_dir")]
pub scripts_dir: String,
#[serde(default)]
pub auto_execute: Vec<String>,
}
/// Конфигурация ACL
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AclConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_allowed_ips")]
pub allowed_ips: Vec<String>,
#[serde(default)]
pub denied_ips: Vec<String>,
}
/// Конфигурация TLS
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TlsConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_cert_path")]
pub cert_path: String,
#[serde(default = "default_key_path")]
pub key_path: String,
}
/// Конфигурация CSV
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CsvConfig {
#[serde(default = "default_csv_import_dir")]
pub import_dir: String,
#[serde(default = "default_csv_export_dir")]
pub export_dir: String,
#[serde(default = "default_max_csv_file_size")]
pub max_file_size: u64,
}
// Функции для значений по умолчанию
fn default_host() -> String {
"127.0.0.1".to_string()
}
fn default_port() -> u16 {
8081
}
fn default_http_enabled() -> bool {
true
}
fn default_https_enabled() -> bool {
false
}
fn default_master_nodes() -> Vec<String> {
vec!["127.0.0.1:8081".to_string(), "127.0.0.1:8083".to_string()]
}
fn default_sync_interval() -> u64 {
5000
}
fn default_cluster_name() -> String {
"futriix-default-cluster".to_string()
}
fn default_scripts_dir() -> String {
"lua_scripts".to_string()
}
fn default_allowed_ips() -> Vec<String> {
vec!["127.0.0.1".to_string(), "::1".to_string()]
}
fn default_cert_path() -> String {
"certs/cert.pem".to_string()
}
fn default_key_path() -> String {
"certs/key.pem".to_string()
}
fn default_csv_import_dir() -> String {
"/futriix/csv/import".to_string()
}
fn default_csv_export_dir() -> String {
"/futriix/csv/export".to_string()
}
fn default_max_csv_file_size() -> u64 {
104857600 // 100MB
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: default_host(),
port: default_port(),
http_port: Some(8082),
https_port: None,
http2_enabled: Some(false),
http: default_http_enabled(),
https: default_https_enabled(),
}
}
}
impl Default for ReplicationConfig {
fn default() -> Self {
Self {
enabled: false,
master_nodes: default_master_nodes(),
sync_interval: default_sync_interval(),
}
}
}
impl Default for ClusterConfig {
fn default() -> Self {
Self {
enabled: false,
name: default_cluster_name(),
}
}
}
impl Default for LuaConfig {
fn default() -> Self {
Self {
scripts_dir: default_scripts_dir(),
auto_execute: vec!["init.lua".to_string()],
}
}
}
impl Default for AclConfig {
fn default() -> Self {
Self {
enabled: false,
allowed_ips: default_allowed_ips(),
denied_ips: vec![],
}
}
}
impl Default for TlsConfig {
fn default() -> Self {
Self {
enabled: false,
cert_path: default_cert_path(),
key_path: default_key_path(),
}
}
}
impl Default for CsvConfig {
fn default() -> Self {
Self {
import_dir: default_csv_import_dir(),
export_dir: default_csv_export_dir(),
max_file_size: default_max_csv_file_size(),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
server: ServerConfig::default(),
replication: ReplicationConfig::default(),
cluster: ClusterConfig::default(),
lua: LuaConfig::default(),
acl: AclConfig::default(),
tls: TlsConfig::default(),
csv: CsvConfig::default(),
}
}
}
impl Config {
/// Загрузка конфигурации из файла
pub fn load(path: &str) -> Result<Self> {
let path = Path::new(path);
if !path.exists() {
// Создание конфигурации по умолчанию
let default_config = Config::default();
let toml_content = toml::to_string_pretty(&default_config)
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
fs::write(path, toml_content)
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
println!("Created default configuration file: {}", path.display());
return Ok(default_config);
}
let content = fs::read_to_string(path)
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
// Парсим конфигурацию с использованием значений по умолчанию
let mut config: Config = toml::from_str(&content)
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
// Убеждаемся, что все поля имеют значения по умолчанию, если они отсутствуют
if config.server.host.is_empty() {
config.server.host = default_host();
}
if config.server.port == 0 {
config.server.port = default_port();
}
if config.replication.master_nodes.is_empty() {
config.replication.master_nodes = default_master_nodes();
}
if config.replication.sync_interval == 0 {
config.replication.sync_interval = default_sync_interval();
}
if config.cluster.name.is_empty() {
config.cluster.name = default_cluster_name();
}
if config.lua.scripts_dir.is_empty() {
config.lua.scripts_dir = default_scripts_dir();
}
if config.acl.allowed_ips.is_empty() {
config.acl.allowed_ips = default_allowed_ips();
}
if config.tls.cert_path.is_empty() {
config.tls.cert_path = default_cert_path();
}
if config.tls.key_path.is_empty() {
config.tls.key_path = default_key_path();
}
if config.csv.import_dir.is_empty() {
config.csv.import_dir = default_csv_import_dir();
}
if config.csv.export_dir.is_empty() {
config.csv.export_dir = default_csv_export_dir();
}
if config.csv.max_file_size == 0 {
config.csv.max_file_size = default_max_csv_file_size();
}
Ok(config)
}
/// Сохранение конфигурации в файл
#[allow(dead_code)]
pub fn save(&self, path: &str) -> Result<()> {
let toml_content = toml::to_string_pretty(self)
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
fs::write(path, toml_content)
.map_err(|e| FutriixError::ConfigError(e.to_string()))
}
}
}
// Модуль протокола
pub mod protocol {
use serde::{Deserialize, Serialize};
/// Команды для выполнения в базе данных
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Command {
// Базовые CRUD команды
Create {
collection: String,
document: Vec<u8>,
},
Read {
collection: String,
id: String,
},
Update {
collection: String,
id: String,
document: Vec<u8>,
},
Delete {
collection: String,
id: String,
},
Query {
collection: String,
filter: Vec<u8>,
},
// Команды для процедур и транзакций
CreateProcedure {
name: String,
code: Vec<u8>,
},
CallProcedure {
name: String,
},
BeginTransaction {
transaction_id: String,
},
CommitTransaction {
transaction_id: String,
},
RollbackTransaction {
transaction_id: String,
},
// Команды для индексов
CreateIndex {
collection: String,
index: crate::server::database::Index,
},
QueryByIndex {
collection: String,
index_name: String,
value: Vec<u8>,
},
// Команды для шардинга с Raft
AddShardNode {
node_id: String,
address: String,
capacity: u64,
},
RemoveShardNode {
node_id: String,
},
MigrateShard {
collection: String,
from_node: String,
to_node: String,
shard_key: String,
},
RebalanceCluster,
GetClusterStatus,
StartElection, // Новая команда для Raft выборов
GetRaftNodes, // Новая команда для получения Raft узлов
// Команды для constraints
AddConstraint {
collection: String,
constraint_name: String,
constraint_type: String,
field: String,
value: Vec<u8>,
},
RemoveConstraint {
collection: String,
constraint_name: String,
},
// Команды для компрессии
EnableCompression {
collection: String,
algorithm: String,
},
DisableCompression {
collection: String,
},
// Команды для глобальных индексов
CreateGlobalIndex {
name: String,
field: String,
unique: bool,
},
QueryGlobalIndex {
index_name: String,
value: Vec<u8>,
},
// Новые команды для CSV импорта/экспорта
ImportCsv {
collection: String,
file_path: String,
},
ExportCsv {
collection: String,
file_path: String,
},
ListCsvFiles,
GetImportProgress {
collection: String,
},
}
/// Ответы от базы данных
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Response {
Success(Vec<u8>),
Error(String),
}
/// Сообщение для репликации
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplicationMessage {
pub sequence: u64,
pub command: Command,
pub timestamp: i64,
}
/// Структура для информации о шарде
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShardInfo {
pub node_id: String,
pub address: String,
pub capacity: u64,
pub used: u64,
pub collections: Vec<String>,
}
/// Структура для информации о Raft узле
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RaftNodeInfo {
pub node_id: String,
pub address: String,
pub state: String, // "leader", "follower", "candidate"
pub term: u64,
pub last_heartbeat: i64,
}
/// Структура для статуса кластера
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterStatus {
pub nodes: Vec<ShardInfo>,
pub total_capacity: u64,
pub total_used: u64,
pub rebalance_needed: bool,
pub cluster_formed: bool, // Новое поле: собран ли кластер
pub leader_exists: bool, // Новое поле: существует ли лидер
pub raft_nodes: Vec<RaftNodeInfo>, // Новое поле: список Raft узлов
}
/// Wait-Free сериализация сообщений
pub fn serialize<T: serde::Serialize>(value: &T) -> crate::common::Result<Vec<u8>> {
rmp_serde::to_vec(value)
.map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))
}
/// Wait-Free десериализация сообщений
pub fn deserialize<'a, T: serde::Deserialize<'a>>(bytes: &'a [u8]) -> crate::common::Result<T> {
rmp_serde::from_slice(bytes)
.map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))
}
}