BREAKING CHANGE(core): replace the TypeScript database engine with a Rust-backed embedded server and bridge
This commit is contained in:
12
rust/crates/rustdb-config/Cargo.toml
Normal file
12
rust/crates/rustdb-config/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "rustdb-config"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
description = "Configuration types for RustDb, compatible with SmartDB JSON schema"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
181
rust/crates/rustdb-config/src/lib.rs
Normal file
181
rust/crates/rustdb-config/src/lib.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Storage backend type.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum StorageType {
|
||||
Memory,
|
||||
File,
|
||||
}
|
||||
|
||||
impl Default for StorageType {
|
||||
fn default() -> Self {
|
||||
StorageType::Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// Top-level configuration for RustDb server.
|
||||
/// Field names use camelCase to match the TypeScript SmartdbServer options.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RustDbOptions {
|
||||
/// TCP port to listen on (default: 27017)
|
||||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
|
||||
/// Host/IP to bind to (default: "127.0.0.1")
|
||||
#[serde(default = "default_host")]
|
||||
pub host: String,
|
||||
|
||||
/// Unix socket path (overrides TCP if set)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub socket_path: Option<String>,
|
||||
|
||||
/// Storage backend type
|
||||
#[serde(default)]
|
||||
pub storage: StorageType,
|
||||
|
||||
/// Base path for file storage (required when storage = "file")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage_path: Option<String>,
|
||||
|
||||
/// Path for periodic persistence of in-memory data
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub persist_path: Option<String>,
|
||||
|
||||
/// Interval in ms for periodic persistence (default: 60000)
|
||||
#[serde(default = "default_persist_interval")]
|
||||
pub persist_interval_ms: u64,
|
||||
}
|
||||
|
||||
fn default_port() -> u16 {
|
||||
27017
|
||||
}
|
||||
|
||||
fn default_host() -> String {
|
||||
"127.0.0.1".to_string()
|
||||
}
|
||||
|
||||
fn default_persist_interval() -> u64 {
|
||||
60000
|
||||
}
|
||||
|
||||
impl Default for RustDbOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: default_port(),
|
||||
host: default_host(),
|
||||
socket_path: None,
|
||||
storage: StorageType::default(),
|
||||
storage_path: None,
|
||||
persist_path: None,
|
||||
persist_interval_ms: default_persist_interval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RustDbOptions {
|
||||
/// Load options from a JSON config file.
|
||||
pub fn from_file(path: &str) -> Result<Self, ConfigError> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(|e| ConfigError::IoError(e.to_string()))?;
|
||||
let options: Self = serde_json::from_str(&content)
|
||||
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
|
||||
options.validate()?;
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
/// Validate the configuration.
|
||||
pub fn validate(&self) -> Result<(), ConfigError> {
|
||||
if self.storage == StorageType::File && self.storage_path.is_none() {
|
||||
return Err(ConfigError::ValidationError(
|
||||
"storagePath is required when storage is 'file'".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the connection URI for this server configuration.
|
||||
pub fn connection_uri(&self) -> String {
|
||||
if let Some(ref socket_path) = self.socket_path {
|
||||
let encoded = urlencoding(socket_path);
|
||||
format!("mongodb://{}", encoded)
|
||||
} else {
|
||||
format!("mongodb://{}:{}", self.host, self.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple URL encoding for socket paths (encode / as %2F, etc.)
|
||||
fn urlencoding(s: &str) -> String {
|
||||
s.chars()
|
||||
.map(|c| match c {
|
||||
'/' => "%2F".to_string(),
|
||||
':' => "%3A".to_string(),
|
||||
' ' => "%20".to_string(),
|
||||
_ => c.to_string(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Configuration errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("IO error: {0}")]
|
||||
IoError(String),
|
||||
#[error("Parse error: {0}")]
|
||||
ParseError(String),
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_options() {
|
||||
let opts = RustDbOptions::default();
|
||||
assert_eq!(opts.port, 27017);
|
||||
assert_eq!(opts.host, "127.0.0.1");
|
||||
assert!(opts.socket_path.is_none());
|
||||
assert_eq!(opts.storage, StorageType::Memory);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_from_json() {
|
||||
let json = r#"{"port": 27018, "storage": "file", "storagePath": "./data"}"#;
|
||||
let opts: RustDbOptions = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(opts.port, 27018);
|
||||
assert_eq!(opts.storage, StorageType::File);
|
||||
assert_eq!(opts.storage_path, Some("./data".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connection_uri_tcp() {
|
||||
let opts = RustDbOptions::default();
|
||||
assert_eq!(opts.connection_uri(), "mongodb://127.0.0.1:27017");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connection_uri_socket() {
|
||||
let opts = RustDbOptions {
|
||||
socket_path: Some("/tmp/smartdb-test.sock".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
opts.connection_uri(),
|
||||
"mongodb://%2Ftmp%2Fsmartdb-test.sock"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validation_file_storage_requires_path() {
|
||||
let opts = RustDbOptions {
|
||||
storage: StorageType::File,
|
||||
storage_path: None,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(opts.validate().is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user